From 4d84397a95116bcffc77d9f9bdacecad4dada1c5 Mon Sep 17 00:00:00 2001 From: Brent Date: Tue, 23 Oct 2018 14:26:42 -0400 Subject: [PATCH] fix(tiller): rollback deleted release (#3722) Solves #3722 by making the changes in #3539 more compatible with the previous behavior. This gives a recovery option for "oops I deleted my helm release" by allowing rollback, which is intended to be a working feature of helm. Note that purging releases removes the history required to rollback, so this doesn't work in that case. However, purging takes significantly more time, so it's harder to accidentally purge everything. Signed-off-by: Brent --- pkg/storage/storage.go | 8 ++-- pkg/tiller/release_rollback.go | 11 ++++- pkg/tiller/release_rollback_test.go | 68 ++++++++++++++++++++++++++++- 3 files changed, 81 insertions(+), 6 deletions(-) diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 40fc558a1..6d5f589b9 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -25,6 +25,8 @@ import ( "k8s.io/helm/pkg/storage/driver" ) +const NoReleasesErr = "has no deployed releases" + // Storage represents a storage engine for a Release. type Storage struct { driver.Driver @@ -124,13 +126,13 @@ func (s *Storage) Deployed(name string) (*rspb.Release, error) { ls, err := s.DeployedAll(name) if err != nil { if strings.Contains(err.Error(), "not found") { - return nil, fmt.Errorf("%q has no deployed releases", name) + return nil, fmt.Errorf("%q %s", name, NoReleasesErr) } return nil, err } if len(ls) == 0 { - return nil, fmt.Errorf("%q has no deployed releases", name) + return nil, fmt.Errorf("%q %s", name, NoReleasesErr) } return ls[0], err @@ -150,7 +152,7 @@ func (s *Storage) DeployedAll(name string) ([]*rspb.Release, error) { return ls, nil } if strings.Contains(err.Error(), "not found") { - return nil, fmt.Errorf("%q has no deployed releases", name) + return nil, fmt.Errorf("%q %s", name, NoReleasesErr) } return nil, err } diff --git a/pkg/tiller/release_rollback.go b/pkg/tiller/release_rollback.go index 75e282fb8..1f72c3256 100644 --- a/pkg/tiller/release_rollback.go +++ b/pkg/tiller/release_rollback.go @@ -18,6 +18,8 @@ package tiller import ( "fmt" + "k8s.io/helm/pkg/storage" + "strings" ctx "golang.org/x/net/context" @@ -151,11 +153,16 @@ func (s *ReleaseServer) performRollback(currentRelease, targetRelease *release.R } } + // update the current release + s.Log("superseding previous deployment %d", currentRelease.Version) + currentRelease.Info.Status.Code = release.Status_SUPERSEDED + s.recordRelease(currentRelease, true) + + // Supersede all previous deployments, see issue #2941. deployed, err := s.env.Releases.DeployedAll(currentRelease.Name) - if err != nil { + if err != nil && !strings.Contains(err.Error(), storage.NoReleasesErr) { return nil, err } - // Supersede all previous deployments, see issue #2941. for _, r := range deployed { s.Log("superseding previous deployment %d", r.Version) r.Info.Status.Code = release.Status_SUPERSEDED diff --git a/pkg/tiller/release_rollback_test.go b/pkg/tiller/release_rollback_test.go index d7909ed8b..6aa895a63 100644 --- a/pkg/tiller/release_rollback_test.go +++ b/pkg/tiller/release_rollback_test.go @@ -169,7 +169,7 @@ func TestRollbackWithReleaseVersion(t *testing.T) { // check that v2 is now in a SUPERSEDED state oldRel, err := rs.env.Releases.Get(rel.Name, 2) if err != nil { - t.Fatalf("Failed to retrieve v1: %s", err) + t.Fatalf("Failed to retrieve v2: %s", err) } if oldRel.Info.Status.Code != release.Status_SUPERSEDED { t.Errorf("Expected v2 to be in a SUPERSEDED state, got %q", oldRel.Info.Status.Code) @@ -184,6 +184,72 @@ func TestRollbackWithReleaseVersion(t *testing.T) { } } +func TestRollbackDeleted(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rs.Log = t.Logf + rs.env.Releases.Log = t.Logf + rel2 := releaseStub() + rel2.Name = "other" + rs.env.Releases.Create(rel2) + rel := releaseStub() + rs.env.Releases.Create(rel) + v2 := upgradeReleaseVersion(rel) + rs.env.Releases.Update(rel) + rs.env.Releases.Create(v2) + v3 := upgradeReleaseVersion(v2) + // retain the original release as DEPLOYED while the update should fail + v2.Info.Status.Code = release.Status_DEPLOYED + v3.Info.Status.Code = release.Status_FAILED + rs.env.Releases.Update(v2) + rs.env.Releases.Create(v3) + + req1 := &services.UninstallReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + } + + _, err := rs.UninstallRelease(c, req1) + if err != nil { + t.Fatalf("Failed uninstall: %s", err) + } + + oldRel, err := rs.env.Releases.Get(rel.Name, 3) + if err != nil { + t.Fatalf("Failed to retrieve v3: %s", err) + } + if oldRel.Info.Status.Code != release.Status_DELETED { + t.Errorf("Expected v3 to be in a DELETED state, got %q", oldRel.Info.Status.Code) + } + + req2 := &services.RollbackReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + Version: 2, + } + + _, err = rs.RollbackRelease(c, req2) + if err != nil { + t.Fatalf("Failed rollback: %s", err) + } + // check that v3 is now in a SUPERSEDED state + oldRel, err = rs.env.Releases.Get(rel.Name, 3) + if err != nil { + t.Fatalf("Failed to retrieve v3: %s", err) + } + if oldRel.Info.Status.Code != release.Status_SUPERSEDED { + t.Errorf("Expected v3 to be in a SUPERSEDED state, got %q", oldRel.Info.Status.Code) + } + // make sure we didn't update some other deployments. + otherRel, err := rs.env.Releases.Get(rel2.Name, 1) + if err != nil { + t.Fatalf("Failed to retrieve other v1: %s", err) + } + if otherRel.Info.Status.Code != release.Status_DEPLOYED { + t.Errorf("Expected other deployed release to stay untouched, got %q", otherRel.Info.Status.Code) + } +} + func TestRollbackReleaseNoHooks(t *testing.T) { c := helm.NewContext() rs := rsFixture()