diff --git a/pkg/action/install.go b/pkg/action/install.go index b46b4446b..a7fccb1c8 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -81,21 +81,22 @@ type Install struct { DryRunOption string // HideSecret can be set to true when DryRun is enabled in order to hide // Kubernetes Secrets in the output. It cannot be used outside of DryRun. - HideSecret bool - DisableHooks bool - Replace bool - WaitStrategy kube.WaitStrategy - WaitForJobs bool - Devel bool - DependencyUpdate bool - Timeout time.Duration - Namespace string - ReleaseName string - GenerateName bool - NameTemplate string - Description string - OutputDir string - Atomic bool + HideSecret bool + DisableHooks bool + Replace bool + WaitStrategy kube.WaitStrategy + WaitForJobs bool + Devel bool + DependencyUpdate bool + Timeout time.Duration + Namespace string + ReleaseName string + GenerateName bool + NameTemplate string + Description string + OutputDir string + // RollbackOnFailure enables rolling back (uninstalling) the release on failure if set + RollbackOnFailure bool SkipCRDs bool SubNotes bool HideNotes bool @@ -298,9 +299,9 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma slog.Debug("API Version list given outside of client only mode, this list will be ignored") } - // Make sure if Atomic is set, that wait is set as well. This makes it so + // Make sure if RollbackOnFailure is set, that wait is set as well. This makes it so // the user doesn't have to specify both - if i.WaitStrategy == kube.HookOnlyStrategy && i.Atomic { + if i.WaitStrategy == kube.HookOnlyStrategy && i.RollbackOnFailure { i.WaitStrategy = kube.StatusWatcherStrategy } @@ -530,8 +531,8 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) { rel.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error())) - if i.Atomic { - slog.Debug("install failed, uninstalling release", "release", i.ReleaseName) + if i.RollbackOnFailure { + slog.Debug("install failed and rollback-on-failure is set, uninstalling release", "release", i.ReleaseName) uninstall := NewUninstall(i.cfg) uninstall.DisableHooks = i.DisableHooks uninstall.KeepHistory = false @@ -539,7 +540,7 @@ func (i *Install) failRelease(rel *release.Release, err error) (*release.Release if _, uninstallErr := uninstall.Run(i.ReleaseName); uninstallErr != nil { return rel, fmt.Errorf("an error occurred while uninstalling the release. original install error: %w: %w", err, uninstallErr) } - return rel, fmt.Errorf("release %s failed, and has been uninstalled due to atomic being set: %w", i.ReleaseName, err) + return rel, fmt.Errorf("release %s failed, and has been uninstalled due to rollback-on-failure being set: %w", i.ReleaseName, err) } i.recordRelease(rel) // Ignore the error, since we have another error to deal with. return rel, err diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 424ee6135..f567b3df4 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -590,16 +590,16 @@ func TestInstallRelease_WaitForJobs(t *testing.T) { is.Equal(res.Info.Status, release.StatusFailed) } -func TestInstallRelease_Atomic(t *testing.T) { +func TestInstallRelease_RollbackOnFailure(t *testing.T) { is := assert.New(t) - t.Run("atomic uninstall succeeds", func(t *testing.T) { + t.Run("rollback-on-failure uninstall succeeds", func(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "come-fail-away" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") instAction.cfg.KubeClient = failer - instAction.Atomic = true + instAction.RollbackOnFailure = true // disabling hooks to avoid an early fail when // WaitForDelete is called on the pre-delete hook execution instAction.DisableHooks = true @@ -608,7 +608,7 @@ func TestInstallRelease_Atomic(t *testing.T) { res, err := instAction.Run(buildChart(), vals) is.Error(err) is.Contains(err.Error(), "I timed out") - is.Contains(err.Error(), "atomic") + is.Contains(err.Error(), "rollback-on-failure") // Now make sure it isn't in storage anymore _, err = instAction.cfg.Releases.Get(res.Name, res.Version) @@ -616,14 +616,14 @@ func TestInstallRelease_Atomic(t *testing.T) { is.Equal(err, driver.ErrReleaseNotFound) }) - t.Run("atomic uninstall fails", func(t *testing.T) { + t.Run("rollback-on-failure uninstall fails", func(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "come-fail-away-with-me" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") failer.DeleteError = fmt.Errorf("uninstall fail") instAction.cfg.KubeClient = failer - instAction.Atomic = true + instAction.RollbackOnFailure = true vals := map[string]interface{}{} _, err := instAction.Run(buildChart(), vals) @@ -633,7 +633,7 @@ func TestInstallRelease_Atomic(t *testing.T) { is.Contains(err.Error(), "an error occurred while uninstalling the release") }) } -func TestInstallRelease_Atomic_Interrupted(t *testing.T) { +func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { is := assert.New(t) instAction := installAction(t) @@ -641,7 +641,7 @@ func TestInstallRelease_Atomic_Interrupted(t *testing.T) { failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitDuration = 10 * time.Second instAction.cfg.KubeClient = failer - instAction.Atomic = true + instAction.RollbackOnFailure = true vals := map[string]interface{}{} ctx, cancel := context.WithCancel(t.Context()) @@ -652,7 +652,7 @@ func TestInstallRelease_Atomic_Interrupted(t *testing.T) { res, err := instAction.RunWithContext(ctx, buildChart(), vals) is.Error(err) is.Contains(err.Error(), "context canceled") - is.Contains(err.Error(), "atomic") + is.Contains(err.Error(), "rollback-on-failure") is.Contains(err.Error(), "uninstalled") // Now make sure it isn't in storage anymore diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index abf4342d3..065fa804e 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -89,8 +89,8 @@ type Upgrade struct { ResetThenReuseValues bool // MaxHistory limits the maximum number of revisions saved per release MaxHistory int - // Atomic, if true, will roll back on failure. - Atomic bool + // RollbackOnFailure enables rolling back the upgraded release on failure + RollbackOnFailure bool // CleanupOnFail will, if true, cause the upgrade to delete newly-created resources on a failed update. CleanupOnFail bool // SubNotes determines whether sub-notes are rendered in the chart. @@ -151,9 +151,9 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart. return nil, err } - // Make sure if Atomic is set, that wait is set as well. This makes it so + // Make sure wait is set if RollbackOnFailure. This makes it so // the user doesn't have to specify both - if u.WaitStrategy == kube.HookOnlyStrategy && u.Atomic { + if u.WaitStrategy == kube.HookOnlyStrategy && u.RollbackOnFailure { u.WaitStrategy = kube.StatusWatcherStrategy } @@ -390,7 +390,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR } } -// Function used to lock the Mutex, this is important for the case when the atomic flag is set. +// Function used to lock the Mutex, this is important for the case when RollbackOnFailure is set. // In that case the upgrade will finish before the rollback is finished so it is necessary to wait for the rollback to finish. // The rollback will be trigger by the function failRelease func (u *Upgrade) reportToPerformUpgrade(c chan<- resultMessage, rel *release.Release, created kube.ResourceList, err error) { @@ -408,7 +408,7 @@ func (u *Upgrade) handleContext(ctx context.Context, done chan interface{}, c ch case <-ctx.Done(): err := ctx.Err() - // when the atomic flag is set the ongoing release finish first and doesn't give time for the rollback happens. + // when RollbackOnFailure is set, the ongoing release finish first and doesn't give time for the rollback happens. u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, err) case <-done: return @@ -499,8 +499,9 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e } slog.Debug("resource cleanup complete") } - if u.Atomic { - slog.Debug("upgrade failed and atomic is set, rolling back to last successful release") + + if u.RollbackOnFailure { + slog.Debug("Upgrade failed and rollback-on-failure is set, rolling back to previous successful release") // As a protection, get the last successful release before rollback. // If there are no successful releases, bail out @@ -534,7 +535,7 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e if rollErr := rollin.Run(rel.Name); rollErr != nil { return rel, fmt.Errorf("an error occurred while rolling back the release. original upgrade error: %w: %w", err, rollErr) } - return rel, fmt.Errorf("release %s failed, and has been rolled back due to atomic being set: %w", rel.Name, err) + return rel, fmt.Errorf("release %s failed, and has been rolled back due to rollback-on-failure being set: %w", rel.Name, err) } return rel, err diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index e20955560..8ec727671 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -141,11 +141,11 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) { is.Equal(res.Info.Status, release.StatusFailed) } -func TestUpgradeRelease_Atomic(t *testing.T) { +func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { is := assert.New(t) req := require.New(t) - t.Run("atomic rollback succeeds", func(t *testing.T) { + t.Run("rollback-on-failure rollback succeeds", func(t *testing.T) { upAction := upgradeAction(t) rel := releaseStub() @@ -157,13 +157,13 @@ func TestUpgradeRelease_Atomic(t *testing.T) { // We can't make Update error because then the rollback won't work failer.WatchUntilReadyError = fmt.Errorf("arming key removed") upAction.cfg.KubeClient = failer - upAction.Atomic = true + upAction.RollbackOnFailure = true vals := map[string]interface{}{} res, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) is.Contains(err.Error(), "arming key removed") - is.Contains(err.Error(), "atomic") + is.Contains(err.Error(), "rollback-on-failure") // Now make sure it is actually upgraded updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3) @@ -172,7 +172,7 @@ func TestUpgradeRelease_Atomic(t *testing.T) { is.Equal(updatedRes.Info.Status, release.StatusDeployed) }) - t.Run("atomic uninstall fails", func(t *testing.T) { + t.Run("rollback-on-failure uninstall fails", func(t *testing.T) { upAction := upgradeAction(t) rel := releaseStub() rel.Name = "fallout" @@ -182,7 +182,7 @@ func TestUpgradeRelease_Atomic(t *testing.T) { failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.UpdateError = fmt.Errorf("update fail") upAction.cfg.KubeClient = failer - upAction.Atomic = true + upAction.RollbackOnFailure = true vals := map[string]interface{}{} _, err := upAction.Run(rel.Name, buildChart(), vals) @@ -409,7 +409,8 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { is.Equal(res.Info.Status, release.StatusFailed) } -func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) { +func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) { + is := assert.New(t) req := require.New(t) @@ -422,7 +423,7 @@ func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) { failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitDuration = 5 * time.Second upAction.cfg.KubeClient = failer - upAction.Atomic = true + upAction.RollbackOnFailure = true vals := map[string]interface{}{} ctx, cancel := context.WithCancel(t.Context()) @@ -431,7 +432,7 @@ func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) { res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) req.Error(err) - is.Contains(err.Error(), "release interrupted-release failed, and has been rolled back due to atomic being set: context canceled") + is.Contains(err.Error(), "release interrupted-release failed, and has been rolled back due to rollback-on-failure being set: context canceled") // Now make sure it is actually upgraded updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3) diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index d53b1d981..f09d8c78b 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -206,7 +206,8 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema") - f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically to \"watcher\" if --atomic is used") + f.BoolVar(&client.RollbackOnFailure, "rollback-on-failure", false, "if set, Helm will rollback (uninstall) the installation upon failure. The --wait flag will be default to \"watcher\" if --rollback-on-failure is set") + f.MarkDeprecated("atomic", "use --rollback-on-failure instead") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index c3288286b..54d9989b8 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -140,7 +140,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.WaitForJobs = client.WaitForJobs instClient.Devel = client.Devel instClient.Namespace = client.Namespace - instClient.Atomic = client.Atomic + instClient.RollbackOnFailure = client.RollbackOnFailure instClient.PostRenderer = client.PostRenderer instClient.DisableOpenAPIValidation = client.DisableOpenAPIValidation instClient.SubNotes = client.SubNotes @@ -281,7 +281,9 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { 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.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 to \"watcher\" if --atomic is used") + f.BoolVar(&client.RollbackOnFailure, "rollback-on-failure", false, "if set, Helm will rollback the upgrade to previous success release upon failure. The --wait flag will be defaulted to \"watcher\" if --rollback-on-failure is set") + f.BoolVar(&client.RollbackOnFailure, "atomic", false, "deprecated") + f.MarkDeprecated("atomic", "use --rollback-on-failure instead") f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")