github.com/azure-devops-engineer/helm@v3.0.0-alpha.2+incompatible/pkg/action/upgrade.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 "fmt" 22 "sort" 23 "time" 24 25 "github.com/pkg/errors" 26 27 "helm.sh/helm/pkg/chart" 28 "helm.sh/helm/pkg/chartutil" 29 "helm.sh/helm/pkg/hooks" 30 "helm.sh/helm/pkg/kube" 31 "helm.sh/helm/pkg/release" 32 "helm.sh/helm/pkg/releaseutil" 33 ) 34 35 // Upgrade is the action for upgrading releases. 36 // 37 // It provides the implementation of 'helm upgrade'. 38 type Upgrade struct { 39 cfg *Configuration 40 41 ChartPathOptions 42 ValueOptions 43 44 Install bool 45 Devel bool 46 Namespace string 47 Timeout time.Duration 48 Wait bool 49 DisableHooks bool 50 DryRun bool 51 Force bool 52 ResetValues bool 53 ReuseValues bool 54 // Recreate will (if true) recreate pods after a rollback. 55 Recreate bool 56 // MaxHistory limits the maximum number of revisions saved per release 57 MaxHistory int 58 Atomic bool 59 } 60 61 // NewUpgrade creates a new Upgrade object with the given configuration. 62 func NewUpgrade(cfg *Configuration) *Upgrade { 63 return &Upgrade{ 64 cfg: cfg, 65 } 66 } 67 68 // Run executes the upgrade on the given release. 69 func (u *Upgrade) Run(name string, chart *chart.Chart) (*release.Release, error) { 70 if err := chartutil.ProcessDependencies(chart, u.rawValues); err != nil { 71 return nil, err 72 } 73 74 // Make sure if Atomic is set, that wait is set as well. This makes it so 75 // the user doesn't have to specify both 76 u.Wait = u.Wait || u.Atomic 77 78 if err := validateReleaseName(name); err != nil { 79 return nil, errors.Errorf("upgradeRelease: Release name is invalid: %s", name) 80 } 81 u.cfg.Log("preparing upgrade for %s", name) 82 currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart) 83 if err != nil { 84 return nil, err 85 } 86 87 u.cfg.Releases.MaxHistory = u.MaxHistory 88 89 if !u.DryRun { 90 u.cfg.Log("creating upgraded release for %s", name) 91 if err := u.cfg.Releases.Create(upgradedRelease); err != nil { 92 return nil, err 93 } 94 } 95 96 u.cfg.Log("performing update for %s", name) 97 res, err := u.performUpgrade(currentRelease, upgradedRelease) 98 if err != nil { 99 return res, err 100 } 101 102 if !u.DryRun { 103 u.cfg.Log("updating status for upgraded release for %s", name) 104 if err := u.cfg.Releases.Update(upgradedRelease); err != nil { 105 return res, err 106 } 107 } 108 109 return res, nil 110 } 111 112 func validateReleaseName(releaseName string) error { 113 if releaseName == "" { 114 return errMissingRelease 115 } 116 117 if !ValidName.MatchString(releaseName) || (len(releaseName) > releaseNameMaxLen) { 118 return errInvalidName 119 } 120 121 return nil 122 } 123 124 // prepareUpgrade builds an upgraded release for an upgrade operation. 125 func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Release, *release.Release, error) { 126 if chart == nil { 127 return nil, nil, errMissingChart 128 } 129 130 // finds the deployed release with the given name 131 currentRelease, err := u.cfg.Releases.Deployed(name) 132 if err != nil { 133 return nil, nil, err 134 } 135 136 // determine if values will be reused 137 if err := u.reuseValues(chart, currentRelease); err != nil { 138 return nil, nil, err 139 } 140 141 // finds the non-deleted release with the given name 142 lastRelease, err := u.cfg.Releases.Last(name) 143 if err != nil { 144 return nil, nil, err 145 } 146 147 // Increment revision count. This is passed to templates, and also stored on 148 // the release object. 149 revision := lastRelease.Version + 1 150 151 options := chartutil.ReleaseOptions{ 152 Name: name, 153 Namespace: currentRelease.Namespace, 154 IsUpgrade: true, 155 } 156 157 caps, err := u.cfg.getCapabilities() 158 if err != nil { 159 return nil, nil, err 160 } 161 valuesToRender, err := chartutil.ToRenderValues(chart, u.rawValues, options, caps) 162 if err != nil { 163 return nil, nil, err 164 } 165 166 hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "") 167 if err != nil { 168 return nil, nil, err 169 } 170 171 // Store an upgraded release. 172 upgradedRelease := &release.Release{ 173 Name: name, 174 Namespace: currentRelease.Namespace, 175 Chart: chart, 176 Config: u.rawValues, 177 Info: &release.Info{ 178 FirstDeployed: currentRelease.Info.FirstDeployed, 179 LastDeployed: Timestamper(), 180 Status: release.StatusPendingUpgrade, 181 Description: "Preparing upgrade", // This should be overwritten later. 182 }, 183 Version: revision, 184 Manifest: manifestDoc.String(), 185 Hooks: hooks, 186 } 187 188 if len(notesTxt) > 0 { 189 upgradedRelease.Info.Notes = notesTxt 190 } 191 err = validateManifest(u.cfg.KubeClient, manifestDoc.Bytes()) 192 return currentRelease, upgradedRelease, err 193 } 194 195 func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Release) (*release.Release, error) { 196 if u.DryRun { 197 u.cfg.Log("dry run for %s", upgradedRelease.Name) 198 upgradedRelease.Info.Description = "Dry run complete" 199 return upgradedRelease, nil 200 } 201 202 // pre-upgrade hooks 203 if !u.DisableHooks { 204 if err := u.execHook(upgradedRelease.Hooks, hooks.PreUpgrade); err != nil { 205 return u.failRelease(upgradedRelease, fmt.Errorf("pre-upgrade hooks failed: %s", err)) 206 } 207 } else { 208 u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name) 209 } 210 if err := u.upgradeRelease(originalRelease, upgradedRelease); err != nil { 211 u.cfg.recordRelease(originalRelease) 212 return u.failRelease(upgradedRelease, err) 213 } 214 215 if u.Wait { 216 buf := bytes.NewBufferString(upgradedRelease.Manifest) 217 if err := u.cfg.KubeClient.Wait(buf, u.Timeout); err != nil { 218 u.cfg.recordRelease(originalRelease) 219 return u.failRelease(upgradedRelease, err) 220 } 221 } 222 223 // post-upgrade hooks 224 if !u.DisableHooks { 225 if err := u.execHook(upgradedRelease.Hooks, hooks.PostUpgrade); err != nil { 226 return u.failRelease(upgradedRelease, fmt.Errorf("post-upgrade hooks failed: %s", err)) 227 } 228 } 229 230 originalRelease.Info.Status = release.StatusSuperseded 231 u.cfg.recordRelease(originalRelease) 232 233 upgradedRelease.Info.Status = release.StatusDeployed 234 upgradedRelease.Info.Description = "Upgrade complete" 235 236 return upgradedRelease, nil 237 } 238 239 func (u *Upgrade) failRelease(rel *release.Release, err error) (*release.Release, error) { 240 msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err) 241 u.cfg.Log("warning: %s", msg) 242 243 rel.Info.Status = release.StatusFailed 244 rel.Info.Description = msg 245 u.cfg.recordRelease(rel) 246 if u.Atomic { 247 u.cfg.Log("Upgrade failed and atomic is set, rolling back to last successful release") 248 249 // As a protection, get the last successful release before rollback. 250 // If there are no successful releases, bail out 251 hist := NewHistory(u.cfg) 252 fullHistory, herr := hist.Run(rel.Name) 253 if herr != nil { 254 return rel, errors.Wrapf(herr, "an error occurred while finding last successful release. original upgrade error: %s", err) 255 } 256 257 // There isn't a way to tell if a previous release was successful, but 258 // generally failed releases do not get superseded unless the next 259 // release is successful, so this should be relatively safe 260 filteredHistory := releaseutil.FilterFunc(func(r *release.Release) bool { 261 return r.Info.Status == release.StatusSuperseded || r.Info.Status == release.StatusDeployed 262 }).Filter(fullHistory) 263 if len(filteredHistory) == 0 { 264 return rel, errors.Wrap(err, "unable to find a previously successful release when attempting to rollback. original upgrade error") 265 } 266 267 releaseutil.Reverse(filteredHistory, releaseutil.SortByRevision) 268 269 rollin := NewRollback(u.cfg) 270 rollin.Version = filteredHistory[0].Version 271 rollin.Wait = true 272 rollin.DisableHooks = u.DisableHooks 273 rollin.Recreate = u.Recreate 274 rollin.Force = u.Force 275 rollin.Timeout = u.Timeout 276 if _, rollErr := rollin.Run(rel.Name); rollErr != nil { 277 return rel, errors.Wrapf(rollErr, "an error occurred while rolling back the release. original upgrade error: %s", err) 278 } 279 return rel, errors.Wrapf(err, "release %s failed, and has been rolled back due to atomic being set", rel.Name) 280 } 281 282 return rel, err 283 } 284 285 // upgradeRelease performs an upgrade from current to target release 286 func (u *Upgrade) upgradeRelease(current, target *release.Release) error { 287 cm := bytes.NewBufferString(current.Manifest) 288 tm := bytes.NewBufferString(target.Manifest) 289 // TODO add wait 290 return u.cfg.KubeClient.Update(cm, tm, u.Force, u.Recreate) 291 } 292 293 // reuseValues copies values from the current release to a new release if the 294 // new release does not have any values. 295 // 296 // If the request already has values, or if there are no values in the current 297 // release, this does nothing. 298 // 299 // This is skipped if the u.ResetValues flag is set, in which case the 300 // request values are not altered. 301 func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release) error { 302 if u.ResetValues { 303 // If ResetValues is set, we comletely ignore current.Config. 304 u.cfg.Log("resetting values to the chart's original version") 305 return nil 306 } 307 308 // If the ReuseValues flag is set, we always copy the old values over the new config's values. 309 if u.ReuseValues { 310 u.cfg.Log("reusing the old release's values") 311 312 // We have to regenerate the old coalesced values: 313 oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) 314 if err != nil { 315 return errors.Wrap(err, "failed to rebuild old values") 316 } 317 318 u.rawValues = chartutil.CoalesceTables(current.Config, u.rawValues) 319 320 chart.Values = oldVals 321 322 return nil 323 } 324 325 if len(u.rawValues) == 0 && len(current.Config) > 0 { 326 u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version) 327 u.rawValues = current.Config 328 } 329 return nil 330 } 331 332 func validateManifest(c kube.Interface, manifest []byte) error { 333 _, err := c.Build(bytes.NewReader(manifest)) 334 return err 335 } 336 337 // execHook executes all of the hooks for the given hook event. 338 func (u *Upgrade) execHook(hs []*release.Hook, hook string) error { 339 timeout := u.Timeout 340 executingHooks := []*release.Hook{} 341 342 for _, h := range hs { 343 for _, e := range h.Events { 344 if string(e) == hook { 345 executingHooks = append(executingHooks, h) 346 } 347 } 348 } 349 350 sort.Sort(hookByWeight(executingHooks)) 351 for _, h := range executingHooks { 352 if err := deleteHookByPolicy(u.cfg, h, hooks.BeforeHookCreation); err != nil { 353 return err 354 } 355 356 b := bytes.NewBufferString(h.Manifest) 357 if err := u.cfg.KubeClient.Create(b); err != nil { 358 return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path) 359 } 360 b.Reset() 361 b.WriteString(h.Manifest) 362 363 if err := u.cfg.KubeClient.WatchUntilReady(b, timeout); err != nil { 364 // If a hook is failed, checkout the annotation of the hook to determine whether the hook should be deleted 365 // under failed condition. If so, then clear the corresponding resource object in the hook 366 if err := deleteHookByPolicy(u.cfg, h, hooks.HookFailed); err != nil { 367 return err 368 } 369 return err 370 } 371 } 372 373 // If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted 374 // under succeeded condition. If so, then clear the corresponding resource object in each hook 375 for _, h := range executingHooks { 376 if err := deleteHookByPolicy(u.cfg, h, hooks.HookSucceeded); err != nil { 377 return err 378 } 379 h.LastRun = time.Now() 380 } 381 382 return nil 383 }