fix(helm): Avoid corrupting storage via a lock

If two `helm upgrade`s are executed at the exact same time, then one of
the invocations will fail with "already exists".

If one `helm upgrade` is executed and a second one is started while the
first is in `pending-upgrade`, then the second invocation will create a
new release. Effectively, two helm invocations will simultaneously
change the state of Kubernetes resources -- which is scary -- then two
releases will be in `deployed` state -- which can cause other issues.

This commit fixes the corrupted storage problem, by introducting a poor
person's lock. If the last release is in a pending state, then helm will
abort. If the last release is in a pending state, due to a previously
killed helm, then the user is expected to do `helm rollback`.

Closes #7274

Signed-off-by: Cristian Klein <cristian.klein@elastisys.com>
pull/7322/head
Cristian Klein 5 years ago
parent 298f574606
commit 9a4f4ec64b

@ -60,6 +60,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 longer than 53")
// errPending indicates that another instance of Helm is already applying an operation on a release.
errPending = errors.New("another operation (install/upgrade/rollback) is in progress")
)
// ValidName is a regular expression for resource names.

@ -170,6 +170,11 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
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.
if lastRelease.Info.Status.IsPending() {
return nil, nil, errPending
}
var currentRelease *release.Release
if lastRelease.Info.Status == release.StatusDeployed {
// no need to retrieve the last deployed release from storage as the last release is deployed

@ -42,3 +42,8 @@ const (
)
func (x Status) String() string { return string(x) }
// IsPending determines if this status is a state or a transition.
func (x Status) IsPending() bool {
return x == StatusPendingInstall || x == StatusPendingUpgrade || x == StatusPendingRollback
}

Loading…
Cancel
Save