Implement "atomic" upgrade/install

Add possibility to put "--safe" argument to install and
upgrade command that restores the state of cluster in
case of failed install/upgrade attempt

Signed-off-by: Alexander Nesterenko <nestorf250@gmail.com>
pull/5143/head
Alexander Nesterenko 6 years ago
parent 0dfe2b865a
commit 8a5c7f1571

@ -131,6 +131,7 @@ type installCmd struct {
version string version string
timeout int64 timeout int64
wait bool wait bool
safe bool
repoURL string repoURL string
username string username string
password string password string
@ -190,6 +191,8 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
} }
inst.chartPath = cp inst.chartPath = cp
inst.client = ensureHelmClient(inst.client) inst.client = ensureHelmClient(inst.client)
inst.wait = inst.wait || inst.safe
return inst.run() return inst.run()
}, },
} }
@ -212,6 +215,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&inst.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed") f.StringVar(&inst.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed")
f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&inst.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.BoolVar(&inst.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.BoolVar(&inst.safe, "safe", false, "if set, upgrade process rolls back changes made in case of failed upgrade")
f.StringVar(&inst.repoURL, "repo", "", "chart repository url where to locate the requested chart") f.StringVar(&inst.repoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&inst.username, "username", "", "chart repository username where to locate the requested chart") f.StringVar(&inst.username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&inst.password, "password", "", "chart repository password where to locate the requested chart") f.StringVar(&inst.password, "password", "", "chart repository password where to locate the requested chart")
@ -307,6 +311,20 @@ func (i *installCmd) run() error {
helm.InstallWait(i.wait), helm.InstallWait(i.wait),
helm.InstallDescription(i.description)) helm.InstallDescription(i.description))
if err != nil { if err != nil {
if i.safe {
fmt.Fprintf(os.Stdout, "INSTALL FAILED\nPURGING CHART\nError: %v", prettyError(err))
deleteSideEffects := &deleteCmd{
name: i.name,
disableHooks: i.disableHooks,
purge: true,
timeout: i.timeout,
description: "",
dryRun: i.dryRun,
out: i.out,
client: i.client,
}
if err := deleteSideEffects.run(); err != nil { return err }
}
return prettyError(err) return prettyError(err)
} }

@ -105,6 +105,7 @@ type upgradeCmd struct {
resetValues bool resetValues bool
reuseValues bool reuseValues bool
wait bool wait bool
safe bool
repoURL string repoURL string
username string username string
password string password string
@ -142,6 +143,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
upgrade.release = args[0] upgrade.release = args[0]
upgrade.chart = args[1] upgrade.chart = args[1]
upgrade.client = ensureHelmClient(upgrade.client) upgrade.client = ensureHelmClient(upgrade.client)
upgrade.wait = upgrade.wait || upgrade.safe
return upgrade.run() return upgrade.run()
}, },
@ -167,6 +169,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart")
f.BoolVar(&upgrade.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(&upgrade.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(&upgrade.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.BoolVar(&upgrade.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.BoolVar(&upgrade.safe, "safe", false, "if set, upgrade process rolls back changes made in case of failed upgrade")
f.StringVar(&upgrade.repoURL, "repo", "", "chart repository url where to locate the requested chart") f.StringVar(&upgrade.repoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&upgrade.username, "username", "", "chart repository username where to locate the requested chart") f.StringVar(&upgrade.username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&upgrade.password, "password", "", "chart repository password where to locate the requested chart") f.StringVar(&upgrade.password, "password", "", "chart repository password where to locate the requested chart")
@ -191,6 +194,8 @@ func (u *upgradeCmd) run() error {
return err return err
} }
releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1))
if u.install { if u.install {
// If a release does not exist, install it. If another error occurs during // If a release does not exist, install it. If another error occurs during
// the check, ignore the error and continue with the upgrade. // the check, ignore the error and continue with the upgrade.
@ -198,7 +203,6 @@ func (u *upgradeCmd) run() error {
// The returned error is a grpc.rpcError that wraps the message from the original error. // The returned error is a grpc.rpcError that wraps the message from the original error.
// So we're stuck doing string matching against the wrapped error, which is nested somewhere // So we're stuck doing string matching against the wrapped error, which is nested somewhere
// inside of the grpc.rpcError message. // inside of the grpc.rpcError message.
releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1))
if err == nil { if err == nil {
if u.namespace == "" { if u.namespace == "" {
@ -232,6 +236,7 @@ func (u *upgradeCmd) run() error {
timeout: u.timeout, timeout: u.timeout,
wait: u.wait, wait: u.wait,
description: u.description, description: u.description,
safe: u.safe,
} }
return ic.run() return ic.run()
} }
@ -270,7 +275,24 @@ func (u *upgradeCmd) run() error {
helm.UpgradeWait(u.wait), helm.UpgradeWait(u.wait),
helm.UpgradeDescription(u.description)) helm.UpgradeDescription(u.description))
if err != nil { if err != nil {
return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err)) fmt.Fprintf(u.out, "UPGRADE FAILED\nROLLING BACK\nError: %v", prettyError(err))
if u.safe {
rollback := &rollbackCmd {
out: u.out,
client: u.client,
name: u.release,
dryRun: u.dryRun,
recreate: u.recreate,
force: u.force,
timeout: u.timeout,
wait: u.wait,
description: "",
revision: releaseHistory.Releases[0].Version,
disableHooks: u.disableHooks,
}
if err := rollback.run(); err != nil { return err }
}
return fmt.Errorf("UPGRADE FAILED: %v\n", prettyError(err))
} }
if settings.Debug { if settings.Debug {

Loading…
Cancel
Save