From a9d59f946a7b4a19904b6c3a48fec085cbd101d3 Mon Sep 17 00:00:00 2001 From: Quentin Devos Date: Tue, 27 Apr 2021 23:17:53 +0200 Subject: [PATCH] feat(helm): add --reset-then-reuse-values flag to 'helm upgrade' When '--reset-then-reuse-values' is used on 'helm upgrade', the chart's values will be reset to the values of the deployed chart while the current release's values will be reused and merged with the values passed as argument (is any). '--reset-values' and '--reuse-values' flags take precedence over `--reset-then-reuse-values', making it ignored if one or the other is also used. Closes #8085, #3957 Signed-off-by: Quentin Devos --- cmd/helm/upgrade.go | 1 + pkg/action/upgrade.go | 11 ++++++++ pkg/action/upgrade_test.go | 53 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 1952b8421..3c28d0fa3 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -179,6 +179,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored") + f.BoolVar(&client.ResetThenReuseValues, "reset-then-reuse-values", false, "when upgrading, reset the values to the ones built into the chart, apply the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' or '--reuse-values' is specified, this is ignored") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used") diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 3b3dd3f1c..a9bd6e881 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -79,6 +79,8 @@ type Upgrade struct { ResetValues bool // ReuseValues will re-use the user's last supplied values. ReuseValues bool + // ResetThenReuseValues will reset the values to the chart's built-ins then merge with user's last supplied values. + ResetThenReuseValues bool // Recreate will (if true) recreate pods after a rollback. Recreate bool // MaxHistory limits the maximum number of revisions saved per release @@ -455,6 +457,15 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newV return newVals, nil } + // If the ResetThenReuseValues flag is set, we use the new chart's values, but we copy the old config's values over the new config's values. + if u.ResetThenReuseValues { + u.cfg.Log("merging values from old release to new values") + + newVals = chartutil.CoalesceTables(newVals, current.Config) + + return newVals, nil + } + if len(newVals) == 0 && len(current.Config) > 0 { u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version) newVals = current.Config diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 5cca7ca1a..8a8feb021 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -277,6 +277,59 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { }) } +func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) { + is := assert.New(t) + + t.Run("reset then reuse values should work with values", func(t *testing.T) { + upAction := upgradeAction(t) + + existingValues := map[string]interface{}{ + "name": "value", + "maxHeapSize": "128m", + "replicas": 2, + } + newValues := map[string]interface{}{ + "name": "newValue", + "maxHeapSize": "512m", + "cpu": "12m", + } + newChartValues := map[string]interface{}{ + "memory": "256m", + } + expectedValues := map[string]interface{}{ + "name": "newValue", + "maxHeapSize": "512m", + "cpu": "12m", + "replicas": 2, + } + + rel := releaseStub() + rel.Name = "nuketown" + rel.Info.Status = release.StatusDeployed + rel.Config = existingValues + + err := upAction.cfg.Releases.Create(rel) + is.NoError(err) + + upAction.ResetThenReuseValues = true + // setting newValues and upgrading + res, err := upAction.Run(rel.Name, buildChart(withValues(newChartValues)), newValues) + is.NoError(err) + + // Now make sure it is actually upgraded + updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2) + is.NoError(err) + + if updatedRes == nil { + is.Fail("Updated Release is nil") + return + } + is.Equal(release.StatusDeployed, updatedRes.Info.Status) + is.Equal(expectedValues, updatedRes.Config) + is.Equal(newChartValues, updatedRes.Chart.Values) + }) +} + func TestUpgradeRelease_Pending(t *testing.T) { req := require.New(t)