diff --git a/docs/configuration.md b/docs/configuration.md index 7c273107..1ce770f1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -116,7 +116,8 @@ helmDefaults: waitForJobs: true # time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300) timeout: 600 - # performs pods restart for the resource if applicable (default false) + # performs pods restart for the resource if applicable (default false). + # Ignored with a warning on Helm 4, which removed --recreate-pods. recreatePods: true # forces resource update through delete/recreate if needed (default false) force: false @@ -254,7 +255,7 @@ releases: # waitRetries: 3 waitForJobs: true timeout: 60 - recreatePods: true + recreatePods: true # ignored with a warning on Helm 4 force: false reuseValues: false # set `false` to uninstall this release on sync. (default true) diff --git a/pkg/state/state.go b/pkg/state/state.go index d100a3b5..1c50065e 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -4296,7 +4296,14 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp flags = append(flags, "--force-conflicts") } - if release.RecreatePods != nil && *release.RecreatePods || release.RecreatePods == nil && st.HelmDefaults.RecreatePods { + recreatePodsEnabled := (release.RecreatePods != nil && *release.RecreatePods) || (release.RecreatePods == nil && st.HelmDefaults.RecreatePods) + + // Helm 4 removed --recreate-pods (helm/helm#31023) and fails with "unknown flag". + // It has no replacement flag; Helm recommends a checksum annotation on the pod template. + switch { + case recreatePodsEnabled && helm.IsHelm4(): + st.logger.Warnf("recreatePods is not supported by Helm 4, ignoring it for release %s", release.Name) + case recreatePodsEnabled: flags = append(flags, "--recreate-pods") } diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 998dcb80..f1fd54fd 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -1,12 +1,14 @@ package state import ( + "bytes" "fmt" "io" "os" "path/filepath" "reflect" "slices" + "strings" "testing" "github.com/Masterminds/semver/v3" @@ -459,10 +461,12 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { wantErr: "force and forceConflicts are mutually exclusive (check both releases[].force/forceConflicts and helmDefaults.force/forceConflicts)", }, { - name: "recreate-pods", + name: "recreate-pods-helm3", defaults: HelmSpec{ - RecreatePods: false, + RecreatePods: false, + CreateNamespace: &disable, }, + version: semver.MustParse("3.10.0"), release: &ReleaseSpec{ Chart: "test/chart", Version: "0.1", @@ -477,10 +481,12 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { }, }, { - name: "recreate-pods-from-default", + name: "recreate-pods-from-default-helm3", defaults: HelmSpec{ - RecreatePods: true, + RecreatePods: true, + CreateNamespace: &disable, }, + version: semver.MustParse("3.10.0"), release: &ReleaseSpec{ Chart: "test/chart", Version: "0.1", @@ -493,6 +499,43 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { "--namespace", "test-namespace", }, }, + { + name: "recreate-pods-helm4", + defaults: HelmSpec{ + RecreatePods: false, + CreateNamespace: &disable, + }, + version: semver.MustParse("4.0.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + RecreatePods: &enable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--namespace", "test-namespace", + }, + }, + { + name: "recreate-pods-from-default-helm4", + defaults: HelmSpec{ + RecreatePods: true, + CreateNamespace: &disable, + }, + version: semver.MustParse("4.0.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--namespace", "test-namespace", + }, + }, { name: "wait", defaults: HelmSpec{ @@ -1438,6 +1481,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { basePath: "./", Releases: []ReleaseSpec{*tt.release}, HelmDefaults: tt.defaults, + logger: logger, valsRuntime: valsRuntime, } helm := &exectest.Helm{ @@ -1458,6 +1502,47 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { } } +func TestHelmState_flagsForUpgrade_RecreatePodsWarning(t *testing.T) { + enable := true + disable := false + tests := []struct { + name string + version *semver.Version + wantWarn bool + }{ + {name: "helm4", version: semver.MustParse("4.0.0"), wantWarn: true}, + {name: "helm3", version: semver.MustParse("3.10.0"), wantWarn: false}, + } + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + var logs bytes.Buffer + release := &ReleaseSpec{ + Chart: "test/chart", + RecreatePods: &enable, + Name: "test-charts", + Namespace: "test-namespace", + } + state := &HelmState{ + basePath: "./", + Releases: []ReleaseSpec{*release}, + HelmDefaults: HelmSpec{CreateNamespace: &disable}, + logger: helmexec.NewLogger(&logs, "warn"), + valsRuntime: valsRuntime, + } + + if _, _, err := state.flagsForUpgrade(&exectest.Helm{Version: tt.version}, release, 0, &SyncOpts{}); err != nil { + t.Fatalf("unexpected error flagsForUpgrade: %v", err) + } + + warned := strings.Contains(logs.String(), "recreatePods is not supported by Helm 4, ignoring it for release test-charts") + if warned != tt.wantWarn { + t.Errorf("warning logged = %v, want %v; logs: %q", warned, tt.wantWarn, logs.String()) + } + }) + } +} + func TestHelmState_flagsForTemplate(t *testing.T) { enable := true disable := false