diff --git a/assets/src/generated/graphql.ts b/assets/src/generated/graphql.ts index cd63e738ba..b713cbd620 100644 --- a/assets/src/generated/graphql.ts +++ b/assets/src/generated/graphql.ts @@ -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; } @@ -3919,6 +3925,7 @@ export type RootQueryType = { pagedClusterGates?: Maybe; pagedClusterServices?: Maybe; pipeline?: Maybe; + pipelineContext?: Maybe; pipelineGate?: Maybe; pipelines?: Maybe; pluralCluster?: Maybe; @@ -4342,6 +4349,11 @@ export type RootQueryTypePipelineArgs = { }; +export type RootQueryTypePipelineContextArgs = { + id: Scalars['ID']['input']; +}; + + export type RootQueryTypePipelineGateArgs = { id: Scalars['ID']['input']; }; diff --git a/lib/console/deployments/pipelines.ex b/lib/console/deployments/pipelines.ex index 1a9c46b472..84b941fa8e 100644 --- a/lib/console/deployments/pipelines.ex +++ b/lib/console/deployments/pipelines.ex @@ -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 @@ -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 """ @@ -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) diff --git a/lib/console/deployments/policies/rbac.ex b/lib/console/deployments/policies/rbac.ex index c3b1afbaae..14757eae3d 100644 --- a/lib/console/deployments/policies/rbac.ex +++ b/lib/console/deployments/policies/rbac.ex @@ -12,6 +12,7 @@ defmodule Console.Deployments.Policies.Rbac do ProviderCredential, Pipeline, PipelineGate, + PipelineContext, AgentMigration, RuntimeService, PrAutomation @@ -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), @@ -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), diff --git a/lib/console/graphql/deployments/pipeline.ex b/lib/console/graphql/deployments/pipeline.ex index 3d0eba40c8..4c505df30e 100644 --- a/lib/console/graphql/deployments/pipeline.ex +++ b/lib/console/graphql/deployments/pipeline.ex @@ -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 diff --git a/lib/console/graphql/resolvers/deployments/pipeline.ex b/lib/console/graphql/resolvers/deployments/pipeline.ex index 5050a39906..47d457817a 100644 --- a/lib/console/graphql/resolvers/deployments/pipeline.ex +++ b/lib/console/graphql/resolvers/deployments/pipeline.ex @@ -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) diff --git a/schema/schema.graphql b/schema/schema.graphql index 0b215117ef..857caeee72 100644 --- a/schema/schema.graphql +++ b/schema/schema.graphql @@ -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 diff --git a/test/console/graphql/queries/deployments/pipeline_queries_test.exs b/test/console/graphql/queries/deployments/pipeline_queries_test.exs index f2e8386f66..bc6730d924 100644 --- a/test/console/graphql/queries/deployments/pipeline_queries_test.exs +++ b/test/console/graphql/queries/deployments/pipeline_queries_test.exs @@ -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)