Skip to content

Commit

Permalink
Dry Run + On-Demand PR generation data models (#606)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeljguarino authored Jan 12, 2024
1 parent eb361c7 commit e0e5bd5
Show file tree
Hide file tree
Showing 23 changed files with 544 additions and 12 deletions.
28 changes: 27 additions & 1 deletion assets/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,7 @@ export type Component = {
};

export type ComponentAttributes = {
content?: InputMaybe<ComponentContentAttributes>;
group: Scalars['String']['input'];
kind: Scalars['String']['input'];
name: Scalars['String']['input'];
Expand All @@ -843,6 +844,24 @@ export type ComponentAttributes = {
version: Scalars['String']['input'];
};

/** dry run content of a service component */
export type ComponentContent = {
__typename?: 'ComponentContent';
/** the inferred desired state of this component */
desired?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
insertedAt?: Maybe<Scalars['DateTime']['output']>;
live?: Maybe<Scalars['String']['output']>;
updatedAt?: Maybe<Scalars['DateTime']['output']>;
};

/** the content of a component when visualized in dry run state */
export type ComponentContentAttributes = {
/** the desired state of a service component as determined from the configured manifests */
desired?: InputMaybe<Scalars['String']['input']>;
live?: InputMaybe<Scalars['String']['input']>;
};

export enum ComponentState {
Failed = 'FAILED',
Paused = 'PAUSED',
Expand Down Expand Up @@ -3304,7 +3323,7 @@ export type RootQueryTypeClustersArgs = {
after?: InputMaybe<Scalars['String']['input']>;
before?: InputMaybe<Scalars['String']['input']>;
first?: InputMaybe<Scalars['Int']['input']>;
health?: InputMaybe<Scalars['Boolean']['input']>;
healthy?: InputMaybe<Scalars['Boolean']['input']>;
last?: InputMaybe<Scalars['Int']['input']>;
q?: InputMaybe<Scalars['String']['input']>;
tag?: InputMaybe<TagInput>;
Expand Down Expand Up @@ -3940,6 +3959,8 @@ export type ServiceComponent = {
__typename?: 'ServiceComponent';
/** any api deprecations discovered from this component */
apiDeprecations?: Maybe<Array<Maybe<ApiDeprecation>>>;
/** the live and desired states of this service component */
content?: Maybe<ComponentContent>;
/** api group of this resource */
group?: Maybe<Scalars['String']['output']>;
/** internal id */
Expand Down Expand Up @@ -3982,6 +4003,8 @@ export type ServiceDeployment = {
deletedAt?: Maybe<Scalars['DateTime']['output']>;
/** fetches the /docs directory within this services git tree. This is a heavy operation and should NOT be used in list queries */
docs?: Maybe<Array<Maybe<GitFile>>>;
/** whether this service should not actively reconcile state and instead simply report pending changes */
dryRun?: Maybe<Scalars['Boolean']['output']>;
/** whether this service is editable */
editable?: Maybe<Scalars['Boolean']['output']>;
/** a list of errors generated by the deployment operator */
Expand All @@ -3996,6 +4019,8 @@ export type ServiceDeployment = {
/** internal id of this service */
id: Scalars['ID']['output'];
insertedAt?: Maybe<Scalars['DateTime']['output']>;
/** the desired sync interval for this service */
interval?: Maybe<Scalars['String']['output']>;
/** kustomize related service metadata */
kustomize?: Maybe<Kustomize>;
/** the commit message currently in use */
Expand Down Expand Up @@ -4045,6 +4070,7 @@ export type ServiceDeploymentRevisionsArgs = {
export type ServiceDeploymentAttributes = {
configuration?: InputMaybe<Array<InputMaybe<ConfigAttributes>>>;
docsPath?: InputMaybe<Scalars['String']['input']>;
dryRun?: InputMaybe<Scalars['Boolean']['input']>;
git?: InputMaybe<GitRefAttributes>;
helm?: InputMaybe<HelmConfigAttributes>;
kustomize?: InputMaybe<KustomizeAttributes>;
Expand Down
2 changes: 2 additions & 0 deletions lib/console/commands/plural.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ defmodule Console.Commands.Plural do

def repair(), do: plural("repair", [])

def template(conf), do: plural("cd", ["template", "--file", conf])

def plural_home(command, args, env \\ []),
do: cmd("plural", [command | args], System.user_home(), env)

Expand Down
118 changes: 113 additions & 5 deletions lib/console/deployments/git.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,29 @@ defmodule Console.Deployments.Git do
import Console.Deployments.Policies
alias Console.PubSub
alias Console.Deployments.Settings
alias Console.Schema.{GitRepository, User, DeploymentSettings}
alias Console.Schema.{GitRepository, User, DeploymentSettings, ScmConnection, ScmWebhook, PrAutomation}

@type repository_resp :: {:ok, GitRepository.t} | Console.error
@type connection_resp :: {:ok, ScmConnection.t} | Console.error
@type webhook_resp :: {:ok, ScmWebhook.t} | Console.error
@type automation_resp :: {:ok, PrAutomation.t} | Console.error

def get_repository(id), do: Console.Repo.get(GitRepository, id)
def get_repository(id), do: Repo.get(GitRepository, id)

def get_repository!(id), do: Console.Repo.get!(GitRepository, id)
def get_repository!(id), do: Repo.get!(GitRepository, id)

def get_by_url!(url), do: Console.Repo.get_by!(GitRepository, url: url)
def get_by_url!(url), do: Repo.get_by!(GitRepository, url: url)

def get_by_url(url), do: Console.Repo.get_by(GitRepository, url: url)
def get_by_url(url), do: Repo.get_by(GitRepository, url: url)

def get_scm_connection(id), do: Repo.get(ScmConnection, id)
def get_scm_connection!(id), do: Repo.get!(ScmConnection, id)

def get_scm_webhook(id), do: Repo.get(ScmWebhook, id)
def get_scm_webhook!(id), do: Repo.get!(ScmWebhook, id)

def get_pr_automation(id), do: Repo.get(PrAutomation, id)
def get_pr_automation!(id), do: Repo.get!(PrAutomation, id)

def deploy_url(), do: "https://github.com/pluralsh/deployment-operator.git"

Expand Down Expand Up @@ -73,6 +85,102 @@ defmodule Console.Deployments.Git do
end
end

@doc """
"""
@spec create_scm_connection(map, User.t) :: connection_resp
def create_scm_connection(attrs, %User{} = user) do
%ScmConnection{}
|> ScmConnection.changeset(attrs)
|> allow(user, :edit)
|> when_ok(:insert)
end

@doc """
"""
@spec update_scm_connection(map, binary, User.t) :: connection_resp
def update_scm_connection(attrs, id, %User{} = user) do
get_scm_connection!(id)
|> ScmConnection.changeset(attrs)
|> allow(user, :edit)
|> when_ok(:update)
end

@doc """
"""
@spec delete_scm_connection(binary, User.t) :: connection_resp
def delete_scm_connection(id, %User{} = user) do
get_scm_connection!(id)
|> allow(user, :edit)
|> when_ok(:delete)
end

@doc """
"""
@spec create_scm_webhook(map, User.t) :: webhook_resp
def create_scm_webhook(attrs, %User{} = user) do
%ScmWebhook{}
|> ScmWebhook.changeset(attrs)
|> allow(user, :edit)
|> when_ok(:insert)
end

@doc """
"""
@spec update_scm_webhook(map, binary, User.t) :: webhook_resp
def update_scm_webhook(attrs, id, %User{} = user) do
get_scm_webhook!(id)
|> ScmWebhook.changeset(attrs)
|> allow(user, :edit)
|> when_ok(:update)
end

@doc """
"""
@spec delete_scm_webhook(binary, User.t) :: webhook_resp
def delete_scm_webhook(id, %User{} = user) do
get_scm_webhook!(id)
|> allow(user, :edit)
|> when_ok(:delete)
end

@doc """
"""
@spec create_pr_automation(map, User.t) :: automation_resp
def create_pr_automation(attrs, %User{} = user) do
%PrAutomation{}
|> PrAutomation.changeset(attrs)
|> allow(user, :edit)
|> when_ok(:insert)
end

@doc """
"""
@spec update_pr_automation(map, binary, User.t) :: automation_resp
def update_pr_automation(attrs, id, %User{} = user) do
get_pr_automation!(id)
|> PrAutomation.changeset(attrs)
|> allow(user, :edit)
|> when_ok(:update)
end

@doc """
"""
@spec delete_pr_automation(binary, User.t) :: automation_resp
def delete_pr_automation(id, %User{} = user) do
get_pr_automation!(id)
|> allow(user, :edit)
|> when_ok(:delete)
end

@doc """
Fetches all helm repos registered in this cluster so far
"""
Expand Down
23 changes: 23 additions & 0 deletions lib/console/deployments/pr/config.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule Console.Deployments.Pr.Config do
alias Console.Schema.PrAutomation

@doc """
Generate onfig for executing a pr template
"""
@spec config(PrAutomation.t, map) :: {:ok, binary} | Console.error
def config(%PrAutomation{} = pr, ctx) do
with {:ok, f} <- Briefly.create(),
{:ok, doc} <- Ymlr.document(structure(pr, ctx)),
:ok <- File.write(f, String.trim_leading(doc, "---\n")),
do: {:ok, f}
end

defp structure(pr, ctx) do
%{
apiVersion: "pr.plural.sh/v1alpha1",
kind: "PrTemplate",
spec: Console.mapify(pr.spec) |> Map.put(:message, pr.message),
context: ctx
}
end
end
29 changes: 29 additions & 0 deletions lib/console/deployments/pr/dispatcher.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule Console.Deployments.Pr.Dispatcher do
import Console.Deployments.Pr.Git
alias Console.Repo
alias Console.Deployments.Pr.Config
alias Console.Commands.Plural
alias Console.Deployments.Pr.Impl.{Github, Gitlab}
alias Console.Schema.{PrAutomation, ScmConnection}

@type pr_resp :: {:ok, binary} | Console.error

@callback create(pr :: PrAutomation.t, identifier :: binary, branch :: binary, context :: map) :: pr_resp

@doc """
Fully creates a pr against the working dispatcher implementation
"""
@spec create(PrAutomation.t, binary, binary, map) :: pr_resp
def create(%PrAutomation{} = pr, identifier, branch, ctx) do
%{scm_connection: conn} = pr = Repo.preload(pr, [:scm_connection])
impl = dispatcher(conn)
with {:ok, conn} <- setup(conn, identifier, branch),
{:ok, f} <- Config.config(pr, ctx),
{:ok, _} <- Plural.template(f),
{:ok, _} <- push(conn, branch),
do: impl.create(pr, identifier, branch, ctx)
end

defp dispatcher(%ScmConnection{type: :github}), do: Github
defp dispatcher(%ScmConnection{type: :gitlab}), do: Gitlab
end
46 changes: 46 additions & 0 deletions lib/console/deployments/pr/git.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
defmodule Console.Deployments.Pr.Git do
alias Console.Schema.ScmConnection

@type git_resp :: {:ok, binary} | Console.error

@spec setup(ScmConnection.t, binary, binary) :: {:ok, ScmConnection.t} | Console.error
def setup(%ScmConnection{} = conn, id, branch) do
with {:ok, dir} <- Briefly.create(directory: true),
conn = %{conn | dir: dir},
{:ok, _} <- git(conn, "clone", [url(conn, id), dir]),
{:ok, _} <- git(conn, "checkout", ["-b", branch]),
do: {:ok, conn}
end

@spec commit(ScmConnection.t, binary) :: git_resp
def commit(%ScmConnection{} = conn, msg), do: git(conn, "commit", ["-m", msg])

@spec push(ScmConnection.t, binary) :: git_resp
def push(%ScmConnection{} = conn, branch), do: git(conn, "push", ["--set-upstream", "origin", branch])

defp git(%ScmConnection{} = conn, cmd, args) when is_list(args) do
case System.cmd("git", [cmd | args], opts(conn)) do
{out, 0} -> {:ok, out}
{out, _} -> {:error, out}
end
end

defp url(%ScmConnection{username: nil} = conn, id), do: url(%{conn | username: "apikey"}, id)
defp url(%ScmConnection{username: username} = conn, id) do
base = url(conn)
uri = URI.parse("#{base}/#{id}.git")
URI.to_string(%{uri | userinfo: username})
end

defp url(%ScmConnection{base_url: base}) when is_binary(base), do: base
defp url(%ScmConnection{type: :github}), do: "https://github.com/"
defp url(%ScmConnection{type: :gitlab}), do: "https://gitlab.com/"

defp opts(%ScmConnection{dir: dir} = conn), do: [env: env(conn), cd: dir, stderr_to_stdout: true]

defp env(%ScmConnection{token: password}) when is_binary(password),
do: [{"GIT_ACCESS_TOKEN", password}, {"GIT_ASKPASS", git_askpass()}]
defp env(_), do: []

defp git_askpass(), do: Console.conf(:git_askpass)
end
5 changes: 5 additions & 0 deletions lib/console/deployments/pr/impl/github.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Console.Deployments.Pr.Impl.Github do
@behaviour Console.Deployments.Pr.Dispatcher

def create(_, _, _, _), do: {:ok, ""}
end
5 changes: 5 additions & 0 deletions lib/console/deployments/pr/impl/gitlab.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Console.Deployments.Pr.Impl.Gitlab do
@behaviour Console.Deployments.Pr.Dispatcher

def create(_, _, _, _), do: {:ok, ""}
end
5 changes: 5 additions & 0 deletions lib/console/deployments/pr/impl/pass.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Console.Deployments.Pr.Impl.Pass do
@behaviour Console.Deployments.Pr.Dispatcher

def create(_, _, _, _), do: {:ok, ""}
end
6 changes: 3 additions & 3 deletions lib/console/graphql/deployments/cluster.ex
Original file line number Diff line number Diff line change
Expand Up @@ -475,9 +475,9 @@ defmodule Console.GraphQl.Deployments.Cluster do
@desc "a relay connection of all clusters visible to the current user"
connection field :clusters, node_type: :cluster do
middleware Authenticated
arg :q, :string
arg :health, :boolean
arg :tag, :tag_input
arg :q, :string
arg :healthy, :boolean
arg :tag, :tag_input

resolve &Deployments.list_clusters/2
end
Expand Down
Loading

0 comments on commit e0e5bd5

Please sign in to comment.