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 <aiopsclub@163.com>

* 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 <aiopsclub@163.com>

* 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 <aiopsclub@163.com>

* 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 <aiopsclub@163.com>

* 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 <aiopsclub@163.com>

* 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 <aiopsclub@163.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
yxxhero 2026-03-11 21:23:51 +08:00 committed by GitHub
parent 8301c491ca
commit 607225c34d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 104 additions and 7 deletions

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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 {

View File

@ -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{