github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/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 "github.com/stefanmcshane/helm/pkg/chartutil" 26 "github.com/stefanmcshane/helm/pkg/kube" 27 "github.com/stefanmcshane/helm/pkg/release" 28 "github.com/stefanmcshane/helm/pkg/releaseutil" 29 helmtime "github.com/stefanmcshane/helm/pkg/time" 30 ) 31 32 // Uninstall is the action for uninstalling releases. 33 // 34 // It provides the implementation of 'helm uninstall'. 35 type Uninstall struct { 36 cfg *Configuration 37 38 DisableHooks bool 39 DryRun bool 40 KeepHistory bool 41 Wait bool 42 Timeout time.Duration 43 Description string 44 } 45 46 // NewUninstall creates a new Uninstall object with the given configuration. 47 func NewUninstall(cfg *Configuration) *Uninstall { 48 return &Uninstall{ 49 cfg: cfg, 50 } 51 } 52 53 // Run uninstalls the given release. 54 func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) { 55 if err := u.cfg.KubeClient.IsReachable(); err != nil { 56 return nil, err 57 } 58 59 if u.DryRun { 60 // In the dry run case, just see if the release exists 61 r, err := u.cfg.releaseContent(name, 0) 62 if err != nil { 63 return &release.UninstallReleaseResponse{}, err 64 } 65 return &release.UninstallReleaseResponse{Release: r}, nil 66 } 67 68 if err := chartutil.ValidateReleaseName(name); err != nil { 69 return nil, errors.Errorf("uninstall: Release name is invalid: %s", name) 70 } 71 72 rels, err := u.cfg.Releases.History(name) 73 if err != nil { 74 return nil, errors.Wrapf(err, "uninstall: Release not loaded: %s", name) 75 } 76 if len(rels) < 1 { 77 return nil, errMissingRelease 78 } 79 80 releaseutil.SortByRevision(rels) 81 rel := rels[len(rels)-1] 82 83 // TODO: Are there any cases where we want to force a delete even if it's 84 // already marked deleted? 85 if rel.Info.Status == release.StatusUninstalled { 86 if !u.KeepHistory { 87 if err := u.purgeReleases(rels...); err != nil { 88 return nil, errors.Wrap(err, "uninstall: Failed to purge the release") 89 } 90 return &release.UninstallReleaseResponse{Release: rel}, nil 91 } 92 return nil, errors.Errorf("the release named %q is already deleted", name) 93 } 94 95 u.cfg.Log("uninstall: Deleting %s", name) 96 rel.Info.Status = release.StatusUninstalling 97 rel.Info.Deleted = helmtime.Now() 98 rel.Info.Description = "Deletion in progress (or silently failed)" 99 res := &release.UninstallReleaseResponse{Release: rel} 100 101 if !u.DisableHooks { 102 if err := u.cfg.execHook(rel, release.HookPreDelete, u.Timeout); err != nil { 103 return res, err 104 } 105 } else { 106 u.cfg.Log("delete hooks disabled for %s", name) 107 } 108 109 // From here on out, the release is currently considered to be in StatusUninstalling 110 // state. 111 if err := u.cfg.Releases.Update(rel); err != nil { 112 u.cfg.Log("uninstall: Failed to store updated release: %s", err) 113 } 114 115 deletedResources, kept, errs := u.deleteRelease(rel) 116 if errs != nil { 117 u.cfg.Log("uninstall: Failed to delete release: %s", errs) 118 return nil, errors.Errorf("failed to delete release: %s", name) 119 } 120 121 if kept != "" { 122 kept = "These resources were kept due to the resource policy:\n" + kept 123 } 124 res.Info = kept 125 126 if u.Wait { 127 if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceExt); ok { 128 if err := kubeClient.WaitForDelete(deletedResources, u.Timeout); err != nil { 129 errs = append(errs, err) 130 } 131 } 132 } 133 134 if !u.DisableHooks { 135 if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout); err != nil { 136 errs = append(errs, err) 137 } 138 } 139 140 rel.Info.Status = release.StatusUninstalled 141 if len(u.Description) > 0 { 142 rel.Info.Description = u.Description 143 } else { 144 rel.Info.Description = "Uninstallation complete" 145 } 146 147 if !u.KeepHistory { 148 u.cfg.Log("purge requested for %s", name) 149 err := u.purgeReleases(rels...) 150 if err != nil { 151 errs = append(errs, errors.Wrap(err, "uninstall: Failed to purge the release")) 152 } 153 154 // Return the errors that occurred while deleting the release, if any 155 if len(errs) > 0 { 156 return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs)) 157 } 158 159 return res, nil 160 } 161 162 if err := u.cfg.Releases.Update(rel); err != nil { 163 u.cfg.Log("uninstall: Failed to store updated release: %s", err) 164 } 165 166 if len(errs) > 0 { 167 return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs)) 168 } 169 return res, nil 170 } 171 172 func (u *Uninstall) purgeReleases(rels ...*release.Release) error { 173 for _, rel := range rels { 174 if _, err := u.cfg.Releases.Delete(rel.Name, rel.Version); err != nil { 175 return err 176 } 177 } 178 return nil 179 } 180 181 func joinErrors(errs []error) string { 182 es := make([]string, 0, len(errs)) 183 for _, e := range errs { 184 es = append(es, e.Error()) 185 } 186 return strings.Join(es, "; ") 187 } 188 189 // deleteRelease deletes the release and returns list of delete resources and manifests that were kept in the deletion process 190 func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, string, []error) { 191 var errs []error 192 caps, err := u.cfg.getCapabilities() 193 if err != nil { 194 return nil, rel.Manifest, []error{errors.Wrap(err, "could not get apiVersions from Kubernetes")} 195 } 196 197 manifests := releaseutil.SplitManifests(rel.Manifest) 198 _, files, err := releaseutil.SortManifests(manifests, caps.APIVersions, releaseutil.UninstallOrder) 199 if err != nil { 200 // We could instead just delete everything in no particular order. 201 // FIXME: One way to delete at this point would be to try a label-based 202 // deletion. The problem with this is that we could get a false positive 203 // and delete something that was not legitimately part of this release. 204 return nil, rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")} 205 } 206 207 filesToKeep, filesToDelete := filterManifestsToKeep(files) 208 var kept string 209 for _, f := range filesToKeep { 210 kept += "[" + f.Head.Kind + "] " + f.Head.Metadata.Name + "\n" 211 } 212 213 var builder strings.Builder 214 for _, file := range filesToDelete { 215 builder.WriteString("\n---\n" + file.Content) 216 } 217 218 resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false) 219 if err != nil { 220 return nil, "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")} 221 } 222 if len(resources) > 0 { 223 _, errs = u.cfg.KubeClient.Delete(resources) 224 } 225 return resources, kept, errs 226 }