diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index 68169fe899e17c8f26841bb79a6de9c416e75ca1..900a5236581de200de44eff6a52e301bbdf24c2c 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -65,6 +65,8 @@ var ( errInvalidRevision = errors.New("invalid release revision") //errInvalidName indicates that an invalid release name was provided errInvalidName = errors.New("invalid release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not be longer than 53") + // errPending indicates that Tiller is already applying an operation on a release + errPending = errors.New("another operation (install/upgrade/rollback) is in progress") ) // ListDefaultLimit is the default limit for number of items returned in a list. diff --git a/pkg/tiller/release_update.go b/pkg/tiller/release_update.go index 5fb1552bff1d78bbff4fe960662ef27721374564..c9ac8733411de3164ed518fb3047e320730f1f8d 100644 --- a/pkg/tiller/release_update.go +++ b/pkg/tiller/release_update.go @@ -93,6 +93,14 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele return nil, nil, err } + // Concurrent `helm upgrade`s will either fail here with `errPending` or + // when creating the release with "already exists". This should act as a + // pessimistic lock. + sc := lastRelease.Info.Status.Code + if sc == release.Status_PENDING_INSTALL || sc == release.Status_PENDING_UPGRADE || sc == release.Status_PENDING_ROLLBACK { + return nil, nil, errPending + } + // Increment revision count. This is passed to templates, and also stored on // the release object. revision := lastRelease.Version + 1 diff --git a/pkg/tiller/release_update_test.go b/pkg/tiller/release_update_test.go index a626295c2378b085c4a30134dddb453063de38d2..0346ba18091684d2b75e6ae603ad5da578c3b430 100644 --- a/pkg/tiller/release_update_test.go +++ b/pkg/tiller/release_update_test.go @@ -104,6 +104,31 @@ func TestUpdateRelease(t *testing.T) { t.Errorf("Expected description %q, got %q", edesc, got) } } +func TestUpdateReleasePendingError(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + rel2 := releaseStub() + rel2.Info.Status.Code = release.Status_PENDING_UPGRADE + rel2.Version = 2 + rs.env.Releases.Create(rel2) + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, + }, + }, + } + _, err := rs.UpdateRelease(c, req) + if err == nil { + t.Fatalf("Expected failure to update") + } +} func TestUpdateRelease_ResetValues(t *testing.T) { c := helm.NewContext() rs := rsFixture()