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 }