github.com/uhthomas/helm@v3.0.0-beta.3+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 err := u.cfg.KubeClient.IsReachable(); err != nil { 51 return nil, err 52 } 53 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.cfg.execHook(rel, release.HookPreDelete, u.Timeout); 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.cfg.execHook(rel, release.HookPostDelete, u.Timeout); 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 // deleteRelease deletes the release and returns manifests that were kept in the deletion process 165 func (u *Uninstall) deleteRelease(rel *release.Release) (string, []error) { 166 caps, err := u.cfg.getCapabilities() 167 if err != nil { 168 return rel.Manifest, []error{errors.Wrap(err, "could not get apiVersions from Kubernetes")} 169 } 170 171 manifests := releaseutil.SplitManifests(rel.Manifest) 172 _, files, err := releaseutil.SortManifests(manifests, caps.APIVersions, releaseutil.UninstallOrder) 173 if err != nil { 174 // We could instead just delete everything in no particular order. 175 // FIXME: One way to delete at this point would be to try a label-based 176 // deletion. The problem with this is that we could get a false positive 177 // and delete something that was not legitimately part of this release. 178 return rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")} 179 } 180 181 filesToKeep, filesToDelete := filterManifestsToKeep(files) 182 var kept string 183 for _, f := range filesToKeep { 184 kept += f.Name + "\n" 185 } 186 187 var builder strings.Builder 188 for _, file := range filesToDelete { 189 builder.WriteString("\n---\n" + file.Content) 190 } 191 resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String())) 192 if err != nil { 193 return "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")} 194 } 195 196 _, errs := u.cfg.KubeClient.Delete(resources) 197 return kept, errs 198 }