diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 0e63ab3a5..c7c88e3bf 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -155,6 +155,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") + f.BoolVar(&client.SyncDeleteHook, "sync-delete-hook", false, "make hooks that perform a delete wait for resources to be deleted") addValueOptionsFlags(f, valueOpts) addChartPathOptionsFlags(f, &client.ChartPathOptions) diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 7ada8e3b1..389ecdf65 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -229,6 +229,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.StringVar(&client.Description, "description", "", "add a custom description") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") + f.BoolVar(&client.SyncDeleteHook, "sync-delete-hook", false, "make hooks that perform a delete wait for resources to be deleted") addChartPathOptionsFlags(f, &client.ChartPathOptions) addValueOptionsFlags(f, valueOpts) bindOutputFlag(cmd, &outfmt) diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index 40c1ffdb6..711af8531 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -22,12 +22,13 @@ import ( "github.com/pkg/errors" + "helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/release" helmtime "helm.sh/helm/v3/pkg/time" ) // execHook executes all of the hooks for the given hook event. -func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, timeout time.Duration) error { +func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, timeout time.Duration, syncDeleteHook bool) error { executingHooks := []*release.Hook{} for _, h := range rl.Hooks { @@ -51,7 +52,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation} } - if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation); err != nil { + if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, syncDeleteHook, timeout); err != nil { return err } @@ -88,7 +89,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, h.LastRun.Phase = release.HookPhaseFailed // If a hook is failed, check 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 := cfg.deleteHookByPolicy(h, release.HookFailed); err != nil { + if err := cfg.deleteHookByPolicy(h, release.HookFailed, syncDeleteHook, timeout); err != nil { return err } return err @@ -99,7 +100,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, // If all hooks are successful, check 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 := cfg.deleteHookByPolicy(h, release.HookSucceeded); err != nil { + if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, syncDeleteHook, timeout); err != nil { return err } } @@ -120,7 +121,7 @@ func (x hookByWeight) Less(i, j int) bool { } // deleteHookByPolicy deletes a hook if the hook policy instructs it to -func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy) error { +func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, syncDeleteHook bool, deleteTimeout time.Duration) error { // Never delete CustomResourceDefinitions; this could cause lots of // cascading garbage collection. if h.Kind == "CustomResourceDefinition" { @@ -135,6 +136,13 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo if len(errs) > 0 { return errors.New(joinErrors(errs)) } + if syncDeleteHook { + if kubeClient, ok := cfg.KubeClient.(kube.InterfaceExt); ok { + if err := kubeClient.WaitForDelete(resources, deleteTimeout); err != nil { + return err + } + } + } } return nil } diff --git a/pkg/action/install.go b/pkg/action/install.go index 32be904b4..1d5dc0465 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -100,6 +100,8 @@ type Install struct { // OutputDir/ UseReleaseName bool PostRenderer postrender.PostRenderer + // Make deletion hooks wait for completion + SyncDeleteHook bool // Lock to control raceconditions when the process receives a SIGTERM Lock sync.Mutex } @@ -355,7 +357,7 @@ func (i *Install) performInstall(c chan<- resultMessage, rel *release.Release, t // pre-install hooks if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout); err != nil { + if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout, i.SyncDeleteHook); err != nil { i.reportToRun(c, rel, fmt.Errorf("failed pre-install: %s", err)) return } @@ -391,7 +393,7 @@ func (i *Install) performInstall(c chan<- resultMessage, rel *release.Release, t } if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout); err != nil { + if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout, i.SyncDeleteHook); err != nil { i.reportToRun(c, rel, fmt.Errorf("failed post-install: %s", err)) return } diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index ecaeaf59f..54d26adde 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -88,7 +88,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) { rel.Hooks = executingHooks } - if err := r.cfg.execHook(rel, release.HookTest, r.Timeout); err != nil { + if err := r.cfg.execHook(rel, release.HookTest, r.Timeout, false); err != nil { rel.Hooks = append(skippedHooks, rel.Hooks...) r.cfg.Releases.Update(rel) return rel, err diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index f3f958f3d..f7b8ad941 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -45,6 +45,8 @@ type Rollback struct { Force bool // will (if true) force resource upgrade through uninstall/recreate if needed CleanupOnFail bool MaxHistory int // MaxHistory limits the maximum number of revisions saved per release + // Make deletion hooks wait for completion + SyncDeleteHook bool } // NewRollback creates a new Rollback object with the given configuration. @@ -157,7 +159,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas // pre-rollback hooks if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.Timeout); err != nil { + if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.Timeout, r.SyncDeleteHook); err != nil { return targetRelease, err } } else { @@ -219,7 +221,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas // post-rollback hooks if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.Timeout); err != nil { + if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.Timeout, r.SyncDeleteHook); err != nil { return targetRelease, err } } diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index 65993df4c..9e0b75764 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -99,7 +99,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) res := &release.UninstallReleaseResponse{Release: rel} if !u.DisableHooks { - if err := u.cfg.execHook(rel, release.HookPreDelete, u.Timeout); err != nil { + if err := u.cfg.execHook(rel, release.HookPreDelete, u.Timeout, false); err != nil { return res, err } } else { @@ -128,7 +128,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) } if !u.DisableHooks { - if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout); err != nil { + if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout, false); err != nil { errs = append(errs, err) } } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index e228f52dc..8c36298dd 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -101,6 +101,8 @@ type Upgrade struct { DisableOpenAPIValidation bool // Get missing dependencies DependencyUpdate bool + // Make deletion hooks wait for completion + SyncDeleteHook bool // Lock to control raceconditions when the process receives a SIGTERM Lock sync.Mutex } @@ -367,7 +369,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele // pre-upgrade hooks if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout); err != nil { + if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout, u.SyncDeleteHook); err != nil { u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err)) return } @@ -410,7 +412,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele // post-upgrade hooks if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout); err != nil { + if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout, u.SyncDeleteHook); err != nil { u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err)) return } @@ -478,6 +480,7 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e rollin.Recreate = u.Recreate rollin.Force = u.Force rollin.Timeout = u.Timeout + rollin.SyncDeleteHook = u.SyncDeleteHook if rollErr := rollin.Run(rel.Name); rollErr != nil { return rel, errors.Wrapf(rollErr, "an error occurred while rolling back the release. original upgrade error: %s", err) }