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

Reply via email to