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