Skip to content

Commit

Permalink
Control promotions for pr pipelines (#685)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeljguarino authored Feb 14, 2024
1 parent 4c228c2 commit 15d6787
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 0 deletions.
12 changes: 12 additions & 0 deletions assets/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ export type Scalars = {
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
/**
* The `DateTime` scalar type represents a date and time in the UTC
* timezone. The DateTime appears in a JSON response as an ISO8601 formatted
* string, including UTC timezone ("Z"). The parsed date and time string will
* be converted to UTC if there is an offset.
*/
DateTime: { input: string; output: string; }
Json: { input: any; output: any; }
Long: { input: any; output: any; }
Expand Down Expand Up @@ -3919,6 +3925,7 @@ export type RootQueryType = {
pagedClusterGates?: Maybe<PipelineGateConnection>;
pagedClusterServices?: Maybe<ServiceDeploymentConnection>;
pipeline?: Maybe<Pipeline>;
pipelineContext?: Maybe<PipelineContext>;
pipelineGate?: Maybe<PipelineGate>;
pipelines?: Maybe<PipelineConnection>;
pluralCluster?: Maybe<PluralCluster>;
Expand Down Expand Up @@ -4342,6 +4349,11 @@ export type RootQueryTypePipelineArgs = {
};


export type RootQueryTypePipelineContextArgs = {
id: Scalars['ID']['input'];
};


export type RootQueryTypePipelineGateArgs = {
id: Scalars['ID']['input'];
};
Expand Down
11 changes: 11 additions & 0 deletions lib/console/deployments/pipelines.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ defmodule Console.Deployments.Pipelines do

def get_gate!(id), do: Repo.get!(PipelineGate, id)

def get_context!(id), do: Repo.get!(PipelineContext, id)

def get_pipeline_by_name(name), do: Repo.get_by(Pipeline, name: name)

def gate_job(%PipelineGate{status: %{job_ref: %{namespace: ns, name: name}}} = gate) do
Expand Down Expand Up @@ -162,6 +164,14 @@ defmodule Console.Deployments.Pipelines do
do: Timex.after?(at, dt)
def promoted?(_, _), do: false

@doc """
Validate if we've already done the promotion for this stage for pr pipelines
"""
@spec pr_promoted?(PipelineEdge.t, PipelinePromotion.t) :: boolean
def pr_promoted?(%PipelineEdge{to: %PipelineStage{context_id: id}}, %PipelinePromotion{context_id: id})
when is_binary(id), do: true
def pr_promoted?(_, _), do: false

@doc """
Fetches all eligible gates for a cluster
"""
Expand Down Expand Up @@ -292,6 +302,7 @@ defmodule Console.Deployments.Pipelines do
|> add_operation(:resolve, fn %{promo: promotion, edges: edges} ->
Enum.filter(edges, &open?/1)
|> Enum.filter(& !promoted?(&1, promotion.revised_at))
|> Enum.filter(& !pr_promoted?(&1, promotion))
|> Enum.reduce(start_transaction(), &promote_edge(&2, promotion, &1))
|> execute()
end)
Expand Down
4 changes: 4 additions & 0 deletions lib/console/deployments/policies/rbac.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule Console.Deployments.Policies.Rbac do
ProviderCredential,
Pipeline,
PipelineGate,
PipelineContext,
AgentMigration,
RuntimeService,
PrAutomation
Expand All @@ -36,6 +37,8 @@ defmodule Console.Deployments.Policies.Rbac do
end
end

def evaluate(%PipelineContext{} = ctx, %User{} = user, action),
do: recurse(ctx, user, action, & &1.pipeline)
def evaluate(%PipelineGate{} = gate, %User{} = user, action),
do: recurse(gate, user, action, & &1.edge.pipeline)
def evaluate(%Pipeline{} = pipe, %User{} = user, action),
Expand All @@ -62,6 +65,7 @@ defmodule Console.Deployments.Policies.Rbac do
do: recurse(settings, user, action)
def evaluate(_, _, _), do: false

def preload(%PipelineContext{} = ctx), do: Repo.preload(ctx, [pipeline: [:read_bindings, :write_bindings]])
def preload(%PipelineGate{} = gate), do: Repo.preload(gate, [edge: [pipeline: [:read_bindings, :write_bindings]]])
def preload(%Pipeline{} = pipe), do: Repo.preload(pipe, [:read_bindings, :write_bindings])
def preload(%Service{} = service),
Expand Down
7 changes: 7 additions & 0 deletions lib/console/graphql/deployments/pipeline.ex
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,13 @@ defmodule Console.GraphQl.Deployments.Pipeline do

resolve &Deployments.resolve_gate/2
end

field :pipeline_context, :pipeline_context do
middleware Authenticated
arg :id, non_null(:id)

resolve &Deployments.resolve_pipeline_context/2
end
end

object :pipeline_mutations do
Expand Down
5 changes: 5 additions & 0 deletions lib/console/graphql/resolvers/deployments/pipeline.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ defmodule Console.GraphQl.Resolvers.Deployments.Pipeline do
|> allow(user, :read)
end

def resolve_pipeline_context(%{id: id}, %{context: %{current_user: user}}) do
Pipelines.get_context!(id)
|> allow(user, :read)
end

def upsert_pipeline(%{name: name, attributes: attrs}, %{context: %{current_user: user}}),
do: Pipelines.upsert(attrs, name, user)

Expand Down
2 changes: 2 additions & 0 deletions schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ type RootQueryType {

pipelineGate(id: ID!): PipelineGate

pipelineContext(id: ID!): PipelineContext

clusterBackup(id: ID, clusterId: ID, namespace: String, name: String): ClusterBackup

clusterBackups(after: String, first: Int, before: String, last: Int, clusterId: ID!): ClusterBackupConnection
Expand Down
29 changes: 29 additions & 0 deletions test/console/graphql/queries/deployments/pipeline_queries_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,35 @@ defmodule Console.GraphQl.Deployments.PipelineQueriesTest do
end
end

describe "pipelineContext" do
test "it can fetch a pipeline gate by id" do
user = insert(:user)
%{group: group} = insert(:group_member, user: user)
pipe = insert(:pipeline, read_bindings: [%{group_id: group.id}])
ctx = insert(:pipeline_context, pipeline: pipe)

{:ok, %{data: %{"pipelineContext" => found}}} = run_query("""
query Context($id: ID!) {
pipelineContext(id: $id) { id }
}
""", %{"id" => ctx.id}, %{current_user: Console.Services.Rbac.preload(user)})

assert found["id"] == ctx.id
end

test "users w/o permission cannot fetch" do
user = insert(:user)
pipe = insert(:pipeline)
ctx = insert(:pipeline_context, pipeline: pipe)

{:ok, %{errors: [_ | _]}} = run_query("""
query Context($id: ID!) {
pipelineContext(id: $id) { id }
}
""", %{"id" => ctx.id}, %{current_user: Console.Services.Rbac.preload(user)})
end
end

describe "clusterGates" do
test "it will fetch the gates configured for a cluster" do
cluster = insert(:cluster)
Expand Down

0 comments on commit 15d6787

Please sign in to comment.