diff --git a/docs/index.md b/docs/index.md index 749a87f8..211dbf8e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -315,7 +315,7 @@ releases: # will attempt to decrypt it using helm-secrets plugin secrets: - vault_secret.yaml - # Override helmDefaults options for verify, wait, waitForJobs, timeout, recreatePods and force. + # Override helmDefaults options for verify, wait, waitForJobs, timeout, recreatePods, force and reuseValues. verify: true keyring: path/to/keyring.gpg # --skip-schema-validation flag to helm 'install', 'upgrade' and 'lint', starts with helm 3.16.0 (default false) @@ -326,6 +326,7 @@ releases: timeout: 60 recreatePods: true force: false + reuseValues: false # set `false` to uninstall this release on sync. (default true) installed: true # restores previous state in case of failed release (default false) diff --git a/pkg/state/state.go b/pkg/state/state.go index 47cdde12..4bf6b457 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -283,6 +283,8 @@ type ReleaseSpec struct { Condition string `yaml:"condition,omitempty"` // CreateNamespace, when set to true (default), --create-namespace is passed to helm3 on install (ignored for helm2) CreateNamespace *bool `yaml:"createNamespace,omitempty"` + // ReuseValues, on helm upgrade/diff, reuse values currently set in the release and merge them with the ones defined within helmfile + ReuseValues *bool `yaml:"reuseValues,omitempty"` // DisableOpenAPIValidation is rarely used to bypass OpenAPI validations only that is used for e.g. // work-around against broken CRs @@ -706,7 +708,7 @@ func (st *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalValu flags = append(flags, "--skip-crds") } - flags = st.appendValuesControlModeFlag(flags, opts.ReuseValues, opts.ResetValues) + flags = st.appendValuesControlModeFlag(flags, opts.ReuseValues, opts.ResetValues, release) if len(errs) > 0 { results <- syncPrepareResult{errors: errs, files: files} @@ -1845,8 +1847,6 @@ func (st *HelmState) commonDiffFlags(detailedExitCode bool, stripTrailingCR bool flags = append(flags, "--output", opt.Output) } - flags = st.appendValuesControlModeFlag(flags, opt.ReuseValues, opt.ResetValues) - if opt.Set != nil { for _, s := range opt.Set { flags = append(flags, "--set", s) @@ -1960,6 +1960,8 @@ func (st *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValu flags = append(flags, "--values", valfile) } + flags = st.appendValuesControlModeFlag(flags, opt.ReuseValues, opt.ResetValues, release) + flags = append(flags, commonDiffFlags...) if len(errs) > 0 { @@ -3019,8 +3021,8 @@ func (st *HelmState) chartOCIFlags(r *ReleaseSpec) []string { return flags } -func (st *HelmState) appendValuesControlModeFlag(flags []string, reuseValues bool, resetValues bool) []string { - if !resetValues && (st.HelmDefaults.ReuseValues || reuseValues) { +func (st *HelmState) appendValuesControlModeFlag(flags []string, reuseValues bool, resetValues bool, release *ReleaseSpec) []string { + if !resetValues && (release.ReuseValues != nil && *release.ReuseValues || release.ReuseValues == nil && st.HelmDefaults.ReuseValues || reuseValues) { flags = append(flags, "--reuse-values") } else { flags = append(flags, "--reset-values") diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 59210312..12faf281 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -4375,3 +4375,202 @@ func TestHelmState_setStringFlags(t *testing.T) { }) } } +func TestPrepareDiffReleases_ValueControlReleaseOverride(t *testing.T) { + tests := []struct { + flags []string + diffOptions *DiffOpts + helmDefaults *HelmSpec + release *ReleaseSpec + }{ + { + flags: []string{"--reuse-values"}, + diffOptions: &DiffOpts{}, + helmDefaults: &HelmSpec{}, + release: &ReleaseSpec{ + Name: "reuse-values-from-release", + ReuseValues: boolValue(true), + }, + }, + { + flags: []string{"--reuse-values"}, + diffOptions: &DiffOpts{ + ReuseValues: true, + }, + helmDefaults: &HelmSpec{}, + release: &ReleaseSpec{ + Name: "reuse-values-from-cli", + ReuseValues: boolValue(false), + }, + }, + { + flags: []string{"--reuse-values"}, + diffOptions: &DiffOpts{ + ReuseValues: true, + }, + helmDefaults: &HelmSpec{ + ReuseValues: true, + }, + release: &ReleaseSpec{ + Name: "reuse-values-all", + ReuseValues: boolValue(true), + }, + }, + { + flags: []string{"--reset-values"}, + diffOptions: &DiffOpts{}, + helmDefaults: &HelmSpec{ + ReuseValues: true, + }, + release: &ReleaseSpec{ + Name: "reset-values-from-helm-defaults", + ReuseValues: boolValue(false), + }, + }, + { + flags: []string{"--reset-values"}, + diffOptions: &DiffOpts{}, + helmDefaults: &HelmSpec{}, + release: &ReleaseSpec{ + Name: "reset-values-from-release", + ReuseValues: boolValue(false), + }, + }, + { + flags: []string{"--reset-values"}, + diffOptions: &DiffOpts{ + ResetValues: true, + }, + helmDefaults: &HelmSpec{}, + release: &ReleaseSpec{ + Name: "reset-values-cli-overrides-release", + ReuseValues: boolValue(true), + }, + }, + } + + for _, tt := range tests { + releases := []ReleaseSpec{ + *tt.release, + } + state := &HelmState{ + ReleaseSetSpec: ReleaseSetSpec{ + Releases: releases, + HelmDefaults: *tt.helmDefaults, + }, + logger: logger, + valsRuntime: valsRuntime, + } + helm := &exectest.Helm{ + Lists: map[exectest.ListKey]string{}, + Helm3: true, + } + results, es := state.prepareDiffReleases(helm, []string{}, 1, false, false, false, []string{}, false, false, false, tt.diffOptions) + + require.Len(t, es, 0) + require.Len(t, results, 1) + + r := results[0] + + require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name) + } +} + +func TestPrepareSyncReleases_ValueControlReleaseOverride(t *testing.T) { + tests := []struct { + flags []string + syncOptions *SyncOpts + helmDefaults *HelmSpec + release *ReleaseSpec + }{ + { + flags: []string{"--reuse-values"}, + syncOptions: &SyncOpts{}, + helmDefaults: &HelmSpec{}, + release: &ReleaseSpec{ + Name: "reuse-values-from-release", + ReuseValues: boolValue(true), + }, + }, + { + flags: []string{"--reuse-values"}, + syncOptions: &SyncOpts{ + ReuseValues: true, + }, + helmDefaults: &HelmSpec{}, + release: &ReleaseSpec{ + Name: "reuse-values-from-cli", + ReuseValues: boolValue(false), + }, + }, + { + flags: []string{"--reuse-values"}, + syncOptions: &SyncOpts{ + ReuseValues: true, + }, + helmDefaults: &HelmSpec{ + ReuseValues: true, + }, + release: &ReleaseSpec{ + Name: "reuse-values-all", + ReuseValues: boolValue(true), + }, + }, + { + flags: []string{"--reset-values"}, + syncOptions: &SyncOpts{}, + helmDefaults: &HelmSpec{ + ReuseValues: true, + }, + release: &ReleaseSpec{ + Name: "reset-values-from-helm-defaults", + ReuseValues: boolValue(false), + }, + }, + { + flags: []string{"--reset-values"}, + syncOptions: &SyncOpts{}, + helmDefaults: &HelmSpec{}, + release: &ReleaseSpec{ + Name: "reset-values-from-release", + ReuseValues: boolValue(false), + }, + }, + { + flags: []string{"--reset-values"}, + syncOptions: &SyncOpts{ + ResetValues: true, + }, + helmDefaults: &HelmSpec{}, + release: &ReleaseSpec{ + Name: "reset-values-cli-overrides-release", + ReuseValues: boolValue(true), + }, + }, + } + + for _, tt := range tests { + releases := []ReleaseSpec{ + *tt.release, + } + state := &HelmState{ + ReleaseSetSpec: ReleaseSetSpec{ + Releases: releases, + HelmDefaults: *tt.helmDefaults, + }, + logger: logger, + valsRuntime: valsRuntime, + } + helm := &exectest.Helm{ + Lists: map[exectest.ListKey]string{}, + Helm3: true, + } + results, es := state.prepareSyncReleases(helm, []string{}, 1, tt.syncOptions) + + require.Len(t, es, 0) + require.Len(t, results, 1) + + r := results[0] + + require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name) + } +}