diff --git a/docs/index.md b/docs/index.md index 211dbf8e..d771d7fb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -329,6 +329,8 @@ releases: reuseValues: false # set `false` to uninstall this release on sync. (default true) installed: true + # when set to "reinstall", an uninstall will be performed before the update + 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 --git a/pkg/app/app.go b/pkg/app/app.go index 65751681..614c9bf7 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -1392,7 +1392,7 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) { SuppressOutputLineRegex: c.SuppressOutputLineRegex(), } - infoMsg, releasesToBeUpdated, releasesToBeDeleted, errs := r.diff(false, detailedExitCode, c, diffOpts) + infoMsg, releasesToBeUpdated, releasesToBeReinstalled, releasesToBeDeleted, errs := r.diff(false, detailedExitCode, c, diffOpts) if len(errs) > 0 { return false, false, errs } @@ -1407,13 +1407,19 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) { toUpdate = append(toUpdate, r) } + for _, r := range releasesToBeReinstalled { + toDelete = append(toDelete, r) + toUpdate = append(toUpdate, r) + } + releasesWithNoChange := map[string]state.ReleaseSpec{} for _, r := range toApplyWithNeeds { release := r id := state.ReleaseToID(&release) _, uninstalled := releasesToBeDeleted[id] _, updated := releasesToBeUpdated[id] - if !uninstalled && !updated { + _, reinstalled := releasesToBeReinstalled[id] + if !uninstalled && !updated && !reinstalled { releasesWithNoChange[id] = release } } @@ -1478,7 +1484,7 @@ Do you really want to apply? } // We upgrade releases by traversing the DAG - if len(releasesToBeUpdated) > 0 { + if len(releasesToBeUpdated) > 0 || len(releasesToBeReinstalled) > 0 { _, updateErrs := withDAG(st, helm, a.Logger, state.PlanOptions{SelectedReleases: toUpdate, Reverse: false, SkipNeeds: true, IncludeTransitiveNeeds: c.IncludeTransitiveNeeds()}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { var rs []state.ReleaseSpec @@ -1487,6 +1493,9 @@ Do you really want to apply? if r2, ok := releasesToBeUpdated[state.ReleaseToID(&release)]; ok { rs = append(rs, r2) } + if r2, ok := releasesToBeReinstalled[state.ReleaseToID(&release)]; ok { + rs = append(rs, r2) + } } subst.Releases = rs @@ -1525,7 +1534,7 @@ Do you really want to apply? a.Logger.Warnf("warn: %v\n", err) } } - if releasesToBeDeleted == nil && releasesToBeUpdated == nil { + if releasesToBeDeleted == nil && releasesToBeUpdated == nil && releasesToBeReinstalled == nil { return true, false, nil } @@ -1609,8 +1618,8 @@ Do you really want to delete? func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error) { var ( - infoMsg *string - updated, deleted map[string]state.ReleaseSpec + infoMsg *string + updated, reinstalled, deleted map[string]state.ReleaseSpec ) ok, errs := a.withNeeds(r, c, true, func(st *state.HelmState) []error { @@ -1642,12 +1651,12 @@ func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error) ctx: r.ctx, Ask: r.Ask, } - infoMsg, updated, deleted, errs = filtered.diff(true, c.DetailedExitcode(), c, opts) + infoMsg, updated, reinstalled, deleted, errs = filtered.diff(true, c.DetailedExitcode(), c, opts) return errs }) - return infoMsg, ok, len(deleted) > 0 || len(updated) > 0, errs + return infoMsg, ok, len(deleted) > 0 || len(updated) > 0 || len(reinstalled) > 0, errs } func (a *App) lint(r *Run, c LintConfigProvider) (bool, []error, []error) { diff --git a/pkg/app/app_diff_test.go b/pkg/app/app_diff_test.go index d9f7b691..8b104f87 100644 --- a/pkg/app/app_diff_test.go +++ b/pkg/app/app_diff_test.go @@ -415,4 +415,28 @@ releases: }, }) }) + + 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: reinstall +- 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 --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 435ed7cf..fb2dd8fd 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -3073,6 +3073,106 @@ baz 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart3-3.1.0 3.1.0 defau concurrency: 1, }, // + // install with upgrade with reinstall + // + { + name: "install-with-upgrade-with-reinstall", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: baz + chart: stable/mychart3 + disableValidationOnInstall: true + updateStrategy: reinstall +- name: foo + chart: stable/mychart1 + disableValidationOnInstall: true + needs: + - bar +- name: bar + chart: stable/mychart2 + disableValidation: true + updateStrategy: reinstall +`, + }, + diffs: map[exectest.DiffKey]error{ + {Name: "baz", Chart: "stable/mychart3", Flags: "--kube-context default --detailed-exitcode --reset-values"}: helmexec.ExitError{Code: 2}, + {Name: "foo", Chart: "stable/mychart1", Flags: "--disable-validation --kube-context default --detailed-exitcode --reset-values"}: helmexec.ExitError{Code: 2}, + {Name: "bar", Chart: "stable/mychart2", Flags: "--disable-validation --kube-context default --detailed-exitcode --reset-values"}: 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{ + // These releases have updateStrategy=reinstall which will be both uninstalled and installed + {Name: "baz", Flags: []string{}}, + {Name: "bar", Flags: []string{}}, + }, + concurrency: 1, + }, + // + // install with upgrade and --skip-diff-on-install with reinstall + // + { + name: "install-with-upgrade-with-skip-diff-on-install-with-reinstall", + loc: location(), + skipDiffOnInstall: true, + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: baz + chart: stable/mychart3 + disableValidationOnInstall: true + updateStrategy: reinstall +- name: foo + chart: stable/mychart1 + disableValidationOnInstall: true + needs: + - bar +- name: bar + chart: stable/mychart2 + disableValidation: true + updateStrategy: reinstall +`, + }, + diffs: map[exectest.DiffKey]error{ + {Name: "baz", Chart: "stable/mychart3", Flags: "--kube-context default --detailed-exitcode --reset-values"}: helmexec.ExitError{Code: 2}, + {Name: "bar", Chart: "stable/mychart2", Flags: "--disable-validation --kube-context default --detailed-exitcode --reset-values"}: 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{ + // These releases have updateStrategy=reinstall which will be both uninstalled and installed + {Name: "baz", Flags: []string{}}, + {Name: "bar", Flags: []string{}}, + }, + concurrency: 1, + }, + // // upgrades // { @@ -3769,7 +3869,7 @@ releases: } 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 --git a/pkg/app/run.go b/pkg/app/run.go index 2beab11a..f1cc19d6 100644 --- a/pkg/app/run.go +++ b/pkg/app/run.go @@ -137,12 +137,13 @@ func (r *Run) Repos(c ReposConfigProvider) error { return r.ctx.SyncReposOnce(r.state, r.helm) } -func (r *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfigProvider, diffOpts *state.DiffOpts) (*string, map[string]state.ReleaseSpec, map[string]state.ReleaseSpec, []error) { +func (r *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfigProvider, diffOpts *state.DiffOpts) (*string, map[string]state.ReleaseSpec, map[string]state.ReleaseSpec, map[string]state.ReleaseSpec, []error) { st := r.state helm := r.helm var changedReleases []state.ReleaseSpec var deletingReleases []state.ReleaseSpec + var reinstallingReleases []state.ReleaseSpec var planningErrs []error // TODO Better way to detect diff on only filtered releases @@ -154,6 +155,10 @@ func (r *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfig if err != nil { planningErrs = append(planningErrs, err) } + reinstallingReleases, err = st.DetectReleasesToBeReinstalledForSync(helm, st.Releases) + if err != nil { + planningErrs = append(planningErrs, err) + } } fatalErrs := []error{} @@ -170,7 +175,7 @@ func (r *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfig } if len(fatalErrs) > 0 { - return nil, nil, nil, fatalErrs + return nil, nil, nil, nil, fatalErrs } releasesToBeDeleted := map[string]state.ReleaseSpec{} @@ -180,6 +185,14 @@ func (r *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfig releasesToBeDeleted[id] = release } + releasesWithReinstalled := map[string]state.ReleaseSpec{} + for _, r := range reinstallingReleases { + release := r + id := state.ReleaseToID(&release) + releasesWithReinstalled[id] = release + } + + releasesToBeReinstalled := map[string]state.ReleaseSpec{} releasesToBeUpdated := map[string]state.ReleaseSpec{} for _, r := range changedReleases { release := r @@ -187,25 +200,36 @@ func (r *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfig // If `helm-diff` detected changes but it is not being `helm delete`ed, we should run `helm upgrade` if _, ok := releasesToBeDeleted[id]; !ok { - releasesToBeUpdated[id] = release + // Is the release with "reinstall" update strategy + if _, ok := releasesWithReinstalled[id]; ok { + // Make sure we wait on the delete in the case of a reinstall + deleteWait := true + release.DeleteWait = &deleteWait + releasesToBeReinstalled[id] = release + } else { + releasesToBeUpdated[id] = release + } } } // sync only when there are changes - if len(releasesToBeUpdated) == 0 && len(releasesToBeDeleted) == 0 { + if len(releasesToBeUpdated) == 0 && len(releasesToBeDeleted) == 0 && len(releasesToBeReinstalled) == 0 { var msg *string if c.DetailedExitcode() { // TODO better way to get the logger m := "No affected releases" msg = &m } - return msg, nil, nil, nil + return msg, nil, nil, nil, nil } names := []string{} for _, r := range releasesToBeUpdated { names = append(names, fmt.Sprintf(" %s (%s) UPDATED", r.Name, r.Chart)) } + for _, r := range releasesToBeReinstalled { + names = append(names, fmt.Sprintf(" %s (%s) REINSTALLED", r.Name, r.Chart)) + } for _, r := range releasesToBeDeleted { releaseToBeDeleted := fmt.Sprintf(" %s (%s) DELETED", r.Name, r.Chart) if c.Color() { @@ -220,5 +244,5 @@ func (r *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfig %s `, strings.Join(names, "\n")) - return &infoMsg, releasesToBeUpdated, releasesToBeDeleted, nil + return &infoMsg, releasesToBeUpdated, releasesToBeReinstalled, releasesToBeDeleted, nil } diff --git a/pkg/app/testdata/app_diff_test/show_diff_on_changed_selected_release_with_reinstall b/pkg/app/testdata/app_diff_test/show_diff_on_changed_selected_release_with_reinstall new file mode 100644 index 00000000..0d9d039d --- /dev/null +++ b/pkg/app/testdata/app_diff_test/show_diff_on_changed_selected_release_with_reinstall @@ -0,0 +1,42 @@ +processing file "helmfile.yaml" in directory "." +changing working directory to "/path/to" +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: a + 3: chart: incubator/raw + 4: namespace: default + 5: updateStrategy: reinstall + 6: - name: b + 7: chart: incubator/raw + 8: namespace: default + 9: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: a + 3: chart: incubator/raw + 4: namespace: default + 5: updateStrategy: reinstall + 6: - name: b + 7: chart: incubator/raw + 8: namespace: default + 9: + +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 --git a/pkg/app/testdata/testapply/install-with-upgrade-with-reinstall/log b/pkg/app/testdata/testapply/install-with-upgrade-with-reinstall/log new file mode 100644 index 00000000..4967a798 --- /dev/null +++ b/pkg/app/testdata/testapply/install-with-upgrade-with-reinstall/log @@ -0,0 +1,74 @@ +processing file "helmfile.yaml" in directory "." +changing working directory to "/path/to" +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: baz + 3: chart: stable/mychart3 + 4: disableValidationOnInstall: true + 5: updateStrategy: reinstall + 6: - name: foo + 7: chart: stable/mychart1 + 8: disableValidationOnInstall: true + 9: needs: +10: - bar +11: - name: bar +12: chart: stable/mychart2 +13: disableValidation: true +14: updateStrategy: reinstall +15: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: baz + 3: chart: stable/mychart3 + 4: disableValidationOnInstall: true + 5: updateStrategy: reinstall + 6: - name: foo + 7: chart: stable/mychart1 + 8: disableValidationOnInstall: true + 9: needs: +10: - bar +11: - name: bar +12: chart: stable/mychart2 +13: disableValidation: true +14: updateStrategy: reinstall +15: + +merged environment: &{default map[] map[]} +3 release(s) found in helmfile.yaml + +Affected releases are: + bar (stable/mychart2) REINSTALLED + baz (stable/mychart3) REINSTALLED + 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 +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 +foo stable/mychart1 0s + +changing working directory back to "/path/to" diff --git a/pkg/app/testdata/testapply/install-with-upgrade-with-skip-diff-on-install-with-reinstall/log b/pkg/app/testdata/testapply/install-with-upgrade-with-skip-diff-on-install-with-reinstall/log new file mode 100644 index 00000000..4967a798 --- /dev/null +++ b/pkg/app/testdata/testapply/install-with-upgrade-with-skip-diff-on-install-with-reinstall/log @@ -0,0 +1,74 @@ +processing file "helmfile.yaml" in directory "." +changing working directory to "/path/to" +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: baz + 3: chart: stable/mychart3 + 4: disableValidationOnInstall: true + 5: updateStrategy: reinstall + 6: - name: foo + 7: chart: stable/mychart1 + 8: disableValidationOnInstall: true + 9: needs: +10: - bar +11: - name: bar +12: chart: stable/mychart2 +13: disableValidation: true +14: updateStrategy: reinstall +15: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: baz + 3: chart: stable/mychart3 + 4: disableValidationOnInstall: true + 5: updateStrategy: reinstall + 6: - name: foo + 7: chart: stable/mychart1 + 8: disableValidationOnInstall: true + 9: needs: +10: - bar +11: - name: bar +12: chart: stable/mychart2 +13: disableValidation: true +14: updateStrategy: reinstall +15: + +merged environment: &{default map[] map[]} +3 release(s) found in helmfile.yaml + +Affected releases are: + bar (stable/mychart2) REINSTALLED + baz (stable/mychart3) REINSTALLED + 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 +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 +foo stable/mychart1 0s + +changing working directory back to "/path/to" diff --git a/pkg/state/state.go b/pkg/state/state.go index ef93ce67..cfb41e45 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -273,6 +273,8 @@ type ReleaseSpec struct { 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 @@ -457,6 +459,7 @@ type SetValue struct { // 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 @@ -775,6 +778,26 @@ func (st *HelmState) DetectReleasesToBeDeletedForSync(helm helmexec.Interface, r return deleted, nil } +func (st *HelmState) DetectReleasesToBeReinstalledForSync(helm helmexec.Interface, releases []ReleaseSpec) ([]ReleaseSpec, error) { + reinstalled := []ReleaseSpec{} + for i := range releases { + release := releases[i] + + if release.Desired() && (release.UpdateStrategy == "reinstall" || release.UpdateStrategy == "reinstallIfForbidden") { + installed, err := st.isReleaseInstalled(st.createHelmContext(&release, 0), helm, release) + if err != nil { + return nil, err + } + if installed { + // Otherwise `release` messed up(https://github.com/roboll/helmfile/issues/554) + r := release + reinstalled = append(reinstalled, r) + } + } + } + return reinstalled, nil +} + func (st *HelmState) DetectReleasesToBeDeleted(helm helmexec.Interface, releases []ReleaseSpec) ([]ReleaseSpec, error) { detected := []ReleaseSpec{} for i := range releases { @@ -1026,20 +1049,93 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme } 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 == "reinstall" || release.UpdateStrategy == "reinstallIfForbidden" { + syncSuccessful := false + if release.UpdateStrategy == "reinstallIfForbidden" { + if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil { + st.logger.Debugf("reinstallIfForbidden 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("reinstallIfForbidden update strategy - sync failed not due to Fobidden updates") + m.Lock() + affectedReleases.Failed = append(affectedReleases.Failed, release) + m.Unlock() + relErr = newReleaseFailedError(release, err) + } + } else { + st.logger.Debugf("reinstallIfForbidden update strategy - sync success") + syncSuccessful = true + 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 + } + } + } + // Don't attempt to reinstall if a sync was successful + if !syncSuccessful { + st.logger.Debugf("reinstallIfForbidden update strategy - reinstalling...") + installed, err := st.isReleaseInstalled(context, helm, *release) + if err != nil { + relErr = newReleaseFailedError(release, err) + } + if installed { + var args []string + if release.Namespace != "" { + args = append(args, "--namespace", release.Namespace) + } + 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) + relErr = newReleaseFailedError(release, err) + } else if err := helm.DeleteRelease(context, release.Name, deletionFlags...); err != nil { + affectedReleases.Failed = append(affectedReleases.Failed, release) + relErr = newReleaseFailedError(release, err) + } else if _, err := st.triggerReleaseEvent("postuninstall", nil, release, "sync"); err != nil { + affectedReleases.Failed = append(affectedReleases.Failed, release) + relErr = 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() + relErr = 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("getting deployed release version failed: %v", err) + } else { + release.installedVersion = installedVersion + } + } + } } 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 + } } }