From 607225c34db2fc27caf35c3d282302ed682e78a8 Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:23:51 +0800 Subject: [PATCH] fix: use --force-replace flag for Helm 4 instead of deprecated --force (#2477) * fix: use --force-replace flag for Helm 4 instead of deprecated --force Helm 4 deprecated the --force flag in favor of --force-replace. This fix detects the Helm version and uses the appropriate flag: - Helm 4: --force-replace - Helm 3: --force Also fixed a nil pointer panic in appendHideNotesFlags when called with nil SyncOpts. Fixes #2476 Signed-off-by: yxxhero * fix(ci): pin semver to v2.12.0 for Go 1.25 compatibility semver@latest requires Go 1.26.1 but the project uses Go 1.25.4. Pinning to v2.12.0 which is compatible with Go 1.25. Signed-off-by: yxxhero * test: add test cases for force flag from defaults with nil release Add test cases to cover the scenario where release.Force is nil and HelmDefaults.Force enables force for both Helm 3 and Helm 4. Signed-off-by: yxxhero * test: add nil ops test and rename misleading test names - Add test case for appendHideNotesFlags with ops=nil to prevent regression - Rename force-from-default-nil-release-* to force-from-default-nil-force-* for clarity (release.Force is nil, not the release itself) Signed-off-by: yxxhero * refactor: add explicit parentheses for force condition Add explicit parentheses around the two disjuncts in the force condition to make the intended grouping unambiguous and easier to read. Signed-off-by: yxxhero * refactor: check ops nil before Helm version in appendHideNotesFlags - Swap the order to check ops == nil first to avoid unnecessary IsVersionAtLeast call - Restore the "see Helm release" comment for consistency with other flag helpers Signed-off-by: yxxhero --------- Signed-off-by: yxxhero --- .github/workflows/ci.yaml | 2 +- pkg/state/helmx.go | 3 ++ pkg/state/helmx_test.go | 9 ++++ pkg/state/state.go | 8 +++- pkg/state/state_test.go | 89 +++++++++++++++++++++++++++++++++++++-- 5 files changed, 104 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index eef441da..448fecb9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -147,7 +147,7 @@ jobs: with: name: built-binaries-${{ github.run_id }} - name: install semver - run: go install github.com/ffurrer2/semver/v2/cmd/semver@latest + run: go install github.com/ffurrer2/semver/v2/cmd/semver@v2.12.0 - name: Extract tar to get built binaries run: tar -xvf built-binaries.tar - name: Display built binaries diff --git a/pkg/state/helmx.go b/pkg/state/helmx.go index 779bffd2..988afc08 100644 --- a/pkg/state/helmx.go +++ b/pkg/state/helmx.go @@ -257,6 +257,9 @@ func (st *HelmState) appendCascadeFlags(flags []string, helm helmexec.Interface, // append hide-notes flags to helm flags func (st *HelmState) appendHideNotesFlags(flags []string, helm helmexec.Interface, ops *SyncOpts) []string { + if ops == nil { + return flags + } // see https://github.com/helm/helm/releases/tag/v3.16.0 if !helm.IsVersionAtLeast("3.16.0") { return flags diff --git a/pkg/state/helmx_test.go b/pkg/state/helmx_test.go index 80b8d5fe..d4cab5ff 100644 --- a/pkg/state/helmx_test.go +++ b/pkg/state/helmx_test.go @@ -442,6 +442,15 @@ func TestAppendHideNotesFlags(t *testing.T) { expected: []string{"--hide-notes"}, }, }, + { + name: "no hide-notes when ops is nil", + args: args{ + flags: []string{}, + helm: testutil.NewVersionHelmExec("3.16.0"), + opt: nil, + expected: []string{}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/state/state.go b/pkg/state/state.go index 046031e0..431864ea 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -3451,8 +3451,12 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp flags = append(flags, st.timeoutFlags(release, opt)...) - if release.Force != nil && *release.Force || release.Force == nil && st.HelmDefaults.Force { - flags = append(flags, "--force") + if (release.Force != nil && *release.Force) || (release.Force == nil && st.HelmDefaults.Force) { + if helm.IsHelm4() { + flags = append(flags, "--force-replace") + } else { + flags = append(flags, "--force") + } } if release.RecreatePods != nil && *release.RecreatePods || release.RecreatePods == nil && st.HelmDefaults.RecreatePods { diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 21e51643..9fc5d893 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -261,10 +261,12 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { }, }, { - name: "force", + name: "force-helm3", defaults: HelmSpec{ - Force: false, + Force: false, + CreateNamespace: &disable, }, + version: semver.MustParse("3.10.0"), release: &ReleaseSpec{ Chart: "test/chart", Version: "0.1", @@ -279,10 +281,32 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { }, }, { - name: "force-from-default", + name: "force-helm4", defaults: HelmSpec{ - Force: true, + Force: false, + CreateNamespace: &disable, }, + version: semver.MustParse("4.0.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Force: &enable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--force-replace", + "--namespace", "test-namespace", + }, + }, + { + name: "force-from-default-helm3", + defaults: HelmSpec{ + Force: true, + CreateNamespace: &disable, + }, + version: semver.MustParse("3.10.0"), release: &ReleaseSpec{ Chart: "test/chart", Version: "0.1", @@ -295,6 +319,63 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { "--namespace", "test-namespace", }, }, + { + name: "force-from-default-helm4", + defaults: HelmSpec{ + Force: true, + CreateNamespace: &disable, + }, + version: semver.MustParse("4.0.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Force: &disable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--namespace", "test-namespace", + }, + }, + { + name: "force-from-default-nil-force-helm3", + defaults: HelmSpec{ + Force: true, + CreateNamespace: &disable, + }, + version: semver.MustParse("3.10.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--force", + "--namespace", "test-namespace", + }, + }, + { + name: "force-from-default-nil-force-helm4", + defaults: HelmSpec{ + Force: 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", + "--force-replace", + "--namespace", "test-namespace", + }, + }, { name: "recreate-pods", defaults: HelmSpec{