Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package helmfile for openSUSE:Factory checked in at 2025-10-31 16:28:35 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/helmfile (Old) and /work/SRC/openSUSE:Factory/.helmfile.new.1980 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "helmfile" Fri Oct 31 16:28:35 2025 rev:76 rq:1314679 version:1.1.9 Changes: -------- --- /work/SRC/openSUSE:Factory/helmfile/helmfile.changes 2025-10-27 14:44:02.449534788 +0100 +++ /work/SRC/openSUSE:Factory/.helmfile.new.1980/helmfile.changes 2025-10-31 16:29:27.405450646 +0100 @@ -1,0 +2,14 @@ +Fri Oct 31 08:31:50 UTC 2025 - Manfred Hollstein <[email protected]> + +- Update to version 1.1.9: + * build(deps): bump actions/upload-artifact from 4 to 5 by + @dependabot[bot] in #2236 + * build(deps): bump actions/download-artifact from 5 to 6 by + @dependabot[bot] in #2235 + * feat: update strategy for reinstall by @simbou2000 in #2019 + * build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 + from 1.88.7 to 1.89.0 by @dependabot[bot] in #2239 + * Fix: Handle empty helmBinary in base files with environment + values by @Copilot in #2237 + +------------------------------------------------------------------- Old: ---- helmfile-1.1.8.tar.gz New: ---- helmfile-1.1.9.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ helmfile.spec ++++++ --- /var/tmp/diff_new_pack.IIygkU/_old 2025-10-31 16:29:28.381492116 +0100 +++ /var/tmp/diff_new_pack.IIygkU/_new 2025-10-31 16:29:28.381492116 +0100 @@ -17,9 +17,9 @@ # -%define git_commit d3908e6a3c179a38ad8f416a1ff715431bb93543 +%define git_commit 4a1c53cf9f09e45c3a94cba63ea2f5679b1b9470 Name: helmfile -Version: 1.1.8 +Version: 1.1.9 Release: 0 Summary: Deploy Kubernetes Helm Charts License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.IIygkU/_old 2025-10-31 16:29:28.429494156 +0100 +++ /var/tmp/diff_new_pack.IIygkU/_new 2025-10-31 16:29:28.433494325 +0100 @@ -5,7 +5,7 @@ <param name="exclude">.git</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> - <param name="revision">v1.1.8</param> + <param name="revision">v1.1.9</param> <param name="changesgenerate">enable</param> <param name="changesauthor">[email protected]</param> </service> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.IIygkU/_old 2025-10-31 16:29:28.465495685 +0100 +++ /var/tmp/diff_new_pack.IIygkU/_new 2025-10-31 16:29:28.469495855 +0100 @@ -1,7 +1,7 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/helmfile/helmfile.git</param> - <param name="changesrevision">d3908e6a3c179a38ad8f416a1ff715431bb93543</param> + <param name="changesrevision">4a1c53cf9f09e45c3a94cba63ea2f5679b1b9470</param> </service> </servicedata> ++++++ helmfile-1.1.8.tar.gz -> helmfile-1.1.9.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/docs/index.md new/helmfile-1.1.9/docs/index.md --- old/helmfile-1.1.8/docs/index.md 2025-10-27 00:41:39.000000000 +0100 +++ new/helmfile-1.1.9/docs/index.md 2025-10-30 04:22:06.000000000 +0100 @@ -328,6 +328,9 @@ reuseValues: false # set `false` to uninstall this release on sync. (default true) installed: true + # Defines the strategy to use when updating. Possible value is: + # - "reinstallIfForbidden": Performs an uninstall before the update only if the update is forbidden (e.g., due to permission issues or conflicts). + updateStrategy: "" # restores previous state in case of failed release (default false) atomic: true # when true, cleans up any new resources created during a failed release (default false) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/go.mod new/helmfile-1.1.9/go.mod --- old/helmfile-1.1.8/go.mod 2025-10-27 00:41:39.000000000 +0100 +++ new/helmfile-1.1.9/go.mod 2025-10-30 04:22:06.000000000 +0100 @@ -7,7 +7,7 @@ github.com/Masterminds/semver/v3 v3.4.0 github.com/Masterminds/sprig/v3 v3.3.0 github.com/aws/aws-sdk-go-v2/config v1.31.15 - github.com/aws/aws-sdk-go-v2/service/s3 v1.88.7 + github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/go-test/deep v1.1.1 github.com/golang/mock v1.6.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/go.sum new/helmfile-1.1.9/go.sum --- old/helmfile-1.1.8/go.sum 2025-10-27 00:41:39.000000000 +0100 +++ new/helmfile-1.1.9/go.sum 2025-10-30 04:22:06.000000000 +0100 @@ -170,8 +170,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11/go.mod h1:3C1gN4FmIVLwYSh8etngUS+f1viY6nLCDVtZmrFbDy0= github.com/aws/aws-sdk-go-v2/service/kms v1.45.6 h1:Br3kil4j7RPW+7LoLVkYt8SuhIWlg6ylmbmzXJ7PgXY= github.com/aws/aws-sdk-go-v2/service/kms v1.45.6/go.mod h1:FKXkHzw1fJZtg1P1qoAIiwen5thz/cDRTTDCIu8ljxc= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.7 h1:Wer3W0GuaedWT7dv/PiWNZGSQFSTcBY2rZpbiUp5xcA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.7/go.mod h1:UHKgcRSx8PVtvsc1Poxb/Co3PD3wL7P+f49P0+cWtuY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0 h1:JbCUlVDEjmhpvpIgXP9QN+/jW61WWWj99cGmxMC49hM= +github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0/go.mod h1:UHKgcRSx8PVtvsc1Poxb/Co3PD3wL7P+f49P0+cWtuY= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.6 h1:9PWl450XOG+m5lKv+qg5BXso1eLxpsZLqq7VPug5km0= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.6/go.mod h1:hwt7auGsDcaNQ8pzLgE2kCNyIWouYlAKSjuUu5Dqr7I= github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 h1:TFg6XiS7EsHN0/jpV3eVNczZi/sPIVP5jxIs+euIESQ= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/app/app.go new/helmfile-1.1.9/pkg/app/app.go --- old/helmfile-1.1.8/pkg/app/app.go 2025-10-27 00:41:39.000000000 +0100 +++ new/helmfile-1.1.9/pkg/app/app.go 2025-10-30 04:22:06.000000000 +0100 @@ -793,6 +793,9 @@ } bin := st.DefaultHelmBinary + if bin == "" { + bin = state.DefaultHelmBinary + } kubeconfig := a.Kubeconfig kubectx := st.HelmDefaults.KubeContext diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/app/app_diff_test.go new/helmfile-1.1.9/pkg/app/app_diff_test.go --- old/helmfile-1.1.8/pkg/app/app_diff_test.go 2025-10-27 00:41:39.000000000 +0100 +++ new/helmfile-1.1.9/pkg/app/app_diff_test.go 2025-10-30 04:22:06.000000000 +0100 @@ -415,4 +415,28 @@ }, }) }) + + t.Run("show diff on changed selected release with reinstall", func(t *testing.T) { + check(t, testcase{ + helmfile: ` +releases: +- name: a + chart: incubator/raw + namespace: default + updateStrategy: reinstallIfForbidden +- name: b + chart: incubator/raw + namespace: default +`, + selectors: []string{"name=a"}, + lists: map[exectest.ListKey]string{ + {Filter: "^a$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default +`, + }, + diffed: []exectest.Release{ + {Name: "a", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}}, + }, + }) + }) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/app/app_gethelm_test.go new/helmfile-1.1.9/pkg/app/app_gethelm_test.go --- old/helmfile-1.1.8/pkg/app/app_gethelm_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/helmfile-1.1.9/pkg/app/app_gethelm_test.go 2025-10-30 04:22:06.000000000 +0100 @@ -0,0 +1,51 @@ +package app + +import ( + goContext "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/helmfile/helmfile/pkg/state" +) + +// TestGetHelmWithEmptyDefaultHelmBinary tests that getHelm properly defaults to "helm" +// when st.DefaultHelmBinary is empty. This addresses the issue where base files with +// environment secrets would fail with "exec: no command" error. +// +// Background: When a base file has environment secrets but doesn't specify helmBinary, +// the state.DefaultHelmBinary would be empty, causing helmexec.New to be called with +// an empty string, which results in "error determining helm version: exec: no command". +// +// The fix in app.getHelm() ensures that when st.DefaultHelmBinary is empty, it defaults +// to state.DefaultHelmBinary ("helm"). +func TestGetHelmWithEmptyDefaultHelmBinary(t *testing.T) { + // Test that app.getHelm() handles empty DefaultHelmBinary correctly by applying a default + st := &state.HelmState{ + ReleaseSetSpec: state.ReleaseSetSpec{ + DefaultHelmBinary: "", // Empty, as would be the case for base files + }, + } + + logger := newAppTestLogger() + app := &App{ + OverrideHelmBinary: "", + OverrideKubeContext: "", + Logger: logger, + Env: "default", + ctx: goContext.Background(), + } + + // This should NOT fail because app.getHelm() defaults empty DefaultHelmBinary to "helm" + helm, err := app.getHelm(st) + + // Verify that no error occurred - the fix in app.getHelm() prevents the "exec: no command" error + require.NoError(t, err, "getHelm should not fail when DefaultHelmBinary is empty (fix should apply default)") + + // Verify that a valid helm execer was returned + require.NotNil(t, helm, "getHelm should return a valid helm execer") + + // Verify that the helm version is accessible (confirms the helm binary is valid) + version := helm.GetVersion() + require.NotNil(t, version, "helm version should be accessible") +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/app/app_test.go new/helmfile-1.1.9/pkg/app/app_test.go --- old/helmfile-1.1.8/pkg/app/app_test.go 2025-10-27 00:41:39.000000000 +0100 +++ new/helmfile-1.1.9/pkg/app/app_test.go 2025-10-30 04:22:06.000000000 +0100 @@ -220,6 +220,97 @@ } } +func TestUpdateStrategyParamValidation(t *testing.T) { + cases := []struct { + files map[string]string + updateStrategy string + isValid bool + }{ + {map[string]string{ + "/path/to/helmfile.yaml": `releases: +- name: zipkin + chart: stable/zipkin + updateStrategy: reinstallIfForbidden +`}, + "reinstallIfForbidden", + true}, + {map[string]string{ + "/path/to/helmfile.yaml": `releases: +- name: zipkin + chart: stable/zipkin + updateStrategy: reinstallIfForbidden +`}, + "reinstallIfForbidden", + true}, + {map[string]string{ + "/path/to/helmfile.yaml": `releases: +- name: zipkin + chart: stable/zipkin + updateStrategy: +`}, + "", + true}, + {map[string]string{ + "/path/to/helmfile.yaml": `releases: +- name: zipkin + chart: stable/zipkin + updateStrategy: foo +`}, + "foo", + false}, + {map[string]string{ + "/path/to/helmfile.yaml": `releases: +- name: zipkin + chart: stable/zipkin + updateStrategy: reinstal +`}, + "reinstal", + false}, + {map[string]string{ + "/path/to/helmfile.yaml": `releases: +- name: zipkin + chart: stable/zipkin + updateStrategy: reinstall1 +`}, + "reinstall1", + false}, + } + + for idx, c := range cases { + fs := testhelper.NewTestFs(c.files) + app := &App{ + OverrideHelmBinary: DefaultHelmBinary, + OverrideKubeContext: "default", + Logger: newAppTestLogger(), + Namespace: "", + Env: "default", + FileOrDir: "helmfile.yaml", + } + + expectNoCallsToHelm(app) + + app = injectFs(app, fs) + + err := app.ForEachState( + Noop, + false, + SetFilter(true), + ) + + if c.isValid && err != nil { + t.Errorf("[case: %d] Unexpected error for valid case: %v", idx, err) + } else if !c.isValid { + var invalidUpdateStrategy state.InvalidUpdateStrategyError + invalidUpdateStrategy.UpdateStrategy = c.updateStrategy + if err == nil { + t.Errorf("[case: %d] Expected error for invalid case", idx) + } else if !strings.Contains(err.Error(), invalidUpdateStrategy.Error()) { + t.Errorf("[case: %d] Unexpected error returned for invalid case\ngot: %v\nexpected underlying error: %s", idx, err, invalidUpdateStrategy.Error()) + } + } + } +} + func TestVisitDesiredStatesWithReleasesFiltered_Issue1008_MissingNonDefaultEnvInBase(t *testing.T) { files := map[string]string{ "/path/to/base.yaml": ` @@ -3076,6 +3167,97 @@ concurrency: 1, }, // + // install with upgrade with reinstallIfForbidden + // + { + name: "install-with-upgrade-with-reinstallIfForbidden", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: baz + chart: stable/mychart3 + disableValidationOnInstall: true + updateStrategy: reinstallIfForbidden +- name: foo + chart: stable/mychart1 + disableValidationOnInstall: true + needs: + - bar +- name: bar + chart: stable/mychart2 + disableValidation: true + updateStrategy: reinstallIfForbidden +`, + }, + diffs: map[exectest.DiffKey]error{ + {Name: "baz", Chart: "stable/mychart3", Flags: "--kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2}, + {Name: "foo", Chart: "stable/mychart1", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2}, + {Name: "bar", Chart: "stable/mychart2", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + lists: map[exectest.ListKey]string{ + {Filter: "^foo$", Flags: listFlags("", "default")}: ``, + {Filter: "^bar$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default +`, + {Filter: "^baz$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +baz 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart3-3.1.0 3.1.0 default +`, + }, + upgraded: []exectest.Release{ + {Name: "baz", Flags: []string{"--kube-context", "default"}}, + {Name: "bar", Flags: []string{"--kube-context", "default"}}, + {Name: "foo", Flags: []string{"--kube-context", "default"}}, + }, + deleted: []exectest.Release{}, + concurrency: 1, + }, + // + // install with upgrade and --skip-diff-on-install with reinstallIfForbidden + // + { + name: "install-with-upgrade-with-skip-diff-on-install-with-reinstallIfForbidden", + loc: location(), + skipDiffOnInstall: true, + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: baz + chart: stable/mychart3 + disableValidationOnInstall: true + updateStrategy: reinstallIfForbidden +- name: foo + chart: stable/mychart1 + disableValidationOnInstall: true + needs: + - bar +- name: bar + chart: stable/mychart2 + disableValidation: true + updateStrategy: reinstallIfForbidden +`, + }, + diffs: map[exectest.DiffKey]error{ + {Name: "baz", Chart: "stable/mychart3", Flags: "--kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2}, + {Name: "bar", Chart: "stable/mychart2", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + lists: map[exectest.ListKey]string{ + {Filter: "^foo$", Flags: listFlags("", "default")}: ``, + {Filter: "^bar$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default +`, + {Filter: "^baz$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +baz 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart3-3.1.0 3.1.0 default +`, + }, + upgraded: []exectest.Release{ + {Name: "baz", Flags: []string{"--kube-context", "default"}}, + {Name: "bar", Flags: []string{"--kube-context", "default"}}, + {Name: "foo", Flags: []string{"--kube-context", "default"}}, + }, + concurrency: 1, + }, + // // upgrades // { @@ -3772,7 +3954,7 @@ } for flagIdx := range wantDeletes[relIdx].Flags { if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] { - t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx]) + t.Errorf("releases[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx]) } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/app/desired_state_file_loader.go new/helmfile-1.1.9/pkg/app/desired_state_file_loader.go --- old/helmfile-1.1.8/pkg/app/desired_state_file_loader.go 2025-10-27 00:41:39.000000000 +0100 +++ new/helmfile-1.1.9/pkg/app/desired_state_file_loader.go 2025-10-30 04:22:06.000000000 +0100 @@ -5,6 +5,7 @@ "errors" "fmt" "path/filepath" + "slices" "dario.cat/mergo" "github.com/helmfile/vals" @@ -285,6 +286,18 @@ } } + // Validate updateStrategy value if set in the releases + for i := range finalState.Releases { + if finalState.Releases[i].UpdateStrategy != "" { + if !slices.Contains(state.ValidUpdateStrategyValues, finalState.Releases[i].UpdateStrategy) { + return nil, &state.StateLoadError{ + Msg: fmt.Sprintf("failed to read %s", finalState.FilePath), + Cause: &state.InvalidUpdateStrategyError{UpdateStrategy: finalState.Releases[i].UpdateStrategy}, + } + } + } + } + finalState.OrginReleases = finalState.Releases return finalState, nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/app/testdata/app_diff_test/show_diff_on_changed_selected_release_with_reinstall new/helmfile-1.1.9/pkg/app/testdata/app_diff_test/show_diff_on_changed_selected_release_with_reinstall --- old/helmfile-1.1.8/pkg/app/testdata/app_diff_test/show_diff_on_changed_selected_release_with_reinstall 1970-01-01 01:00:00.000000000 +0100 +++ new/helmfile-1.1.9/pkg/app/testdata/app_diff_test/show_diff_on_changed_selected_release_with_reinstall 2025-10-30 04:22:06.000000000 +0100 @@ -0,0 +1,11 @@ +processing file "helmfile.yaml" in directory "." +changing working directory to "/path/to" +merged environment: &{default map[] map[]} +1 release(s) matching name=a found in helmfile.yaml + +processing 1 groups of releases in this order: +GROUP RELEASES +1 default/default/a + +processing releases in group 1/1: default/default/a +changing working directory back to "/path/to" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/app/testdata/testapply/install-with-upgrade-with-reinstallifforbidden/log new/helmfile-1.1.9/pkg/app/testdata/testapply/install-with-upgrade-with-reinstallifforbidden/log --- old/helmfile-1.1.8/pkg/app/testdata/testapply/install-with-upgrade-with-reinstallifforbidden/log 1970-01-01 01:00:00.000000000 +0100 +++ new/helmfile-1.1.9/pkg/app/testdata/testapply/install-with-upgrade-with-reinstallifforbidden/log 2025-10-30 04:22:06.000000000 +0100 @@ -0,0 +1,35 @@ +processing file "helmfile.yaml" in directory "." +changing working directory to "/path/to" +merged environment: &{default map[] map[]} +3 release(s) found in helmfile.yaml + +Affected releases are: + bar (stable/mychart2) UPDATED + baz (stable/mychart3) UPDATED + foo (stable/mychart1) UPDATED + +invoking preapply hooks for 2 groups of releases in this order: +GROUP RELEASES +1 default//foo +2 default//baz, default//bar + +invoking preapply hooks for releases in group 1/2: default//foo +invoking preapply hooks for releases in group 2/2: default//baz, default//bar +processing 2 groups of releases in this order: +GROUP RELEASES +1 default//baz, default//bar +2 default//foo + +processing releases in group 1/2: default//baz, default//bar +update strategy - sync success +update strategy - sync success +processing releases in group 2/2: default//foo +getting deployed release version failed: Failed to get the version for: mychart1 + +UPDATED RELEASES: +NAME NAMESPACE CHART VERSION DURATION +baz stable/mychart3 3.1.0 0s +bar stable/mychart2 3.1.0 0s +foo stable/mychart1 0s + +changing working directory back to "/path/to" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/app/testdata/testapply/install-with-upgrade-with-skip-diff-on-install-with-reinstallifforbidden/log new/helmfile-1.1.9/pkg/app/testdata/testapply/install-with-upgrade-with-skip-diff-on-install-with-reinstallifforbidden/log --- old/helmfile-1.1.8/pkg/app/testdata/testapply/install-with-upgrade-with-skip-diff-on-install-with-reinstallifforbidden/log 1970-01-01 01:00:00.000000000 +0100 +++ new/helmfile-1.1.9/pkg/app/testdata/testapply/install-with-upgrade-with-skip-diff-on-install-with-reinstallifforbidden/log 2025-10-30 04:22:06.000000000 +0100 @@ -0,0 +1,35 @@ +processing file "helmfile.yaml" in directory "." +changing working directory to "/path/to" +merged environment: &{default map[] map[]} +3 release(s) found in helmfile.yaml + +Affected releases are: + bar (stable/mychart2) UPDATED + baz (stable/mychart3) UPDATED + foo (stable/mychart1) UPDATED + +invoking preapply hooks for 2 groups of releases in this order: +GROUP RELEASES +1 default//foo +2 default//baz, default//bar + +invoking preapply hooks for releases in group 1/2: default//foo +invoking preapply hooks for releases in group 2/2: default//baz, default//bar +processing 2 groups of releases in this order: +GROUP RELEASES +1 default//baz, default//bar +2 default//foo + +processing releases in group 1/2: default//baz, default//bar +update strategy - sync success +update strategy - sync success +processing releases in group 2/2: default//foo +getting deployed release version failed: Failed to get the version for: mychart1 + +UPDATED RELEASES: +NAME NAMESPACE CHART VERSION DURATION +baz stable/mychart3 3.1.0 0s +bar stable/mychart2 3.1.0 0s +foo stable/mychart1 0s + +changing working directory back to "/path/to" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/exectest/helm.go new/helmfile-1.1.9/pkg/exectest/helm.go --- old/helmfile-1.1.8/pkg/exectest/helm.go 2025-10-27 00:41:39.000000000 +0100 +++ new/helmfile-1.1.9/pkg/exectest/helm.go 2025-10-30 04:22:06.000000000 +0100 @@ -56,9 +56,10 @@ } type Affected struct { - Upgraded []*Release - Deleted []*Release - Failed []*Release + Upgraded []*Release + Reinstalled []*Release + Deleted []*Release + Failed []*Release } func (helm *Helm) UpdateDeps(chart string) error { @@ -107,7 +108,24 @@ return nil } func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart, namespace string, flags ...string) error { - if strings.Contains(name, "error") { + if strings.Contains(name, "forbidden") { + releaseExists := false + for _, release := range helm.Releases { + if release.Name == name { + releaseExists = true + } + } + releaseDeleted := false + for _, release := range helm.Deleted { + if release.Name == name { + releaseDeleted = true + } + } + // Only fail if the release is present in the helm.Releases to simulate a forbidden update if it exists + if releaseExists && !releaseDeleted { + return fmt.Errorf("cannot patch %q with kind StatefulSet: StatefulSet.apps %q is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden", name, name) + } + } else if strings.Contains(name, "error") { return errors.New("error") } helm.sync(helm.ReleasesMutex, func() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/state/create.go new/helmfile-1.1.9/pkg/state/create.go --- old/helmfile-1.1.8/pkg/state/create.go 2025-10-27 00:41:39.000000000 +0100 +++ new/helmfile-1.1.9/pkg/state/create.go 2025-10-30 04:22:06.000000000 +0100 @@ -26,6 +26,8 @@ DefaultHCLFileExtension = ".hcl" ) +var ValidUpdateStrategyValues = []string{UpdateStrategyReinstallIfForbidden} + type StateLoadError struct { Msg string Cause error @@ -43,6 +45,14 @@ return fmt.Sprintf("environment \"%s\" is not defined", e.Env) } +type InvalidUpdateStrategyError struct { + UpdateStrategy string +} + +func (e *InvalidUpdateStrategyError) Error() string { + return fmt.Sprintf("updateStrategy %q is invalid, valid values are: %s or not set", e.UpdateStrategy, strings.Join(ValidUpdateStrategyValues, ", ")) +} + type StateCreator struct { logger *zap.SugaredLogger diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/state/state.go new/helmfile-1.1.9/pkg/state/state.go --- old/helmfile-1.1.8/pkg/state/state.go 2025-10-27 00:41:39.000000000 +0100 +++ new/helmfile-1.1.9/pkg/state/state.go 2025-10-30 04:22:06.000000000 +0100 @@ -43,6 +43,9 @@ // This is used by an interim solution to make the urfave/cli command report to the helmfile internal about that the // --timeout flag is missingl EmptyTimeout = -1 + + // Valid enum for updateStrategy values + UpdateStrategyReinstallIfForbidden = "reinstallIfForbidden" ) // ReleaseSetSpec is release set spec @@ -277,6 +280,8 @@ Force *bool `yaml:"force,omitempty"` // Installed, when set to true, `delete --purge` the release Installed *bool `yaml:"installed,omitempty"` + // UpdateStrategy, when set, indicate the strategy to use to update the release + UpdateStrategy string `yaml:"updateStrategy,omitempty"` // Atomic, when set to true, restore previous state in case of a failed install/upgrade attempt Atomic *bool `yaml:"atomic,omitempty"` // CleanupOnFail, when set to true, the --cleanup-on-fail helm flag is passed to the upgrade command @@ -467,6 +472,7 @@ // AffectedReleases hold the list of released that where updated, deleted, or in error type AffectedReleases struct { Upgraded []*ReleaseSpec + Reinstalled []*ReleaseSpec Deleted []*ReleaseSpec Failed []*ReleaseSpec DeleteFailed []*ReleaseSpec @@ -1037,20 +1043,24 @@ } m.Unlock() } - } else if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil { - m.Lock() - affectedReleases.Failed = append(affectedReleases.Failed, release) - m.Unlock() - relErr = newReleaseFailedError(release, err) + } else if release.UpdateStrategy == UpdateStrategyReinstallIfForbidden { + relErr = st.performSyncOrReinstallOfRelease(affectedReleases, helm, context, release, chart, m, flags...) } else { - m.Lock() - affectedReleases.Upgraded = append(affectedReleases.Upgraded, release) - m.Unlock() - installedVersion, err := st.getDeployedVersion(context, helm, release) - if err != nil { // err is not really impacting so just log it - st.logger.Debugf("getting deployed release version failed: %v", err) + if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil { + m.Lock() + affectedReleases.Failed = append(affectedReleases.Failed, release) + m.Unlock() + relErr = newReleaseFailedError(release, err) } else { - release.installedVersion = installedVersion + m.Lock() + affectedReleases.Upgraded = append(affectedReleases.Upgraded, release) + m.Unlock() + installedVersion, err := st.getDeployedVersion(context, helm, release) + if err != nil { // err is not really impacting so just log it + st.logger.Debugf("getting deployed release version failed: %v", err) + } else { + release.installedVersion = installedVersion + } } } @@ -1096,6 +1106,77 @@ return nil } +func (st *HelmState) performSyncOrReinstallOfRelease(affectedReleases *AffectedReleases, helm helmexec.Interface, context helmexec.HelmContext, release *ReleaseSpec, chart string, m *sync.Mutex, flags ...string) *ReleaseError { + if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil { + st.logger.Debugf("update strategy - sync failed: %s", err.Error()) + // Only fail if a different error than forbidden updates + if !strings.Contains(err.Error(), "Forbidden: updates") { + st.logger.Debugf("update strategy - sync failed not due to Forbidden updates") + m.Lock() + affectedReleases.Failed = append(affectedReleases.Failed, release) + m.Unlock() + return newReleaseFailedError(release, err) + } + } else { + st.logger.Debugf("update strategy - sync success") + m.Lock() + affectedReleases.Upgraded = append(affectedReleases.Upgraded, release) + m.Unlock() + installedVersion, err := st.getDeployedVersion(context, helm, release) + if err != nil { // err is not really impacting so just log it + st.logger.Debugf("update strategy - getting deployed release version failed: %v", err) + } else { + release.installedVersion = installedVersion + } + return nil + } + + st.logger.Infof("Failed to sync due to forbidden updates, attempting to reinstall %q allowed by update strategy", release.Name) + installed, err := st.isReleaseInstalled(context, helm, *release) + if err != nil { + return newReleaseFailedError(release, err) + } + if installed { + var args []string + if release.Namespace != "" { + args = append(args, "--namespace", release.Namespace) + } + deleteWaitFlag := true + release.DeleteWait = &deleteWaitFlag + args = st.appendDeleteWaitFlags(args, release) + deletionFlags := st.appendConnectionFlags(args, release) + m.Lock() + if _, err := st.triggerReleaseEvent("preuninstall", nil, release, "sync"); err != nil { + affectedReleases.Failed = append(affectedReleases.Failed, release) + return newReleaseFailedError(release, err) + } else if err := helm.DeleteRelease(context, release.Name, deletionFlags...); err != nil { + affectedReleases.Failed = append(affectedReleases.Failed, release) + return newReleaseFailedError(release, err) + } else if _, err := st.triggerReleaseEvent("postuninstall", nil, release, "sync"); err != nil { + affectedReleases.Failed = append(affectedReleases.Failed, release) + return newReleaseFailedError(release, err) + } + m.Unlock() + } + if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil { + m.Lock() + affectedReleases.Failed = append(affectedReleases.Failed, release) + m.Unlock() + return newReleaseFailedError(release, err) + } else { + m.Lock() + affectedReleases.Reinstalled = append(affectedReleases.Reinstalled, release) + m.Unlock() + installedVersion, err := st.getDeployedVersion(context, helm, release) + if err != nil { // err is not really impacting so just log it + st.logger.Debugf("update strategy - getting deployed release version failed: %v", err) + } else { + release.installedVersion = installedVersion + } + } + return nil +} + func (st *HelmState) listReleases(context helmexec.HelmContext, helm helmexec.Interface, release *ReleaseSpec) (string, error) { flags := st.kubeConnectionFlags(release) if release.Namespace != "" { @@ -3726,6 +3807,28 @@ modifiedChart, modErr := hideChartCredentials(release.Chart) if modErr != nil { logger.Warn("Could not modify chart credentials, %v", modErr) + continue + } + err := tbl.AddRow(release.Name, release.Namespace, modifiedChart, release.installedVersion, release.duration.Round(time.Second)) + if err != nil { + logger.Warn("Could not add row, %v", err) + } + } + logger.Info(tbl.String()) + } + if len(ar.Reinstalled) > 0 { + logger.Info("\nREINSTALLED RELEASES:") + tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"}, + prettytable.Column{Header: "NAMESPACE", MinWidth: 6}, + prettytable.Column{Header: "CHART", MinWidth: 6}, + prettytable.Column{Header: "VERSION", MinWidth: 6}, + prettytable.Column{Header: "DURATION", AlignRight: true}, + ) + tbl.Separator = " " + for _, release := range ar.Reinstalled { + modifiedChart, modErr := hideChartCredentials(release.Chart) + if modErr != nil { + logger.Warn("Could not modify chart credentials, %v", modErr) continue } err := tbl.AddRow(release.Name, release.Namespace, modifiedChart, release.installedVersion, release.duration.Round(time.Second)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/state/state_test.go new/helmfile-1.1.9/pkg/state/state_test.go --- old/helmfile-1.1.8/pkg/state/state_test.go 2025-10-27 00:41:39.000000000 +0100 +++ new/helmfile-1.1.9/pkg/state/state_test.go 2025-10-30 04:22:06.000000000 +0100 @@ -1640,6 +1640,112 @@ } } +func TestHelmState_SyncReleasesAffectedReleasesWithReinstallIfForbidden(t *testing.T) { + no := false + tests := []struct { + name string + releases []ReleaseSpec + installed []bool + wantAffected exectest.Affected + }{ + { + name: "2 new", + releases: []ReleaseSpec{ + { + Name: "releaseNameFoo-forbidden", + Chart: "foo", + UpdateStrategy: "reinstallIfForbidden", + }, + { + Name: "releaseNameBar-forbidden", + Chart: "foo", + UpdateStrategy: "reinstallIfForbidden", + }, + }, + wantAffected: exectest.Affected{ + Upgraded: []*exectest.Release{ + {Name: "releaseNameFoo-forbidden", Flags: []string{}}, + {Name: "releaseNameBar-forbidden", Flags: []string{}}, + }, + Reinstalled: nil, + Deleted: nil, + Failed: nil, + }, + }, + { + name: "1 removed, 1 new, 1 reinstalled first new", + releases: []ReleaseSpec{ + { + Name: "releaseNameFoo-forbidden", + Chart: "foo", + UpdateStrategy: "reinstallIfForbidden", + }, + { + Name: "releaseNameBar", + Chart: "foo", + UpdateStrategy: "reinstallIfForbidden", + Installed: &no, + }, + { + Name: "releaseNameFoo-forbidden", + Chart: "foo", + UpdateStrategy: "reinstallIfForbidden", + }, + }, + installed: []bool{true, true, true}, + wantAffected: exectest.Affected{ + Upgraded: []*exectest.Release{ + {Name: "releaseNameFoo-forbidden", Flags: []string{}}, + }, + Reinstalled: []*exectest.Release{ + {Name: "releaseNameFoo-forbidden", Flags: []string{}}, + }, + Deleted: []*exectest.Release{ + {Name: "releaseNameBar", Flags: []string{}}, + }, + Failed: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + state := &HelmState{ + ReleaseSetSpec: ReleaseSetSpec{ + Releases: tt.releases, + }, + logger: logger, + valsRuntime: valsRuntime, + RenderedValues: map[string]any{}, + } + helm := &exectest.Helm{ + Lists: map[exectest.ListKey]string{}, + } + //simulate the release is already installed + for i, release := range tt.releases { + if tt.installed != nil && tt.installed[i] { + helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$", Flags: "--uninstalling --deployed --failed --pending"}] = release.Name + } + } + + affectedReleases := AffectedReleases{} + if err := state.SyncReleases(&affectedReleases, helm, []string{}, 1); err != nil { + if !testEq(affectedReleases.Failed, tt.wantAffected.Failed) { + t.Errorf("HelmState.SyncReleases() error failed for [%s] = %v, want %v", tt.name, affectedReleases.Failed, tt.wantAffected.Failed) + } //else expected error + } + if !testEq(affectedReleases.Upgraded, tt.wantAffected.Upgraded) { + t.Errorf("HelmState.SyncReleases() upgrade failed for [%s] = %v, want %v", tt.name, affectedReleases.Upgraded, tt.wantAffected.Upgraded) + } + if !testEq(affectedReleases.Reinstalled, tt.wantAffected.Reinstalled) { + t.Errorf("HelmState.SyncReleases() reinstalled failed for [%s] = %v, want %v", tt.name, affectedReleases.Reinstalled, tt.wantAffected.Reinstalled) + } + if !testEq(affectedReleases.Deleted, tt.wantAffected.Deleted) { + t.Errorf("HelmState.SyncReleases() deleted failed for [%s] = %v, want %v", tt.name, affectedReleases.Deleted, tt.wantAffected.Deleted) + } + }) + } +} + func testEq(a []*ReleaseSpec, b []*exectest.Release) bool { // If one is nil, the other must also be nil. if (a == nil) != (b == nil) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/helmfile-1.1.8/pkg/state/temp_test.go new/helmfile-1.1.9/pkg/state/temp_test.go --- old/helmfile-1.1.8/pkg/state/temp_test.go 2025-10-27 00:41:39.000000000 +0100 +++ new/helmfile-1.1.9/pkg/state/temp_test.go 2025-10-30 04:22:06.000000000 +0100 @@ -38,39 +38,39 @@ run(testcase{ subject: "baseline", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, - want: "foo-values-7d454b9558", + want: "foo-values-67dc97cbcb", }) run(testcase{ subject: "different bytes content", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, data: []byte(`{"k":"v"}`), - want: "foo-values-59c86d55bf", + want: "foo-values-75d7c4758c", }) run(testcase{ subject: "different map content", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, data: map[string]any{"k": "v"}, - want: "foo-values-6f87c5cd79", + want: "foo-values-685f8cf685", }) run(testcase{ subject: "different chart", release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"}, - want: "foo-values-5dfd748475", + want: "foo-values-75597d9c57", }) run(testcase{ subject: "different name", release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"}, - want: "bar-values-858b9c55cc", + want: "bar-values-7b77df65ff", }) run(testcase{ subject: "specific ns", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"}, - want: "myns-foo-values-58dc9c6667", + want: "myns-foo-values-85f979545c", }) for id, n := range ids { ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/helmfile/vendor.tar.gz /work/SRC/openSUSE:Factory/.helmfile.new.1980/vendor.tar.gz differ: char 58, line 1
