diff --git a/chart/templates/migrations/pre-upgrade-3-20-0.yaml b/chart/templates/migrations/pre-upgrade-3-20-0.yaml index b75c2b9..25d6ba6 100644 --- a/chart/templates/migrations/pre-upgrade-3-20-0.yaml +++ b/chart/templates/migrations/pre-upgrade-3-20-0.yaml @@ -14,7 +14,13 @@ {{- $chartLabel := index $existingResource.metadata.labels "helm.sh/chart" }} {{- if $chartLabel }} {{- $prevVersion := trimPrefix "x509-certificate-exporter-" $chartLabel }} - {{- if semverCompare "<3.20.0" $prevVersion }} + {{/* Only fire when both the source release is pre-3.20.0 AND the + target release is still in the v3 line. Upgrades that cross + into v4+ are handled by pre-upgrade-4-0-0, which subsumes + the same Deployment/DaemonSet recreation alongside its own + Service migration. Without this gate the two hooks would run + in parallel and double-delete the same resources. */}} + {{- if and (semverCompare "<3.20.0" $prevVersion) (semverCompare "<4.0.0" .Chart.Version) }} {{- $needsMigration = true }} {{- end }} {{- end }} diff --git a/chart/templates/migrations/pre-upgrade-4-0-0.yaml b/chart/templates/migrations/pre-upgrade-4-0-0.yaml index 678c530..90e9f11 100644 --- a/chart/templates/migrations/pre-upgrade-4-0-0.yaml +++ b/chart/templates/migrations/pre-upgrade-4-0-0.yaml @@ -1,25 +1,55 @@ {{/* Pre-upgrade hook for v3 → v4 chart upgrades. -Why: v4 flipped `service.headless` to `true` by default, which sets -`clusterIP: None` on the Service. Kubernetes refuses to mutate a -Service's clusterIP in place — `spec.clusterIPs[0]: may not change once -set` — so an `helm upgrade` from a v3 release with an assigned ClusterIP -to a v4 default install fails before any other reconciliation. +Two unrelated immutable-field changes force a delete-and-recreate cycle: -Strategy: if the existing Service carries a `helm.sh/chart` label -matching a chart version <4.0.0, AND the new release will render a -Service (`.Values.service.create` is true), delete it pre-upgrade so -helm can recreate it from the new template — headless or otherwise, -whichever the new values dictate. + 1. Service.spec.clusterIP — v4 flipped `service.headless` to `true` by + default, which sets `clusterIP: None`. Kubernetes refuses to mutate + a Service's clusterIP in place (`spec.clusterIPs[0]: may not + change once set`). + 2. Deployment.spec.selector and DaemonSet.spec.selector — v4 reworked + the per-component label set, which propagates to the immutable + selector. `helm upgrade` reports `field is immutable` on these. + +Strategy: if any v4-managed resource (Service, secrets-exporter +Deployment, or any host-paths-exporter DaemonSet) still carries a +`helm.sh/chart` label from a chart version <4.0.0, delete all of them +pre-upgrade so helm can recreate them with the new schema. The pods +they back are stateless: a few seconds of unavailability during the +upgrade is the only side-effect. + +This hook also subsumes the responsibility of pre-upgrade-3-20-0 when +the upgrade target is v4+ — see the version-gate inside that hook. */}} {{- $needsMigration := false }} -{{- if and .Release.IsUpgrade .Values.service.create }} +{{- if .Release.IsUpgrade }} {{- $ns := include "x509-certificate-exporter.namespace" . }} - {{- $svcName := include "x509-certificate-exporter.fullname" . }} - {{- $existingSvc := lookup "v1" "Service" $ns $svcName }} - {{- if $existingSvc }} - {{- $chartLabel := index $existingSvc.metadata.labels "helm.sh/chart" }} + + {{/* Find any chart-managed resource still on chart <4.0.0. Probe the + Service first (always present unless the user disabled it), then + fall back to the Deployment, then any DaemonSet — covers users + who run with service.create=false. */}} + {{- $existingResource := dict }} + {{- if .Values.service.create }} + {{- $svc := lookup "v1" "Service" $ns (include "x509-certificate-exporter.fullname" .) }} + {{- if $svc }}{{ $existingResource = $svc }}{{ end }} + {{- end }} + {{- if not $existingResource }} + {{- $dep := lookup "apps/v1" "Deployment" $ns (include "x509-certificate-exporter.secretsExporterName" .) }} + {{- if $dep }}{{ $existingResource = $dep }}{{ end }} + {{- end }} + {{- if not $existingResource }} + {{- range $name, $_ := .Values.hostPathsExporter.daemonSets }} + {{- if not $existingResource }} + {{- $dsName := printf "%s-%s" (include "x509-certificate-exporter.fullname" $) $name }} + {{- $ds := lookup "apps/v1" "DaemonSet" $ns $dsName }} + {{- if $ds }}{{ $existingResource = $ds }}{{ end }} + {{- end }} + {{- end }} + {{- end }} + + {{- if $existingResource }} + {{- $chartLabel := index $existingResource.metadata.labels "helm.sh/chart" }} {{- if $chartLabel }} {{- $prevVersion := trimPrefix "x509-certificate-exporter-" $chartLabel }} {{- if semverCompare "<4.0.0" $prevVersion }} @@ -57,6 +87,9 @@ rules: - apiGroups: [""] resources: ["services"] verbs: ["get", "list", "delete"] +- apiGroups: ["apps"] + resources: ["deployments", "daemonsets"] + verbs: ["get", "list", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding @@ -125,5 +158,11 @@ spec: - --ignore-not-found=true - -n - {{ include "x509-certificate-exporter.namespace" . }} + {{- if .Values.service.create }} - service/{{ include "x509-certificate-exporter.fullname" . }} + {{- end }} + - deployment/{{ include "x509-certificate-exporter.secretsExporterName" . }} + {{- range $name, $_ := .Values.hostPathsExporter.daemonSets }} + - daemonset/{{ printf "%s-%s" (include "x509-certificate-exporter.fullname" $) $name }} + {{- end }} {{- end }}