github.com/aaronmell/helm@v3.0.0-beta.2+incompatible/pkg/action/uninstall.go (about)

     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  	"helm.sh/helm/pkg/release"
    26  	"helm.sh/helm/pkg/releaseutil"
    27  )
    28  
    29  // Uninstall is the action for uninstalling releases.
    30  //
    31  // It provides the implementation of 'helm uninstall'.
    32  type Uninstall struct {
    33  	cfg *Configuration
    34  
    35  	DisableHooks bool
    36  	DryRun       bool
    37  	KeepHistory  bool
    38  	Timeout      time.Duration
    39  }
    40  
    41  // NewUninstall creates a new Uninstall object with the given configuration.
    42  func NewUninstall(cfg *Configuration) *Uninstall {
    43  	return &Uninstall{
    44  		cfg: cfg,
    45  	}
    46  }
    47  
    48  // Run uninstalls the given release.
    49  func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) {
    50  	if u.DryRun {
    51  		// In the dry run case, just see if the release exists
    52  		r, err := u.cfg.releaseContent(name, 0)
    53  		if err != nil {
    54  			return &release.UninstallReleaseResponse{}, err
    55  		}
    56  		return &release.UninstallReleaseResponse{Release: r}, nil
    57  	}
    58  
    59  	if err := validateReleaseName(name); err != nil {
    60  		return nil, errors.Errorf("uninstall: Release name is invalid: %s", name)
    61  	}
    62  
    63  	rels, err := u.cfg.Releases.History(name)
    64  	if err != nil {
    65  		return nil, errors.Wrapf(err, "uninstall: Release not loaded: %s", name)
    66  	}
    67  	if len(rels) < 1 {
    68  		return nil, errMissingRelease
    69  	}
    70  
    71  	releaseutil.SortByRevision(rels)
    72  	rel := rels[len(rels)-1]
    73  
    74  	// TODO: Are there any cases where we want to force a delete even if it's
    75  	// already marked deleted?
    76  	if rel.Info.Status == release.StatusUninstalled {
    77  		if !u.KeepHistory {
    78  			if err := u.purgeReleases(rels...); err != nil {
    79  				return nil, errors.Wrap(err, "uninstall: Failed to purge the release")
    80  			}
    81  			return &release.UninstallReleaseResponse{Release: rel}, nil
    82  		}
    83  		return nil, errors.Errorf("the release named %q is already deleted", name)
    84  	}
    85  
    86  	u.cfg.Log("uninstall: Deleting %s", name)
    87  	rel.Info.Status = release.StatusUninstalling
    88  	rel.Info.Deleted = time.Now()
    89  	rel.Info.Description = "Deletion in progress (or silently failed)"
    90  	res := &release.UninstallReleaseResponse{Release: rel}
    91  
    92  	if !u.DisableHooks {
    93  		if err := u.cfg.execHook(rel, release.HookPreDelete, u.Timeout); err != nil {
    94  			return res, err
    95  		}
    96  	} else {
    97  		u.cfg.Log("delete hooks disabled for %s", name)
    98  	}
    99  
   100  	// From here on out, the release is currently considered to be in StatusUninstalling
   101  	// state.
   102  	if err := u.cfg.Releases.Update(rel); err != nil {
   103  		u.cfg.Log("uninstall: Failed to store updated release: %s", err)
   104  	}
   105  
   106  	kept, errs := u.deleteRelease(rel)
   107  	res.Info = kept
   108  
   109  	if !u.DisableHooks {
   110  		if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout); err != nil {
   111  			errs = append(errs, err)
   112  		}
   113  	}
   114  
   115  	rel.Info.Status = release.StatusUninstalled
   116  	rel.Info.Description = "Uninstallation complete"
   117  
   118  	if !u.KeepHistory {
   119  		u.cfg.Log("purge requested for %s", name)
   120  		err := u.purgeReleases(rels...)
   121  		if err != nil {
   122  			errs = append(errs, errors.Wrap(err, "uninstall: Failed to purge the release"))
   123  		}
   124  
   125  		// Return the errors that occurred while deleting the release, if any
   126  		if len(errs) > 0 {
   127  			return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs))
   128  		}
   129  
   130  		return res, nil
   131  	}
   132  
   133  	if err := u.cfg.Releases.Update(rel); err != nil {
   134  		u.cfg.Log("uninstall: Failed to store updated release: %s", err)
   135  	}
   136  
   137  	if len(errs) > 0 {
   138  		return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs))
   139  	}
   140  	return res, nil
   141  }
   142  
   143  func (u *Uninstall) purgeReleases(rels ...*release.Release) error {
   144  	for _, rel := range rels {
   145  		if _, err := u.cfg.Releases.Delete(rel.Name, rel.Version); err != nil {
   146  			return err
   147  		}
   148  	}
   149  	return nil
   150  }
   151  
   152  func joinErrors(errs []error) string {
   153  	es := make([]string, 0, len(errs))
   154  	for _, e := range errs {
   155  		es = append(es, e.Error())
   156  	}
   157  	return strings.Join(es, "; ")
   158  }
   159  
   160  // deleteRelease deletes the release and returns manifests that were kept in the deletion process
   161  func (u *Uninstall) deleteRelease(rel *release.Release) (string, []error) {
   162  	caps, err := u.cfg.getCapabilities()
   163  	if err != nil {
   164  		return rel.Manifest, []error{errors.Wrap(err, "could not get apiVersions from Kubernetes")}
   165  	}
   166  
   167  	manifests := releaseutil.SplitManifests(rel.Manifest)
   168  	_, files, err := releaseutil.SortManifests(manifests, caps.APIVersions, releaseutil.UninstallOrder)
   169  	if err != nil {
   170  		// We could instead just delete everything in no particular order.
   171  		// FIXME: One way to delete at this point would be to try a label-based
   172  		// deletion. The problem with this is that we could get a false positive
   173  		// and delete something that was not legitimately part of this release.
   174  		return rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")}
   175  	}
   176  
   177  	filesToKeep, filesToDelete := filterManifestsToKeep(files)
   178  	var kept string
   179  	for _, f := range filesToKeep {
   180  		kept += f.Name + "\n"
   181  	}
   182  
   183  	var builder strings.Builder
   184  	for _, file := range filesToDelete {
   185  		builder.WriteString("\n---\n" + file.Content)
   186  	}
   187  	resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()))
   188  	if err != nil {
   189  		return "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")}
   190  	}
   191  
   192  	_, errs := u.cfg.KubeClient.Delete(resources)
   193  	return kept, errs
   194  }