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