diff --git a/cmd/helm/rollback.go b/cmd/helm/rollback.go index 9c97a4abd..c6fd0993b 100644 --- a/cmd/helm/rollback.go +++ b/cmd/helm/rollback.go @@ -19,6 +19,7 @@ package main import ( "fmt" "io" + "strconv" "time" "github.com/spf13/cobra" @@ -36,7 +37,7 @@ second is a revision (version) number. To see revision numbers, run ` func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { - client := action.NewRollback(cfg) + client := action.NewUpgrade(cfg) cmd := &cobra.Command{ Use: "rollback [RELEASE] [REVISION]", @@ -44,7 +45,22 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: rollbackDesc, Args: require.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - _, err := client.Run(args[0]) + + version, err := strconv.Atoi(args[1]) + if err != nil { + return fmt.Errorf("could not convert revision to a number: %v", err) + } + + releaseToRollbackTo, err := cfg.Releases.Get(args[0], version) + if err != nil { + return err + } + + if err := client.ValueOptions.MergeValues(settings); err != nil { + return err + } + + _, err = client.Run(args[0], releaseToRollbackTo.Chart) if err != nil { return err } @@ -56,13 +72,15 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } f := cmd.Flags() - f.IntVar(&client.Version, "version", 0, "revision number to rollback to (default: rollback to previous release)") f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback") f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&client.Force, "force", false, "force resource update through delete/recreate if needed") - f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during rollback") + f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks") 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.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") + f.IntVar(&client.MaxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.") return cmd } diff --git a/cmd/helm/rollback_test.go b/cmd/helm/rollback_test.go index 6283f6f20..bd593be32 100644 --- a/cmd/helm/rollback_test.go +++ b/cmd/helm/rollback_test.go @@ -24,42 +24,50 @@ import ( ) func TestRollbackCmd(t *testing.T) { - rels := []*release.Release{ - { - Name: "funny-honey", - Info: &release.Info{Status: release.StatusSuperseded}, - Chart: &chart.Chart{}, - Version: 1, - }, + + relMock := func(n string, v int, ch *chart.Chart) *release.Release { + return release.Mock(&release.MockReleaseOptions{Name: n, Version: v, Chart: ch}) + } + + ch := &chart.Chart{ + Metadata: &chart.Metadata{}, + } + + tests := []cmdTestCase{ { - Name: "funny-honey", - Info: &release.Info{Status: release.StatusDeployed}, - Chart: &chart.Chart{}, - Version: 2, + name: "rollback a release", + cmd: "rollback funny-honey 1", + golden: "output/rollback.txt", + rels: []*release.Release{ + relMock("funny-honey", 1, ch), + relMock("funny-honey", 2, ch), + }, + }, { + name: "rollback a release with timeout", + cmd: "rollback funny-honey 1 --timeout 120s", + golden: "output/rollback-timeout.txt", + rels: []*release.Release{ + relMock("funny-honey", 1, ch), + relMock("funny-honey", 2, ch), + }, + }, { + name: "rollback a release with wait", + cmd: "rollback funny-honey 1 --wait", + golden: "output/rollback-wait.txt", + rels: []*release.Release{ + relMock("funny-honey", 1, ch), + relMock("funny-honey", 2, ch), + }, + }, { + name: "rollback a release without revision", + cmd: "rollback funny-honey", + golden: "output/rollback-no-args.txt", + rels: []*release.Release{ + relMock("funny-honey", 1, ch), + relMock("funny-honey", 2, ch), + }, + wantError: true, }, } - - tests := []cmdTestCase{{ - name: "rollback a release", - cmd: "rollback funny-honey 1", - golden: "output/rollback.txt", - rels: rels, - }, { - name: "rollback a release with timeout", - cmd: "rollback funny-honey 1 --timeout 120s", - golden: "output/rollback-timeout.txt", - rels: rels, - }, { - name: "rollback a release with wait", - cmd: "rollback funny-honey 1 --wait", - golden: "output/rollback-wait.txt", - rels: rels, - }, { - name: "rollback a release without revision", - cmd: "rollback funny-honey", - golden: "output/rollback-no-args.txt", - rels: rels, - wantError: true, - }} runTestCmd(t, tests) } diff --git a/docs/charts_hooks.md b/docs/charts_hooks.md index 1284b9c2f..e75098151 100644 --- a/docs/charts_hooks.md +++ b/docs/charts_hooks.md @@ -32,10 +32,6 @@ The following hooks are defined: before a Kubernetes apply operation). - post-upgrade: Executes on an upgrade after all resources have been upgraded. -- pre-rollback: Executes on a rollback request after templates are - rendered, but before any resources have been rolled back. -- post-rollback: Executes on a rollback request after all resources - have been modified. ## Hooks and the Release Lifecycle diff --git a/pkg/action/install.go b/pkg/action/install.go index 290362ac1..097c93a1f 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -764,3 +764,12 @@ func readFile(filePath string, settings cli.EnvSettings) ([]byte, error) { data, err := getter.Get(filePath) return data.Bytes(), err } + +// deleteHookByPolicy deletes a hook if the hook policy instructs it to +func deleteHookByPolicy(cfg *Configuration, h *release.Hook, policy string) error { + if hookHasDeletePolicy(h, policy) { + b := bytes.NewBufferString(h.Manifest) + return cfg.KubeClient.Delete(b) + } + return nil +} diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go deleted file mode 100644 index 2db6ed7a9..000000000 --- a/pkg/action/rollback.go +++ /dev/null @@ -1,253 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package action - -import ( - "bytes" - "fmt" - "sort" - "time" - - "github.com/pkg/errors" - - "helm.sh/helm/pkg/hooks" - "helm.sh/helm/pkg/release" -) - -// Rollback is the action for rolling back to a given release. -// -// It provides the implementation of 'helm rollback'. -type Rollback struct { - cfg *Configuration - - Version int - Timeout time.Duration - Wait bool - DisableHooks bool - DryRun bool - Recreate bool // will (if true) recreate pods after a rollback. - Force bool // will (if true) force resource upgrade through uninstall/recreate if needed -} - -// NewRollback creates a new Rollback object with the given configuration. -func NewRollback(cfg *Configuration) *Rollback { - return &Rollback{ - cfg: cfg, - } -} - -// Run executes 'helm rollback' against the given release. -func (r *Rollback) Run(name string) (*release.Release, error) { - r.cfg.Log("preparing rollback of %s", name) - currentRelease, targetRelease, err := r.prepareRollback(name) - if err != nil { - return nil, err - } - - if !r.DryRun { - r.cfg.Log("creating rolled back release for %s", name) - if err := r.cfg.Releases.Create(targetRelease); err != nil { - return nil, err - } - } - r.cfg.Log("performing rollback of %s", name) - res, err := r.performRollback(currentRelease, targetRelease) - if err != nil { - return res, err - } - - if !r.DryRun { - r.cfg.Log("updating status for rolled back release for %s", name) - if err := r.cfg.Releases.Update(targetRelease); err != nil { - return res, err - } - } - - return res, nil -} - -// prepareRollback finds the previous release and prepares a new release object with -// the previous release's configuration -func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, error) { - if err := validateReleaseName(name); err != nil { - return nil, nil, errors.Errorf("prepareRollback: Release name is invalid: %s", name) - } - - if r.Version < 0 { - return nil, nil, errInvalidRevision - } - - currentRelease, err := r.cfg.Releases.Last(name) - if err != nil { - return nil, nil, err - } - - previousVersion := r.Version - if r.Version == 0 { - previousVersion = currentRelease.Version - 1 - } - - r.cfg.Log("rolling back %s (current: v%d, target: v%d)", name, currentRelease.Version, previousVersion) - - previousRelease, err := r.cfg.Releases.Get(name, previousVersion) - if err != nil { - return nil, nil, err - } - - // Store a new release object with previous release's configuration - targetRelease := &release.Release{ - Name: name, - Namespace: currentRelease.Namespace, - Chart: previousRelease.Chart, - Config: previousRelease.Config, - Info: &release.Info{ - FirstDeployed: currentRelease.Info.FirstDeployed, - LastDeployed: time.Now(), - Status: release.StatusPendingRollback, - Notes: previousRelease.Info.Notes, - // Because we lose the reference to previous version elsewhere, we set the - // message here, and only override it later if we experience failure. - Description: fmt.Sprintf("Rollback to %d", previousVersion), - }, - Version: currentRelease.Version + 1, - Manifest: previousRelease.Manifest, - Hooks: previousRelease.Hooks, - } - - return currentRelease, targetRelease, nil -} - -func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release) (*release.Release, error) { - - if r.DryRun { - r.cfg.Log("dry run for %s", targetRelease.Name) - return targetRelease, nil - } - - // pre-rollback hooks - if !r.DisableHooks { - if err := r.execHook(targetRelease.Hooks, hooks.PreRollback); err != nil { - return targetRelease, err - } - } else { - r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name) - } - - cr := bytes.NewBufferString(currentRelease.Manifest) - tr := bytes.NewBufferString(targetRelease.Manifest) - - if err := r.cfg.KubeClient.Update(cr, tr, r.Force, r.Recreate); err != nil { - msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) - r.cfg.Log("warning: %s", msg) - currentRelease.Info.Status = release.StatusSuperseded - targetRelease.Info.Status = release.StatusFailed - targetRelease.Info.Description = msg - r.cfg.recordRelease(currentRelease) - r.cfg.recordRelease(targetRelease) - return targetRelease, err - } - - if r.Wait { - buf := bytes.NewBufferString(targetRelease.Manifest) - if err := r.cfg.KubeClient.Wait(buf, r.Timeout); err != nil { - targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) - r.cfg.recordRelease(currentRelease) - r.cfg.recordRelease(targetRelease) - return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name) - } - } - - // post-rollback hooks - if !r.DisableHooks { - if err := r.execHook(targetRelease.Hooks, hooks.PostRollback); err != nil { - return targetRelease, err - } - } - - deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) - if err != nil { - return nil, err - } - // Supersede all previous deployments, see issue #2941. - for _, rel := range deployed { - r.cfg.Log("superseding previous deployment %d", rel.Version) - rel.Info.Status = release.StatusSuperseded - r.cfg.recordRelease(rel) - } - - targetRelease.Info.Status = release.StatusDeployed - - return targetRelease, nil -} - -// execHook executes all of the hooks for the given hook event. -func (r *Rollback) execHook(hs []*release.Hook, hook string) error { - timeout := r.Timeout - executingHooks := []*release.Hook{} - - for _, h := range hs { - for _, e := range h.Events { - if string(e) == hook { - executingHooks = append(executingHooks, h) - } - } - } - - sort.Sort(hookByWeight(executingHooks)) - - for _, h := range executingHooks { - if err := deleteHookByPolicy(r.cfg, h, hooks.BeforeHookCreation); err != nil { - return err - } - - b := bytes.NewBufferString(h.Manifest) - if err := r.cfg.KubeClient.Create(b); err != nil { - return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path) - } - b.Reset() - b.WriteString(h.Manifest) - - if err := r.cfg.KubeClient.WatchUntilReady(b, timeout); err != nil { - // If a hook is failed, checkout the annotation of the hook to determine whether the hook should be deleted - // under failed condition. If so, then clear the corresponding resource object in the hook - if err := deleteHookByPolicy(r.cfg, h, hooks.HookFailed); err != nil { - return err - } - return err - } - } - - // If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted - // under succeeded condition. If so, then clear the corresponding resource object in each hook - for _, h := range executingHooks { - if err := deleteHookByPolicy(r.cfg, h, hooks.HookSucceeded); err != nil { - return err - } - h.LastRun = time.Now() - } - - return nil -} - -// deleteHookByPolicy deletes a hook if the hook policy instructs it to -func deleteHookByPolicy(cfg *Configuration, h *release.Hook, policy string) error { - if hookHasDeletePolicy(h, policy) { - b := bytes.NewBufferString(h.Manifest) - return cfg.KubeClient.Delete(b) - } - return nil -} diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go index 6b6c6fdcc..3688d1be1 100644 --- a/pkg/hooks/hooks.go +++ b/pkg/hooks/hooks.go @@ -37,8 +37,6 @@ const ( PostDelete = "post-delete" PreUpgrade = "pre-upgrade" PostUpgrade = "post-upgrade" - PreRollback = "pre-rollback" - PostRollback = "post-rollback" ReleaseTestSuccess = "test-success" ReleaseTestFailure = "test-failure" ) diff --git a/pkg/release/hook.go b/pkg/release/hook.go index d4cb73d54..f402ff036 100644 --- a/pkg/release/hook.go +++ b/pkg/release/hook.go @@ -28,8 +28,6 @@ const ( HookPostDelete HookEvent = "post-delete" HookPreUpgrade HookEvent = "pre-upgrade" HookPostUpgrade HookEvent = "post-upgrade" - HookPreRollback HookEvent = "pre-rollback" - HookPostRollback HookEvent = "post-rollback" HookReleaseTestSuccess HookEvent = "release-test-success" HookReleaseTestFailure HookEvent = "release-test-failure" ) diff --git a/pkg/releaseutil/manifest_sorter.go b/pkg/releaseutil/manifest_sorter.go index 41e501c9f..d4a9eec57 100644 --- a/pkg/releaseutil/manifest_sorter.go +++ b/pkg/releaseutil/manifest_sorter.go @@ -59,8 +59,6 @@ var events = map[string]release.HookEvent{ hooks.PostDelete: release.HookPostDelete, hooks.PreUpgrade: release.HookPreUpgrade, hooks.PostUpgrade: release.HookPostUpgrade, - hooks.PreRollback: release.HookPreRollback, - hooks.PostRollback: release.HookPostRollback, hooks.ReleaseTestSuccess: release.HookReleaseTestSuccess, hooks.ReleaseTestFailure: release.HookReleaseTestFailure, }