...

Source file src/helm.sh/helm/v3/pkg/action/uninstall.go

Documentation: helm.sh/helm/v3/pkg/action

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package action
    18  
    19  import (
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/pkg/errors"
    24  
    25  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  
    27  	"helm.sh/helm/v3/pkg/chartutil"
    28  	"helm.sh/helm/v3/pkg/kube"
    29  	"helm.sh/helm/v3/pkg/release"
    30  	"helm.sh/helm/v3/pkg/releaseutil"
    31  	helmtime "helm.sh/helm/v3/pkg/time"
    32  )
    33  
    34  // Uninstall is the action for uninstalling releases.
    35  //
    36  // It provides the implementation of 'helm uninstall'.
    37  type Uninstall struct {
    38  	cfg *Configuration
    39  
    40  	DisableHooks        bool
    41  	DryRun              bool
    42  	IgnoreNotFound      bool
    43  	KeepHistory         bool
    44  	Wait                bool
    45  	DeletionPropagation string
    46  	Timeout             time.Duration
    47  	Description         string
    48  }
    49  
    50  // NewUninstall creates a new Uninstall object with the given configuration.
    51  func NewUninstall(cfg *Configuration) *Uninstall {
    52  	return &Uninstall{
    53  		cfg: cfg,
    54  	}
    55  }
    56  
    57  // Run uninstalls the given release.
    58  func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) {
    59  	if err := u.cfg.KubeClient.IsReachable(); err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	if u.DryRun {
    64  		// In the dry run case, just see if the release exists
    65  		r, err := u.cfg.releaseContent(name, 0)
    66  		if err != nil {
    67  			return &release.UninstallReleaseResponse{}, err
    68  		}
    69  		return &release.UninstallReleaseResponse{Release: r}, nil
    70  	}
    71  
    72  	if err := chartutil.ValidateReleaseName(name); err != nil {
    73  		return nil, errors.Errorf("uninstall: Release name is invalid: %s", name)
    74  	}
    75  
    76  	rels, err := u.cfg.Releases.History(name)
    77  	if err != nil {
    78  		if u.IgnoreNotFound {
    79  			return nil, nil
    80  		}
    81  		return nil, errors.Wrapf(err, "uninstall: Release not loaded: %s", name)
    82  	}
    83  	if len(rels) < 1 {
    84  		return nil, errMissingRelease
    85  	}
    86  
    87  	releaseutil.SortByRevision(rels)
    88  	rel := rels[len(rels)-1]
    89  
    90  	// TODO: Are there any cases where we want to force a delete even if it's
    91  	// already marked deleted?
    92  	if rel.Info.Status == release.StatusUninstalled {
    93  		if !u.KeepHistory {
    94  			if err := u.purgeReleases(rels...); err != nil {
    95  				return nil, errors.Wrap(err, "uninstall: Failed to purge the release")
    96  			}
    97  			return &release.UninstallReleaseResponse{Release: rel}, nil
    98  		}
    99  		return nil, errors.Errorf("the release named %q is already deleted", name)
   100  	}
   101  
   102  	u.cfg.Log("uninstall: Deleting %s", name)
   103  	rel.Info.Status = release.StatusUninstalling
   104  	rel.Info.Deleted = helmtime.Now()
   105  	rel.Info.Description = "Deletion in progress (or silently failed)"
   106  	res := &release.UninstallReleaseResponse{Release: rel}
   107  
   108  	if !u.DisableHooks {
   109  		if err := u.cfg.execHook(rel, release.HookPreDelete, u.Timeout); err != nil {
   110  			return res, err
   111  		}
   112  	} else {
   113  		u.cfg.Log("delete hooks disabled for %s", name)
   114  	}
   115  
   116  	// From here on out, the release is currently considered to be in StatusUninstalling
   117  	// state.
   118  	if err := u.cfg.Releases.Update(rel); err != nil {
   119  		u.cfg.Log("uninstall: Failed to store updated release: %s", err)
   120  	}
   121  
   122  	deletedResources, kept, errs := u.deleteRelease(rel)
   123  	if errs != nil {
   124  		u.cfg.Log("uninstall: Failed to delete release: %s", errs)
   125  		return nil, errors.Errorf("failed to delete release: %s", name)
   126  	}
   127  
   128  	if kept != "" {
   129  		kept = "These resources were kept due to the resource policy:\n" + kept
   130  	}
   131  	res.Info = kept
   132  
   133  	if u.Wait {
   134  		if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceExt); ok {
   135  			if err := kubeClient.WaitForDelete(deletedResources, u.Timeout); err != nil {
   136  				errs = append(errs, err)
   137  			}
   138  		}
   139  	}
   140  
   141  	if !u.DisableHooks {
   142  		if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout); err != nil {
   143  			errs = append(errs, err)
   144  		}
   145  	}
   146  
   147  	rel.Info.Status = release.StatusUninstalled
   148  	if len(u.Description) > 0 {
   149  		rel.Info.Description = u.Description
   150  	} else {
   151  		rel.Info.Description = "Uninstallation complete"
   152  	}
   153  
   154  	if !u.KeepHistory {
   155  		u.cfg.Log("purge requested for %s", name)
   156  		err := u.purgeReleases(rels...)
   157  		if err != nil {
   158  			errs = append(errs, errors.Wrap(err, "uninstall: Failed to purge the release"))
   159  		}
   160  
   161  		// Return the errors that occurred while deleting the release, if any
   162  		if len(errs) > 0 {
   163  			return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs))
   164  		}
   165  
   166  		return res, nil
   167  	}
   168  
   169  	if err := u.cfg.Releases.Update(rel); err != nil {
   170  		u.cfg.Log("uninstall: Failed to store updated release: %s", err)
   171  	}
   172  
   173  	if len(errs) > 0 {
   174  		return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs))
   175  	}
   176  	return res, nil
   177  }
   178  
   179  func (u *Uninstall) purgeReleases(rels ...*release.Release) error {
   180  	for _, rel := range rels {
   181  		if _, err := u.cfg.Releases.Delete(rel.Name, rel.Version); err != nil {
   182  			return err
   183  		}
   184  	}
   185  	return nil
   186  }
   187  
   188  func joinErrors(errs []error) string {
   189  	es := make([]string, 0, len(errs))
   190  	for _, e := range errs {
   191  		es = append(es, e.Error())
   192  	}
   193  	return strings.Join(es, "; ")
   194  }
   195  
   196  // deleteRelease deletes the release and returns list of delete resources and manifests that were kept in the deletion process
   197  func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, string, []error) {
   198  	var errs []error
   199  	caps, err := u.cfg.getCapabilities()
   200  	if err != nil {
   201  		return nil, rel.Manifest, []error{errors.Wrap(err, "could not get apiVersions from Kubernetes")}
   202  	}
   203  
   204  	manifests := releaseutil.SplitManifests(rel.Manifest)
   205  	_, files, err := releaseutil.SortManifests(manifests, caps.APIVersions, releaseutil.UninstallOrder)
   206  	if err != nil {
   207  		// We could instead just delete everything in no particular order.
   208  		// FIXME: One way to delete at this point would be to try a label-based
   209  		// deletion. The problem with this is that we could get a false positive
   210  		// and delete something that was not legitimately part of this release.
   211  		return nil, rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")}
   212  	}
   213  
   214  	filesToKeep, filesToDelete := filterManifestsToKeep(files)
   215  	var kept string
   216  	for _, f := range filesToKeep {
   217  		kept += "[" + f.Head.Kind + "] " + f.Head.Metadata.Name + "\n"
   218  	}
   219  
   220  	var builder strings.Builder
   221  	for _, file := range filesToDelete {
   222  		builder.WriteString("\n---\n" + file.Content)
   223  	}
   224  
   225  	resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false)
   226  	if err != nil {
   227  		return nil, "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")}
   228  	}
   229  	if len(resources) > 0 {
   230  		if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceDeletionPropagation); ok {
   231  			_, errs = kubeClient.DeleteWithPropagationPolicy(resources, parseCascadingFlag(u.cfg, u.DeletionPropagation))
   232  			return resources, kept, errs
   233  		}
   234  		_, errs = u.cfg.KubeClient.Delete(resources)
   235  	}
   236  	return resources, kept, errs
   237  }
   238  
   239  func parseCascadingFlag(cfg *Configuration, cascadingFlag string) v1.DeletionPropagation {
   240  	switch cascadingFlag {
   241  	case "orphan":
   242  		return v1.DeletePropagationOrphan
   243  	case "foreground":
   244  		return v1.DeletePropagationForeground
   245  	case "background":
   246  		return v1.DeletePropagationBackground
   247  	default:
   248  		cfg.Log("uninstall: given cascade value: %s, defaulting to delete propagation background", cascadingFlag)
   249  		return v1.DeletePropagationBackground
   250  	}
   251  }
   252  

View as plain text