From 9355a20db66cadbcff3cf892613f3536fbcfae81 Mon Sep 17 00:00:00 2001 From: michaeljguarino Date: Tue, 17 Sep 2024 18:21:07 -0400 Subject: [PATCH] don't drain protected services on cluster delete --- AGENT_VERSION | 2 +- assets/src/generated/graphql-plural.ts | 1 + assets/src/generated/graphql.ts | 2 ++ .../deployments.plural.sh_globalservices.yaml | 4 ++++ .../deployments.plural.sh_managednamespaces.yaml | 4 ++++ go/client/models_gen.go | 2 ++ .../api/v1alpha1/managednamespace_types.go | 5 +++++ .../api/v1alpha1/zz_generated.deepcopy.go | 5 +++++ .../deployments.plural.sh_globalservices.yaml | 4 ++++ .../deployments.plural.sh_managednamespaces.yaml | 4 ++++ go/controller/docs/api.md | 1 + go/controller/internal/controller/common.go | 1 + lib/console/deployments/global.ex | 11 ++++++++--- lib/console/graphql/deployments/global.ex | 1 + lib/console/graphql/deployments/stack.ex | 16 ++++++++++++++-- lib/console/mailer.ex | 11 +++++++---- lib/console/schema/service.ex | 2 +- lib/console/schema/service_template.ex | 1 + lib/console_web/views/email_view.ex | 2 +- .../deployments.plural.sh_globalservices.yaml | 4 ++++ .../deployments.plural.sh_managednamespaces.yaml | 4 ++++ priv/notifications/secret.txt.eex | 2 +- ...240917220528_add_protect_service_template.exs | 9 +++++++++ schema/schema.graphql | 3 +++ test/console/deployments/global_test.exs | 7 +++++-- 25 files changed, 93 insertions(+), 15 deletions(-) create mode 100644 priv/repo/migrations/20240917220528_add_protect_service_template.exs diff --git a/AGENT_VERSION b/AGENT_VERSION index 1e8a26035d..7df7e1f7ce 100644 --- a/AGENT_VERSION +++ b/AGENT_VERSION @@ -1 +1 @@ -v0.4.45 \ No newline at end of file +v0.4.46 \ No newline at end of file diff --git a/assets/src/generated/graphql-plural.ts b/assets/src/generated/graphql-plural.ts index 9373f284de..2cda37dd0d 100644 --- a/assets/src/generated/graphql-plural.ts +++ b/assets/src/generated/graphql-plural.ts @@ -3397,6 +3397,7 @@ export type RootMutationTypeLinkPublisherArgs = { export type RootMutationTypeLoginArgs = { + captcha?: InputMaybe; deviceToken?: InputMaybe; email: Scalars['String']['input']; password: Scalars['String']['input']; diff --git a/assets/src/generated/graphql.ts b/assets/src/generated/graphql.ts index db169f37e0..772c771dee 100644 --- a/assets/src/generated/graphql.ts +++ b/assets/src/generated/graphql.ts @@ -7492,6 +7492,8 @@ export type ServiceTemplateAttributes = { name?: InputMaybe; /** the namespace for this service (optional for managed namespaces) */ namespace?: InputMaybe; + /** whether to protect this templated service from deletion */ + protect?: InputMaybe; /** the id of a repository to source manifests for this service */ repositoryId?: InputMaybe; /** attributes to configure sync settings for this service */ diff --git a/charts/controller/crds/deployments.plural.sh_globalservices.yaml b/charts/controller/crds/deployments.plural.sh_globalservices.yaml index 2bb1e51be3..606187f45e 100644 --- a/charts/controller/crds/deployments.plural.sh_globalservices.yaml +++ b/charts/controller/crds/deployments.plural.sh_globalservices.yaml @@ -449,6 +449,10 @@ spec: description: Namespace the namespace for this service (optional for managed namespaces) type: string + protect: + description: Whether to protect this service from deletion. Protected + services are also not drained on cluster deletion. + type: boolean repositoryRef: description: |- ObjectReference contains enough information to let you inspect or modify the referred object. diff --git a/charts/controller/crds/deployments.plural.sh_managednamespaces.yaml b/charts/controller/crds/deployments.plural.sh_managednamespaces.yaml index 2512ea9630..4ca8c07871 100644 --- a/charts/controller/crds/deployments.plural.sh_managednamespaces.yaml +++ b/charts/controller/crds/deployments.plural.sh_managednamespaces.yaml @@ -366,6 +366,10 @@ spec: description: Namespace the namespace for this service (optional for managed namespaces) type: string + protect: + description: Whether to protect this service from deletion. Protected + services are also not drained on cluster deletion. + type: boolean repositoryRef: description: |- ObjectReference contains enough information to let you inspect or modify the referred object. diff --git a/go/client/models_gen.go b/go/client/models_gen.go index b033e63078..678e2c3a28 100644 --- a/go/client/models_gen.go +++ b/go/client/models_gen.go @@ -4409,6 +4409,8 @@ type ServiceTemplateAttributes struct { // the namespace for this service (optional for managed namespaces) Namespace *string `json:"namespace,omitempty"` Templated *bool `json:"templated,omitempty"` + // whether to protect this templated service from deletion + Protect *bool `json:"protect,omitempty"` // the id of a repository to source manifests for this service RepositoryID *string `json:"repositoryId,omitempty"` // a list of context ids to add to this service diff --git a/go/controller/api/v1alpha1/managednamespace_types.go b/go/controller/api/v1alpha1/managednamespace_types.go index 0dac56792e..a922ade16f 100644 --- a/go/controller/api/v1alpha1/managednamespace_types.go +++ b/go/controller/api/v1alpha1/managednamespace_types.go @@ -78,6 +78,11 @@ type ServiceTemplate struct { Templated *bool `json:"templated,omitempty"` // +kubebuilder:validation:Optional RepositoryRef *corev1.ObjectReference `json:"repositoryRef"` + + // Whether to protect this service from deletion. Protected services are also not drained on cluster deletion. + // +kubebuilder:validation:Optional + Protect *bool `json:"protect,omitempty"` + // a list of context ids to add to this service // +kubebuilder:validation:Optional Contexts []string `json:"contexts,omitempty"` diff --git a/go/controller/api/v1alpha1/zz_generated.deepcopy.go b/go/controller/api/v1alpha1/zz_generated.deepcopy.go index 25d5552359..93647decb6 100644 --- a/go/controller/api/v1alpha1/zz_generated.deepcopy.go +++ b/go/controller/api/v1alpha1/zz_generated.deepcopy.go @@ -4250,6 +4250,11 @@ func (in *ServiceTemplate) DeepCopyInto(out *ServiceTemplate) { *out = new(v1.ObjectReference) **out = **in } + if in.Protect != nil { + in, out := &in.Protect, &out.Protect + *out = new(bool) + **out = **in + } if in.Contexts != nil { in, out := &in.Contexts, &out.Contexts *out = make([]string, len(*in)) diff --git a/go/controller/config/crd/bases/deployments.plural.sh_globalservices.yaml b/go/controller/config/crd/bases/deployments.plural.sh_globalservices.yaml index 2bb1e51be3..606187f45e 100644 --- a/go/controller/config/crd/bases/deployments.plural.sh_globalservices.yaml +++ b/go/controller/config/crd/bases/deployments.plural.sh_globalservices.yaml @@ -449,6 +449,10 @@ spec: description: Namespace the namespace for this service (optional for managed namespaces) type: string + protect: + description: Whether to protect this service from deletion. Protected + services are also not drained on cluster deletion. + type: boolean repositoryRef: description: |- ObjectReference contains enough information to let you inspect or modify the referred object. diff --git a/go/controller/config/crd/bases/deployments.plural.sh_managednamespaces.yaml b/go/controller/config/crd/bases/deployments.plural.sh_managednamespaces.yaml index 2512ea9630..4ca8c07871 100644 --- a/go/controller/config/crd/bases/deployments.plural.sh_managednamespaces.yaml +++ b/go/controller/config/crd/bases/deployments.plural.sh_managednamespaces.yaml @@ -366,6 +366,10 @@ spec: description: Namespace the namespace for this service (optional for managed namespaces) type: string + protect: + description: Whether to protect this service from deletion. Protected + services are also not drained on cluster deletion. + type: boolean repositoryRef: description: |- ObjectReference contains enough information to let you inspect or modify the referred object. diff --git a/go/controller/docs/api.md b/go/controller/docs/api.md index 55d16c1dbe..49a98507ad 100644 --- a/go/controller/docs/api.md +++ b/go/controller/docs/api.md @@ -2189,6 +2189,7 @@ _Appears in:_ | `namespace` _string_ | Namespace the namespace for this service (optional for managed namespaces) | | Optional: {}
| | `templated` _boolean_ | | | Optional: {}
| | `repositoryRef` _[ObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectreference-v1-core)_ | | | Optional: {}
| +| `protect` _boolean_ | Whether to protect this service from deletion. Protected services are also not drained on cluster deletion. | | Optional: {}
| | `contexts` _string array_ | a list of context ids to add to this service | | Optional: {}
| | `git` _[GitRef](#gitref)_ | Git settings to configure git for a service | | Optional: {}
| | `helm` _[ServiceHelm](#servicehelm)_ | Helm settings to configure helm for a service | | Optional: {}
| diff --git a/go/controller/internal/controller/common.go b/go/controller/internal/controller/common.go index 2dd235dfe9..83b2e920df 100644 --- a/go/controller/internal/controller/common.go +++ b/go/controller/internal/controller/common.go @@ -105,6 +105,7 @@ func genServiceTemplate(ctx context.Context, c runtimeclient.Client, namespace s Namespace: srv.Namespace, Templated: lo.ToPtr(true), RepositoryID: repositoryID, + Protect: srv.Protect, SyncConfig: syncConf, } if len(srv.Dependencies) > 0 { diff --git a/lib/console/deployments/global.ex b/lib/console/deployments/global.ex index 4d3dfb9fb6..6a3f7df7fa 100644 --- a/lib/console/deployments/global.ex +++ b/lib/console/deployments/global.ex @@ -465,6 +465,7 @@ defmodule Console.Deployments.Global do owner_id: owner_id, configuration: Enum.map(Map.merge(dest_secrets, source_secrets), fn {k, v} -> %{name: k, value: v} end), repository_id: source.repository_id, + protect: source.protect, sync_config: clean(source.sync_config), git: clean(source.git), helm: clean(source.helm), @@ -510,8 +511,8 @@ defmodule Console.Deployments.Global do dest = Repo.preload(dest, [:dependencies]) with {:ok, source_secrets} <- configuration(spec), {:ok, dest_secrets} <- Services.configuration(dest), - do: (spec.repository_id != dest.repository_id || spec.templated != dest.templated || - specs_different?(spec, dest) || !contexts_equal?(spec, dest) || !deps_equal?(spec, dest) || + do: (fields_different?(spec, dest) || specs_different?(spec, dest) || + !contexts_equal?(spec, dest) || !deps_equal?(spec, dest) || missing_source?(source_secrets, dest_secrets)) end @@ -526,7 +527,7 @@ defmodule Console.Deployments.Global do def diff?(_, _), do: false defp diff?(%Service{} = s, %Service{} = d, source, dest) do - missing_source?(source, dest) || !deps_equal?(s, d) || specs_different?(s, d) || s.repository_id != d.repository_id || s.namespace != d.namespace || s.templated != d.templated + missing_source?(source, dest) || !deps_equal?(s, d) || specs_different?(s, d) || fields_different?(s, d) end defp ensure_revision(%ServiceTemplate{} = template, config) do @@ -572,6 +573,10 @@ defmodule Console.Deployments.Global do |> MapSet.equal?(MapSet.new(deps || [], & &1.name)) end + defp fields_different?(svc1, svc2) do + Enum.any?(~w(repository_id namespace templated protect)a, & Map.get(svc1, &1) != Map.get(svc2, &1)) + end + @spec svc_deps([ServiceDependency.t], [ServiceDependency.t]) :: [ServiceDependency.t] defp svc_deps(dependencies, existing) do existing_by_name = Map.new(existing, & {&1.name, &1}) diff --git a/lib/console/graphql/deployments/global.ex b/lib/console/graphql/deployments/global.ex index 03a78764a9..e2f2ca5d49 100644 --- a/lib/console/graphql/deployments/global.ex +++ b/lib/console/graphql/deployments/global.ex @@ -35,6 +35,7 @@ defmodule Console.GraphQl.Deployments.Global do field :name, :string, description: "the name for this service (optional for managed namespaces)" field :namespace, :string, description: "the namespace for this service (optional for managed namespaces)" field :templated, :boolean + field :protect, :boolean, description: "whether to protect this templated service from deletion" field :repository_id, :id, description: "the id of a repository to source manifests for this service" field :contexts, list_of(:id), description: "a list of context ids to add to this service" field :configuration, list_of(:config_attributes), description: "a list of secure configuration that will be added to any services created by this template" diff --git a/lib/console/graphql/deployments/stack.ex b/lib/console/graphql/deployments/stack.ex index 16722d66f4..076fca6509 100644 --- a/lib/console/graphql/deployments/stack.ex +++ b/lib/console/graphql/deployments/stack.ex @@ -138,7 +138,13 @@ defmodule Console.GraphQl.Deployments.Stack do field :paused, :boolean, description: "whether the stack is actively tracking changes in git" field :status, non_null(:stack_status), description: "The status of the last run of the stack" field :job_spec, :job_gate_spec, description: "optional k8s job configuration for the job that will apply this stack" - field :configuration, non_null(:stack_configuration), description: "version/image config for the tool you're using" + + @desc "version/image config for the tool you're using" + field :configuration, non_null(:stack_configuration), resolve: fn + %{configuration: %{} = conf}, _, _ -> {:ok, conf} + _, _, _ -> {:ok, %{hooks: []}} + end + field :approval, :boolean, description: "whether to require approval" field :deleted_at, :datetime, description: "whether this stack was previously deleted and is pending cleanup" field :cancellation_reason, :string, description: "why this run was cancelled" @@ -229,7 +235,13 @@ defmodule Console.GraphQl.Deployments.Stack do field :job_spec, :job_gate_spec, description: "optional k8s job configuration for the job that will apply this stack", resolve: &Deployments.job_spec/3 - field :configuration, non_null(:stack_configuration), description: "version/image config for the tool you're using" + + @desc "version/image config for the tool you're using" + field :configuration, non_null(:stack_configuration), resolve: fn + %{configuration: %{} = conf}, _, _ -> {:ok, conf} + _, _, _ -> {:ok, %{hooks: []}} + end + field :approval, :boolean, description: "whether to require approval" field :message, :string, description: "the commit message" field :approved_at, :datetime, description: "when this run was approved" diff --git a/lib/console/mailer.ex b/lib/console/mailer.ex index 03d0535204..2190ff78ad 100644 --- a/lib/console/mailer.ex +++ b/lib/console/mailer.ex @@ -11,7 +11,7 @@ defmodule Console.Mailer do :ok else {:error, err} -> - Logger.info "not delivering email, reason: #{err}" + Logger.info "not delivering email, reason: #{inspect(err)}" {:error, err} end end @@ -24,11 +24,14 @@ defmodule Console.Mailer do end end - defp smtp_config() do + def smtp_config() do case Settings.cached() do - %DeploymentSettings{smtp: %DeploymentSettings.SMTP{} = smtp} -> - {:ok, Map.take(smtp, DeploymentSettings.smtp_config())} + %DeploymentSettings{smtp: %DeploymentSettings.SMTP{} = config} -> {:ok, smtp(config)} _ -> {:error, "smtp not configured"} end end + + defp smtp(%DeploymentSettings.SMTP{user: u, password: p, server: s, port: port, ssl: ssl}) do + %{username: u, password: p, server: s, port: port, ssl: ssl, auth: :always} + end end diff --git a/lib/console/schema/service.ex b/lib/console/schema/service.ex index 1bf08ef3ee..5c51f345cd 100644 --- a/lib/console/schema/service.ex +++ b/lib/console/schema/service.ex @@ -165,7 +165,7 @@ defmodule Console.Schema.Service do end def drainable(query \\ __MODULE__) do - from(s in query, where: s.name != "deploy-operator") + from(s in query, where: s.name != "deploy-operator" and (is_nil(s.protect) or not s.protect)) end def nonsystem(query \\ __MODULE__) do diff --git a/lib/console/schema/service_template.ex b/lib/console/schema/service_template.ex index e7dee08592..750f47aecc 100644 --- a/lib/console/schema/service_template.ex +++ b/lib/console/schema/service_template.ex @@ -6,6 +6,7 @@ defmodule Console.Schema.ServiceTemplate do field :name, :string field :namespace, :string field :templated, :boolean, default: true + field :protect, :boolean, default: false field :contexts, {:array, :string} field :configuration, {:array, :map}, virtual: true field :ignore_sync, :boolean, virtual: true diff --git a/lib/console_web/views/email_view.ex b/lib/console_web/views/email_view.ex index 7be04cb1ad..17270ef215 100644 --- a/lib/console_web/views/email_view.ex +++ b/lib/console_web/views/email_view.ex @@ -1,7 +1,7 @@ defmodule ConsoleWeb.EmailView do use ConsoleWeb, :view - def url(path), do: "#{Console.conf(:host)}#{path}" + def url(path), do: Console.url(path) def row(assigns \\ %{}, do: block), do: render_template("row.html", assigns, block) diff --git a/plural/helm/console/crds/deployments.plural.sh_globalservices.yaml b/plural/helm/console/crds/deployments.plural.sh_globalservices.yaml index 2bb1e51be3..606187f45e 100644 --- a/plural/helm/console/crds/deployments.plural.sh_globalservices.yaml +++ b/plural/helm/console/crds/deployments.plural.sh_globalservices.yaml @@ -449,6 +449,10 @@ spec: description: Namespace the namespace for this service (optional for managed namespaces) type: string + protect: + description: Whether to protect this service from deletion. Protected + services are also not drained on cluster deletion. + type: boolean repositoryRef: description: |- ObjectReference contains enough information to let you inspect or modify the referred object. diff --git a/plural/helm/console/crds/deployments.plural.sh_managednamespaces.yaml b/plural/helm/console/crds/deployments.plural.sh_managednamespaces.yaml index 2512ea9630..4ca8c07871 100644 --- a/plural/helm/console/crds/deployments.plural.sh_managednamespaces.yaml +++ b/plural/helm/console/crds/deployments.plural.sh_managednamespaces.yaml @@ -366,6 +366,10 @@ spec: description: Namespace the namespace for this service (optional for managed namespaces) type: string + protect: + description: Whether to protect this service from deletion. Protected + services are also not drained on cluster deletion. + type: boolean repositoryRef: description: |- ObjectReference contains enough information to let you inspect or modify the referred object. diff --git a/priv/notifications/secret.txt.eex b/priv/notifications/secret.txt.eex index a690ed7cb4..c62e896052 100644 --- a/priv/notifications/secret.txt.eex +++ b/priv/notifications/secret.txt.eex @@ -1,3 +1,3 @@ <%= @user.name %> shared a secret with you -It can be downloaded at a one-time url here: <%= Console.url("/secrets/share/#{@handle}") %> +It can be downloaded at a one-time url here: <%= Console.url("/secrets/#{@handle}") %> diff --git a/priv/repo/migrations/20240917220528_add_protect_service_template.exs b/priv/repo/migrations/20240917220528_add_protect_service_template.exs new file mode 100644 index 0000000000..fa87770773 --- /dev/null +++ b/priv/repo/migrations/20240917220528_add_protect_service_template.exs @@ -0,0 +1,9 @@ +defmodule Console.Repo.Migrations.AddProtectServiceTemplate do + use Ecto.Migration + + def change do + alter table(:service_templates) do + add :protect, :boolean, default: false + end + end +end diff --git a/schema/schema.graphql b/schema/schema.graphql index e0a7b941ac..f068a22eab 100644 --- a/schema/schema.graphql +++ b/schema/schema.graphql @@ -1808,6 +1808,9 @@ input ServiceTemplateAttributes { templated: Boolean + "whether to protect this templated service from deletion" + protect: Boolean + "the id of a repository to source manifests for this service" repositoryId: ID diff --git a/test/console/deployments/global_test.exs b/test/console/deployments/global_test.exs index c292bbd0c9..9c70c3b506 100644 --- a/test/console/deployments/global_test.exs +++ b/test/console/deployments/global_test.exs @@ -539,14 +539,17 @@ defmodule Console.Deployments.GlobalTest do test "it returns false if targets have all the same relevant config" do svc = insert(:service, helm: %{chart: "test", version: "0.4.0", repository: %{name: "chart", namespace: "infra"}}, - templated: true + templated: true, + protect: false, + namespace: "test" ) template = insert(:service_template, repository: svc.repository, helm: %{chart: "test", version: "0.4.0", repository: %{name: "chart", namespace: "infra"}}, templated: true, - git: svc.git + git: svc.git, + namespace: "test" ) refute Global.diff?(template, Console.Repo.preload(svc, [:contexts]))