1
16
17 package action
18
19 import (
20 "strings"
21 "time"
22
23 "github.com/pkg/errors"
24
25 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26
27 "helm.sh/helm/v3/pkg/chartutil"
28 "helm.sh/helm/v3/pkg/kube"
29 "helm.sh/helm/v3/pkg/release"
30 "helm.sh/helm/v3/pkg/releaseutil"
31 helmtime "helm.sh/helm/v3/pkg/time"
32 )
33
34
35
36
37 type Uninstall struct {
38 cfg *Configuration
39
40 DisableHooks bool
41 DryRun bool
42 IgnoreNotFound bool
43 KeepHistory bool
44 Wait bool
45 DeletionPropagation string
46 Timeout time.Duration
47 Description string
48 }
49
50
51 func NewUninstall(cfg *Configuration) *Uninstall {
52 return &Uninstall{
53 cfg: cfg,
54 }
55 }
56
57
58 func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) {
59 if err := u.cfg.KubeClient.IsReachable(); err != nil {
60 return nil, err
61 }
62
63 if u.DryRun {
64
65 r, err := u.cfg.releaseContent(name, 0)
66 if err != nil {
67 return &release.UninstallReleaseResponse{}, err
68 }
69 return &release.UninstallReleaseResponse{Release: r}, nil
70 }
71
72 if err := chartutil.ValidateReleaseName(name); err != nil {
73 return nil, errors.Errorf("uninstall: Release name is invalid: %s", name)
74 }
75
76 rels, err := u.cfg.Releases.History(name)
77 if err != nil {
78 if u.IgnoreNotFound {
79 return nil, nil
80 }
81 return nil, errors.Wrapf(err, "uninstall: Release not loaded: %s", name)
82 }
83 if len(rels) < 1 {
84 return nil, errMissingRelease
85 }
86
87 releaseutil.SortByRevision(rels)
88 rel := rels[len(rels)-1]
89
90
91
92 if rel.Info.Status == release.StatusUninstalled {
93 if !u.KeepHistory {
94 if err := u.purgeReleases(rels...); err != nil {
95 return nil, errors.Wrap(err, "uninstall: Failed to purge the release")
96 }
97 return &release.UninstallReleaseResponse{Release: rel}, nil
98 }
99 return nil, errors.Errorf("the release named %q is already deleted", name)
100 }
101
102 u.cfg.Log("uninstall: Deleting %s", name)
103 rel.Info.Status = release.StatusUninstalling
104 rel.Info.Deleted = helmtime.Now()
105 rel.Info.Description = "Deletion in progress (or silently failed)"
106 res := &release.UninstallReleaseResponse{Release: rel}
107
108 if !u.DisableHooks {
109 if err := u.cfg.execHook(rel, release.HookPreDelete, u.Timeout); err != nil {
110 return res, err
111 }
112 } else {
113 u.cfg.Log("delete hooks disabled for %s", name)
114 }
115
116
117
118 if err := u.cfg.Releases.Update(rel); err != nil {
119 u.cfg.Log("uninstall: Failed to store updated release: %s", err)
120 }
121
122 deletedResources, kept, errs := u.deleteRelease(rel)
123 if errs != nil {
124 u.cfg.Log("uninstall: Failed to delete release: %s", errs)
125 return nil, errors.Errorf("failed to delete release: %s", name)
126 }
127
128 if kept != "" {
129 kept = "These resources were kept due to the resource policy:\n" + kept
130 }
131 res.Info = kept
132
133 if u.Wait {
134 if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceExt); ok {
135 if err := kubeClient.WaitForDelete(deletedResources, u.Timeout); err != nil {
136 errs = append(errs, err)
137 }
138 }
139 }
140
141 if !u.DisableHooks {
142 if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout); err != nil {
143 errs = append(errs, err)
144 }
145 }
146
147 rel.Info.Status = release.StatusUninstalled
148 if len(u.Description) > 0 {
149 rel.Info.Description = u.Description
150 } else {
151 rel.Info.Description = "Uninstallation complete"
152 }
153
154 if !u.KeepHistory {
155 u.cfg.Log("purge requested for %s", name)
156 err := u.purgeReleases(rels...)
157 if err != nil {
158 errs = append(errs, errors.Wrap(err, "uninstall: Failed to purge the release"))
159 }
160
161
162 if len(errs) > 0 {
163 return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs))
164 }
165
166 return res, nil
167 }
168
169 if err := u.cfg.Releases.Update(rel); err != nil {
170 u.cfg.Log("uninstall: Failed to store updated release: %s", err)
171 }
172
173 if len(errs) > 0 {
174 return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs))
175 }
176 return res, nil
177 }
178
179 func (u *Uninstall) purgeReleases(rels ...*release.Release) error {
180 for _, rel := range rels {
181 if _, err := u.cfg.Releases.Delete(rel.Name, rel.Version); err != nil {
182 return err
183 }
184 }
185 return nil
186 }
187
188 func joinErrors(errs []error) string {
189 es := make([]string, 0, len(errs))
190 for _, e := range errs {
191 es = append(es, e.Error())
192 }
193 return strings.Join(es, "; ")
194 }
195
196
197 func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, string, []error) {
198 var errs []error
199 caps, err := u.cfg.getCapabilities()
200 if err != nil {
201 return nil, rel.Manifest, []error{errors.Wrap(err, "could not get apiVersions from Kubernetes")}
202 }
203
204 manifests := releaseutil.SplitManifests(rel.Manifest)
205 _, files, err := releaseutil.SortManifests(manifests, caps.APIVersions, releaseutil.UninstallOrder)
206 if err != nil {
207
208
209
210
211 return nil, rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")}
212 }
213
214 filesToKeep, filesToDelete := filterManifestsToKeep(files)
215 var kept string
216 for _, f := range filesToKeep {
217 kept += "[" + f.Head.Kind + "] " + f.Head.Metadata.Name + "\n"
218 }
219
220 var builder strings.Builder
221 for _, file := range filesToDelete {
222 builder.WriteString("\n---\n" + file.Content)
223 }
224
225 resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false)
226 if err != nil {
227 return nil, "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")}
228 }
229 if len(resources) > 0 {
230 if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceDeletionPropagation); ok {
231 _, errs = kubeClient.DeleteWithPropagationPolicy(resources, parseCascadingFlag(u.cfg, u.DeletionPropagation))
232 return resources, kept, errs
233 }
234 _, errs = u.cfg.KubeClient.Delete(resources)
235 }
236 return resources, kept, errs
237 }
238
239 func parseCascadingFlag(cfg *Configuration, cascadingFlag string) v1.DeletionPropagation {
240 switch cascadingFlag {
241 case "orphan":
242 return v1.DeletePropagationOrphan
243 case "foreground":
244 return v1.DeletePropagationForeground
245 case "background":
246 return v1.DeletePropagationBackground
247 default:
248 cfg.Log("uninstall: given cascade value: %s, defaulting to delete propagation background", cascadingFlag)
249 return v1.DeletePropagationBackground
250 }
251 }
252
View as plain text