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 }