github.com/azure-devops-engineer/helm@v3.0.0-alpha.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  	"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  		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  // execHook executes all of the hooks for the given hook event.
   165  func (u *Uninstall) execHook(hs []*release.Hook, hook string) error {
   166  	executingHooks := []*release.Hook{}
   167  
   168  	for _, h := range hs {
   169  		for _, e := range h.Events {
   170  			if string(e) == hook {
   171  				executingHooks = append(executingHooks, h)
   172  			}
   173  		}
   174  	}
   175  
   176  	sort.Sort(hookByWeight(executingHooks))
   177  
   178  	for _, h := range executingHooks {
   179  		if err := deleteHookByPolicy(u.cfg, h, hooks.BeforeHookCreation); err != nil {
   180  			return err
   181  		}
   182  
   183  		b := bytes.NewBufferString(h.Manifest)
   184  		if err := u.cfg.KubeClient.Create(b); err != nil {
   185  			return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path)
   186  		}
   187  		b.Reset()
   188  		b.WriteString(h.Manifest)
   189  
   190  		if err := u.cfg.KubeClient.WatchUntilReady(b, u.Timeout); err != nil {
   191  			// If a hook is failed, checkout the annotation of the hook to determine whether the hook should be deleted
   192  			// under failed condition. If so, then clear the corresponding resource object in the hook
   193  			if err := deleteHookByPolicy(u.cfg, h, hooks.HookFailed); err != nil {
   194  				return err
   195  			}
   196  			return err
   197  		}
   198  	}
   199  
   200  	// If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted
   201  	// under succeeded condition. If so, then clear the corresponding resource object in each hook
   202  	for _, h := range executingHooks {
   203  		if err := deleteHookByPolicy(u.cfg, h, hooks.HookSucceeded); err != nil {
   204  			return err
   205  		}
   206  		h.LastRun = time.Now()
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  // deleteRelease deletes the release and returns manifests that were kept in the deletion process
   213  func (u *Uninstall) deleteRelease(rel *release.Release) (kept string, errs []error) {
   214  	caps, err := u.cfg.getCapabilities()
   215  	if err != nil {
   216  		return rel.Manifest, []error{errors.Wrap(err, "could not get apiVersions from Kubernetes")}
   217  	}
   218  
   219  	manifests := releaseutil.SplitManifests(rel.Manifest)
   220  	_, files, err := releaseutil.SortManifests(manifests, caps.APIVersions, releaseutil.UninstallOrder)
   221  	if err != nil {
   222  		// We could instead just delete everything in no particular order.
   223  		// FIXME: One way to delete at this point would be to try a label-based
   224  		// deletion. The problem with this is that we could get a false positive
   225  		// and delete something that was not legitimately part of this release.
   226  		return rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")}
   227  	}
   228  
   229  	filesToKeep, filesToDelete := filterManifestsToKeep(files)
   230  	for _, f := range filesToKeep {
   231  		kept += f.Name + "\n"
   232  	}
   233  
   234  	for _, file := range filesToDelete {
   235  		b := bytes.NewBufferString(strings.TrimSpace(file.Content))
   236  		if b.Len() == 0 {
   237  			continue
   238  		}
   239  		if err := u.cfg.KubeClient.Delete(b); err != nil {
   240  			u.cfg.Log("uninstall: Failed deletion of %q: %s", rel.Name, err)
   241  			if err == kube.ErrNoObjectsVisited {
   242  				// Rewrite the message from "no objects visited"
   243  				err = errors.New("object not found, skipping delete")
   244  			}
   245  			errs = append(errs, err)
   246  		}
   247  	}
   248  	return kept, errs
   249  }