mirror of
https://github.com/helm/helm.git
synced 2026-06-30 19:57:48 +00:00
Merge pull request #11479 from MichaelMorrisEst/cascade
Add option to support foreground cascade deletion
This commit is contained in:
@@ -51,6 +51,10 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return compListReleases(toComplete, args, cfg)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
validationErr := validateCascadeFlag(client)
|
||||
if validationErr != nil {
|
||||
return validationErr
|
||||
}
|
||||
for i := 0; i < len(args); i++ {
|
||||
|
||||
res, err := client.Run(args[i])
|
||||
@@ -72,8 +76,16 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation")
|
||||
f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history")
|
||||
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all the resources are deleted before returning. It will wait for as long as --timeout")
|
||||
f.StringVar(&client.DeletionPropagation, "cascade", "background", "Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents. Defaults to background.")
|
||||
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
|
||||
f.StringVar(&client.Description, "description", "", "add a custom description")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func validateCascadeFlag(client *action.Uninstall) error {
|
||||
if client.DeletionPropagation != "background" && client.DeletionPropagation != "foreground" && client.DeletionPropagation != "orphan" {
|
||||
return fmt.Errorf("invalid cascade value (%s). Must be \"background\", \"foreground\", or \"orphan\"", client.DeletionPropagation)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"helm.sh/helm/v3/pkg/kube"
|
||||
@@ -35,12 +36,13 @@ import (
|
||||
type Uninstall struct {
|
||||
cfg *Configuration
|
||||
|
||||
DisableHooks bool
|
||||
DryRun bool
|
||||
KeepHistory bool
|
||||
Wait bool
|
||||
Timeout time.Duration
|
||||
Description string
|
||||
DisableHooks bool
|
||||
DryRun bool
|
||||
KeepHistory bool
|
||||
Wait bool
|
||||
DeletionPropagation string
|
||||
Timeout time.Duration
|
||||
Description string
|
||||
}
|
||||
|
||||
// NewUninstall creates a new Uninstall object with the given configuration.
|
||||
@@ -220,7 +222,25 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri
|
||||
return nil, "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")}
|
||||
}
|
||||
if len(resources) > 0 {
|
||||
if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceDeletionPropagation); ok {
|
||||
_, errs = kubeClient.DeleteWithPropagationPolicy(resources, parseCascadingFlag(u.cfg, u.DeletionPropagation))
|
||||
return resources, kept, errs
|
||||
}
|
||||
_, errs = u.cfg.KubeClient.Delete(resources)
|
||||
}
|
||||
return resources, kept, errs
|
||||
}
|
||||
|
||||
func parseCascadingFlag(cfg *Configuration, cascadingFlag string) v1.DeletionPropagation {
|
||||
switch cascadingFlag {
|
||||
case "orphan":
|
||||
return v1.DeletePropagationOrphan
|
||||
case "foreground":
|
||||
return v1.DeletePropagationForeground
|
||||
case "background":
|
||||
return v1.DeletePropagationBackground
|
||||
default:
|
||||
cfg.Log("uninstall: given cascade value: %s, defaulting to delete propagation background", cascadingFlag)
|
||||
return v1.DeletePropagationBackground
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,3 +95,35 @@ func TestUninstallRelease_Wait(t *testing.T) {
|
||||
is.Contains(err.Error(), "U timed out")
|
||||
is.Equal(res.Release.Info.Status, release.StatusUninstalled)
|
||||
}
|
||||
|
||||
func TestUninstallRelease_Cascade(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
unAction := uninstallAction(t)
|
||||
unAction.DisableHooks = true
|
||||
unAction.DryRun = false
|
||||
unAction.Wait = false
|
||||
unAction.DeletionPropagation = "foreground"
|
||||
|
||||
rel := releaseStub()
|
||||
rel.Name = "come-fail-away"
|
||||
rel.Manifest = `{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"metadata": {
|
||||
"name": "secret"
|
||||
},
|
||||
"type": "Opaque",
|
||||
"data": {
|
||||
"password": "password"
|
||||
}
|
||||
}`
|
||||
unAction.cfg.Releases.Create(rel)
|
||||
failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
|
||||
failer.DeleteWithPropagationError = fmt.Errorf("Uninstall with cascade failed")
|
||||
failer.BuildDummy = true
|
||||
unAction.cfg.KubeClient = failer
|
||||
_, err := unAction.Run(rel.Name)
|
||||
is.Error(err)
|
||||
is.Contains(err.Error(), "failed to delete release: come-fail-away")
|
||||
}
|
||||
|
||||
@@ -457,7 +457,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
|
||||
c.Log("Skipping delete of %q due to annotation [%s=%s]", info.Name, ResourcePolicyAnno, KeepPolicy)
|
||||
continue
|
||||
}
|
||||
if err := deleteResource(info); err != nil {
|
||||
if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil {
|
||||
c.Log("Failed to delete %q, err: %s", info.ObjectName(), err)
|
||||
continue
|
||||
}
|
||||
@@ -466,17 +466,29 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Delete deletes Kubernetes resources specified in the resources list. It will
|
||||
// attempt to delete all resources even if one or more fail and collect any
|
||||
// errors. All successfully deleted items will be returned in the `Deleted`
|
||||
// ResourceList that is part of the result.
|
||||
// Delete deletes Kubernetes resources specified in the resources list with
|
||||
// background cascade deletion. It will attempt to delete all resources even
|
||||
// if one or more fail and collect any errors. All successfully deleted items
|
||||
// will be returned in the `Deleted` ResourceList that is part of the result.
|
||||
func (c *Client) Delete(resources ResourceList) (*Result, []error) {
|
||||
return delete(c, resources, metav1.DeletePropagationBackground)
|
||||
}
|
||||
|
||||
// Delete deletes Kubernetes resources specified in the resources list with
|
||||
// given deletion propagation policy. It will attempt to delete all resources even
|
||||
// if one or more fail and collect any errors. All successfully deleted items
|
||||
// will be returned in the `Deleted` ResourceList that is part of the result.
|
||||
func (c *Client) DeleteWithPropagationPolicy(resources ResourceList, policy metav1.DeletionPropagation) (*Result, []error) {
|
||||
return delete(c, resources, policy)
|
||||
}
|
||||
|
||||
func delete(c *Client, resources ResourceList, propagation metav1.DeletionPropagation) (*Result, []error) {
|
||||
var errs []error
|
||||
res := &Result{}
|
||||
mtx := sync.Mutex{}
|
||||
err := perform(resources, func(info *resource.Info) error {
|
||||
c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind)
|
||||
err := deleteResource(info)
|
||||
err := deleteResource(info, propagation)
|
||||
if err == nil || apierrors.IsNotFound(err) {
|
||||
if err != nil {
|
||||
c.Log("Ignoring delete failure for %q %s: %v", info.Name, info.Mapping.GroupVersionKind, err)
|
||||
@@ -597,8 +609,7 @@ func createResource(info *resource.Info) error {
|
||||
return info.Refresh(obj, true)
|
||||
}
|
||||
|
||||
func deleteResource(info *resource.Info) error {
|
||||
policy := metav1.DeletePropagationBackground
|
||||
func deleteResource(info *resource.Info, policy metav1.DeletionPropagation) error {
|
||||
opts := &metav1.DeleteOptions{PropagationPolicy: &policy}
|
||||
_, err := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()).DeleteWithOptions(info.Namespace, info.Name, opts)
|
||||
return err
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
|
||||
@@ -37,10 +38,12 @@ type FailingKubeClient struct {
|
||||
GetError error
|
||||
WaitError error
|
||||
DeleteError error
|
||||
DeleteWithPropagationError error
|
||||
WatchUntilReadyError error
|
||||
UpdateError error
|
||||
BuildError error
|
||||
BuildTableError error
|
||||
BuildDummy bool
|
||||
BuildUnstructuredError error
|
||||
WaitAndGetCompletedPodPhaseError error
|
||||
WaitDuration time.Duration
|
||||
@@ -116,6 +119,9 @@ func (f *FailingKubeClient) Build(r io.Reader, _ bool) (kube.ResourceList, error
|
||||
if f.BuildError != nil {
|
||||
return []*resource.Info{}, f.BuildError
|
||||
}
|
||||
if f.BuildDummy {
|
||||
return createDummyResourceList(), nil
|
||||
}
|
||||
return f.PrintingKubeClient.Build(r, false)
|
||||
}
|
||||
|
||||
@@ -134,3 +140,21 @@ func (f *FailingKubeClient) WaitAndGetCompletedPodPhase(s string, d time.Duratio
|
||||
}
|
||||
return f.PrintingKubeClient.WaitAndGetCompletedPodPhase(s, d)
|
||||
}
|
||||
|
||||
// DeleteWithPropagationPolicy returns the configured error if set or prints
|
||||
func (f *FailingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceList, policy metav1.DeletionPropagation) (*kube.Result, []error) {
|
||||
if f.DeleteWithPropagationError != nil {
|
||||
return nil, []error{f.DeleteWithPropagationError}
|
||||
}
|
||||
return f.PrintingKubeClient.DeleteWithPropagationPolicy(resources, policy)
|
||||
}
|
||||
|
||||
func createDummyResourceList() kube.ResourceList {
|
||||
var resInfo resource.Info
|
||||
resInfo.Name = "dummyName"
|
||||
resInfo.Namespace = "dummyNamespace"
|
||||
var resourceList kube.ResourceList
|
||||
resourceList.Append(&resInfo)
|
||||
return resourceList
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
|
||||
@@ -115,6 +116,17 @@ func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(_ string, _ time.Durati
|
||||
return v1.PodSucceeded, nil
|
||||
}
|
||||
|
||||
// DeleteWithPropagationPolicy implements KubeClient delete.
|
||||
//
|
||||
// It only prints out the content to be deleted.
|
||||
func (p *PrintingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceList, policy metav1.DeletionPropagation) (*kube.Result, []error) {
|
||||
_, err := io.Copy(p.Out, bufferize(resources))
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
return &kube.Result{Deleted: resources}, nil
|
||||
}
|
||||
|
||||
func bufferize(resources kube.ResourceList) io.Reader {
|
||||
var builder strings.Builder
|
||||
for _, info := range resources {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
@@ -79,6 +80,14 @@ type InterfaceExt interface {
|
||||
WaitForDelete(resources ResourceList, timeout time.Duration) error
|
||||
}
|
||||
|
||||
// InterfaceDeletionPropagation is introduced to avoid breaking backwards compatibility for Interface implementers.
|
||||
//
|
||||
// TODO Helm 4: Remove InterfaceDeletionPropagation and integrate its method(s) into the Interface.
|
||||
type InterfaceDeletionPropagation interface {
|
||||
// Delete destroys one or more resources. The deletion propagation is handled as per the given deletion propagation value.
|
||||
DeleteWithPropagationPolicy(resources ResourceList, policy metav1.DeletionPropagation) (*Result, []error)
|
||||
}
|
||||
|
||||
// InterfaceResources is introduced to avoid breaking backwards compatibility for Interface implementers.
|
||||
//
|
||||
// TODO Helm 4: Remove InterfaceResources and integrate its method(s) into the Interface.
|
||||
@@ -103,4 +112,5 @@ type InterfaceResources interface {
|
||||
|
||||
var _ Interface = (*Client)(nil)
|
||||
var _ InterfaceExt = (*Client)(nil)
|
||||
var _ InterfaceDeletionPropagation = (*Client)(nil)
|
||||
var _ InterfaceResources = (*Client)(nil)
|
||||
|
||||
Reference in New Issue
Block a user