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 }