github.com/appscode/helm@v3.0.0-alpha.1+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  	"bytes"
    21  	"sort"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/pkg/errors"
    26  
    27  	"helm.sh/helm/pkg/hooks"
    28  	"helm.sh/helm/pkg/kube"
    29  	"helm.sh/helm/pkg/release"
    30  	"helm.sh/helm/pkg/releaseutil"
    31  )
    32  
    33  // Uninstall is the action for uninstalling releases.
    34  //
    35  // It provides the implementation of 'helm uninstall'.
    36  type Uninstall struct {
    37  	cfg *Configuration
    38  
    39  	DisableHooks bool
    40  	DryRun       bool
    41  	KeepHistory  bool
    42  	Timeout      time.Duration
    43  }
    44  
    45  // NewUninstall creates a new Uninstall object with the given configuration.
    46  func NewUninstall(cfg *Configuration) *Uninstall {
    47  	return &Uninstall{
    48  		cfg: cfg,
    49  	}
    50  }
    51  
    52  // Run uninstalls the given release.
    53  func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) {
    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.execHook(rel.Hooks, hooks.PreDelete); 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.execHook(rel.Hooks, hooks.PostDelete); 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  		return res, errors.Wrap(err, "uninstall: Failed to purge the release")
   126  	}
   127  
   128  	if err := u.cfg.Releases.Update(rel); err != nil {
   129  		u.cfg.Log("uninstall: Failed to store updated release: %s", err)
   130  	}
   131  
   132  	if len(errs) > 0 {
   133  		return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs))
   134  	}
   135  	return res, nil
   136  }
   137  
   138  func (u *Uninstall) purgeReleases(rels ...*release.Release) error {
   139  	for _, rel := range rels {
   140  		if _, err := u.cfg.Releases.Delete(rel.Name, rel.Version); err != nil {
   141  			return err
   142  		}
   143  	}
   144  	return nil
   145  }
   146  
   147  func joinErrors(errs []error) string {
   148  	es := make([]string, 0, len(errs))
   149  	for _, e := range errs {
   150  		es = append(es, e.Error())
   151  	}
   152  	return strings.Join(es, "; ")
   153  }
   154  
   155  // execHook executes all of the hooks for the given hook event.
   156  func (u *Uninstall) execHook(hs []*release.Hook, hook string) error {
   157  	executingHooks := []*release.Hook{}
   158  
   159  	for _, h := range hs {
   160  		for _, e := range h.Events {
   161  			if string(e) == hook {
   162  				executingHooks = append(executingHooks, h)
   163  			}
   164  		}
   165  	}
   166  
   167  	sort.Sort(hookByWeight(executingHooks))
   168  
   169  	for _, h := range executingHooks {
   170  		if err := deleteHookByPolicy(u.cfg, h, hooks.BeforeHookCreation); err != nil {
   171  			return err
   172  		}
   173  
   174  		b := bytes.NewBufferString(h.Manifest)
   175  		if err := u.cfg.KubeClient.Create(b); err != nil {
   176  			return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path)
   177  		}
   178  		b.Reset()
   179  		b.WriteString(h.Manifest)
   180  
   181  		if err := u.cfg.KubeClient.WatchUntilReady(b, u.Timeout); err != nil {
   182  			// If a hook is failed, checkout the annotation of the hook to determine whether the hook should be deleted
   183  			// under failed condition. If so, then clear the corresponding resource object in the hook
   184  			if err := deleteHookByPolicy(u.cfg, h, hooks.HookFailed); err != nil {
   185  				return err
   186  			}
   187  			return err
   188  		}
   189  	}
   190  
   191  	// If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted
   192  	// under succeeded condition. If so, then clear the corresponding resource object in each hook
   193  	for _, h := range executingHooks {
   194  		if err := deleteHookByPolicy(u.cfg, h, hooks.HookSucceeded); err != nil {
   195  			return err
   196  		}
   197  		h.LastRun = time.Now()
   198  	}
   199  
   200  	return nil
   201  }
   202  
   203  // deleteRelease deletes the release and returns manifests that were kept in the deletion process
   204  func (u *Uninstall) deleteRelease(rel *release.Release) (kept string, errs []error) {
   205  	caps, err := u.cfg.getCapabilities()
   206  	if err != nil {
   207  		return rel.Manifest, []error{errors.Wrap(err, "could not get apiVersions from Kubernetes")}
   208  	}
   209  
   210  	manifests := releaseutil.SplitManifests(rel.Manifest)
   211  	_, files, err := releaseutil.SortManifests(manifests, caps.APIVersions, releaseutil.UninstallOrder)
   212  	if err != nil {
   213  		// We could instead just delete everything in no particular order.
   214  		// FIXME: One way to delete at this point would be to try a label-based
   215  		// deletion. The problem with this is that we could get a false positive
   216  		// and delete something that was not legitimately part of this release.
   217  		return rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")}
   218  	}
   219  
   220  	filesToKeep, filesToDelete := filterManifestsToKeep(files)
   221  	for _, f := range filesToKeep {
   222  		kept += f.Name + "\n"
   223  	}
   224  
   225  	for _, file := range filesToDelete {
   226  		b := bytes.NewBufferString(strings.TrimSpace(file.Content))
   227  		if b.Len() == 0 {
   228  			continue
   229  		}
   230  		if err := u.cfg.KubeClient.Delete(b); err != nil {
   231  			u.cfg.Log("uninstall: Failed deletion of %q: %s", rel.Name, err)
   232  			if err == kube.ErrNoObjectsVisited {
   233  				// Rewrite the message from "no objects visited"
   234  				err = errors.New("object not found, skipping delete")
   235  			}
   236  			errs = append(errs, err)
   237  		}
   238  	}
   239  	return kept, errs
   240  }