github.com/appscode/helm@v3.0.0-alpha.1+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 ) 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 ValueOptions 42 43 Install bool 44 Devel bool 45 Namespace string 46 Timeout time.Duration 47 Wait bool 48 // Values is a string containing (unparsed) YAML values. 49 Values map[string]interface{} 50 DisableHooks bool 51 DryRun bool 52 Force bool 53 ResetValues bool 54 ReuseValues bool 55 // Recreate will (if true) recreate pods after a rollback. 56 Recreate bool 57 // MaxHistory limits the maximum number of revisions saved per release 58 MaxHistory int 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.Values); err != nil { 71 return nil, err 72 } 73 74 if err := validateReleaseName(name); err != nil { 75 return nil, errors.Errorf("upgradeRelease: Release name is invalid: %s", name) 76 } 77 u.cfg.Log("preparing upgrade for %s", name) 78 currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart) 79 if err != nil { 80 return nil, err 81 } 82 83 u.cfg.Releases.MaxHistory = u.MaxHistory 84 85 if !u.DryRun { 86 u.cfg.Log("creating upgraded release for %s", name) 87 if err := u.cfg.Releases.Create(upgradedRelease); err != nil { 88 return nil, err 89 } 90 } 91 92 u.cfg.Log("performing update for %s", name) 93 res, err := u.performUpgrade(currentRelease, upgradedRelease) 94 if err != nil { 95 return res, err 96 } 97 98 if !u.DryRun { 99 u.cfg.Log("updating status for upgraded release for %s", name) 100 if err := u.cfg.Releases.Update(upgradedRelease); err != nil { 101 return res, err 102 } 103 } 104 105 return res, nil 106 } 107 108 func validateReleaseName(releaseName string) error { 109 if releaseName == "" { 110 return errMissingRelease 111 } 112 113 if !ValidName.MatchString(releaseName) || (len(releaseName) > releaseNameMaxLen) { 114 return errInvalidName 115 } 116 117 return nil 118 } 119 120 // prepareUpgrade builds an upgraded release for an upgrade operation. 121 func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Release, *release.Release, error) { 122 if chart == nil { 123 return nil, nil, errMissingChart 124 } 125 126 // finds the deployed release with the given name 127 currentRelease, err := u.cfg.Releases.Deployed(name) 128 if err != nil { 129 return nil, nil, err 130 } 131 132 // determine if values will be reused 133 if err := u.reuseValues(chart, currentRelease); err != nil { 134 return nil, nil, err 135 } 136 137 // finds the non-deleted release with the given name 138 lastRelease, err := u.cfg.Releases.Last(name) 139 if err != nil { 140 return nil, nil, err 141 } 142 143 // Increment revision count. This is passed to templates, and also stored on 144 // the release object. 145 revision := lastRelease.Version + 1 146 147 options := chartutil.ReleaseOptions{ 148 Name: name, 149 Namespace: currentRelease.Namespace, 150 IsUpgrade: true, 151 } 152 153 caps, err := u.cfg.getCapabilities() 154 if err != nil { 155 return nil, nil, err 156 } 157 valuesToRender, err := chartutil.ToRenderValues(chart, u.Values, options, caps) 158 if err != nil { 159 return nil, nil, err 160 } 161 162 hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender) 163 if err != nil { 164 return nil, nil, err 165 } 166 167 // Store an upgraded release. 168 upgradedRelease := &release.Release{ 169 Name: name, 170 Namespace: currentRelease.Namespace, 171 Chart: chart, 172 Config: u.Values, 173 Info: &release.Info{ 174 FirstDeployed: currentRelease.Info.FirstDeployed, 175 LastDeployed: Timestamper(), 176 Status: release.StatusPendingUpgrade, 177 Description: "Preparing upgrade", // This should be overwritten later. 178 }, 179 Version: revision, 180 Manifest: manifestDoc.String(), 181 Hooks: hooks, 182 } 183 184 if len(notesTxt) > 0 { 185 upgradedRelease.Info.Notes = notesTxt 186 } 187 err = validateManifest(u.cfg.KubeClient, manifestDoc.Bytes()) 188 return currentRelease, upgradedRelease, err 189 } 190 191 func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Release) (*release.Release, error) { 192 if u.DryRun { 193 u.cfg.Log("dry run for %s", upgradedRelease.Name) 194 upgradedRelease.Info.Description = "Dry run complete" 195 return upgradedRelease, nil 196 } 197 198 // pre-upgrade hooks 199 if !u.DisableHooks { 200 if err := u.execHook(upgradedRelease.Hooks, hooks.PreUpgrade); err != nil { 201 return upgradedRelease, err 202 } 203 } else { 204 u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name) 205 } 206 if err := u.upgradeRelease(originalRelease, upgradedRelease); err != nil { 207 msg := fmt.Sprintf("Upgrade %q failed: %s", upgradedRelease.Name, err) 208 u.cfg.Log("warning: %s", msg) 209 upgradedRelease.Info.Status = release.StatusFailed 210 upgradedRelease.Info.Description = msg 211 u.cfg.recordRelease(originalRelease) 212 u.cfg.recordRelease(upgradedRelease) 213 return upgradedRelease, err 214 } 215 216 // post-upgrade hooks 217 if !u.DisableHooks { 218 if err := u.execHook(upgradedRelease.Hooks, hooks.PostUpgrade); err != nil { 219 return upgradedRelease, err 220 } 221 } 222 223 originalRelease.Info.Status = release.StatusSuperseded 224 u.cfg.recordRelease(originalRelease) 225 226 upgradedRelease.Info.Status = release.StatusDeployed 227 upgradedRelease.Info.Description = "Upgrade complete" 228 229 return upgradedRelease, nil 230 } 231 232 // upgradeRelease performs an upgrade from current to target release 233 func (u *Upgrade) upgradeRelease(current, target *release.Release) error { 234 cm := bytes.NewBufferString(current.Manifest) 235 tm := bytes.NewBufferString(target.Manifest) 236 // TODO add wait 237 return u.cfg.KubeClient.Update(cm, tm, u.Force, u.Recreate) 238 } 239 240 // reuseValues copies values from the current release to a new release if the 241 // new release does not have any values. 242 // 243 // If the request already has values, or if there are no values in the current 244 // release, this does nothing. 245 // 246 // This is skipped if the u.ResetValues flag is set, in which case the 247 // request values are not altered. 248 func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release) error { 249 if u.ResetValues { 250 // If ResetValues is set, we comletely ignore current.Config. 251 u.cfg.Log("resetting values to the chart's original version") 252 return nil 253 } 254 255 // If the ReuseValues flag is set, we always copy the old values over the new config's values. 256 if u.ReuseValues { 257 u.cfg.Log("reusing the old release's values") 258 259 // We have to regenerate the old coalesced values: 260 oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) 261 if err != nil { 262 return errors.Wrap(err, "failed to rebuild old values") 263 } 264 265 u.Values = chartutil.CoalesceTables(current.Config, u.Values) 266 267 chart.Values = oldVals 268 269 return nil 270 } 271 272 if len(u.Values) == 0 && len(current.Config) > 0 { 273 u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version) 274 u.Values = current.Config 275 } 276 return nil 277 } 278 279 func validateManifest(c kube.Interface, manifest []byte) error { 280 _, err := c.Build(bytes.NewReader(manifest)) 281 return err 282 } 283 284 // execHook executes all of the hooks for the given hook event. 285 func (u *Upgrade) execHook(hs []*release.Hook, hook string) error { 286 timeout := u.Timeout 287 executingHooks := []*release.Hook{} 288 289 for _, h := range hs { 290 for _, e := range h.Events { 291 if string(e) == hook { 292 executingHooks = append(executingHooks, h) 293 } 294 } 295 } 296 297 sort.Sort(hookByWeight(executingHooks)) 298 299 for _, h := range executingHooks { 300 if err := deleteHookByPolicy(u.cfg, h, hooks.BeforeHookCreation); err != nil { 301 return err 302 } 303 304 b := bytes.NewBufferString(h.Manifest) 305 if err := u.cfg.KubeClient.Create(b); err != nil { 306 return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path) 307 } 308 b.Reset() 309 b.WriteString(h.Manifest) 310 311 if err := u.cfg.KubeClient.WatchUntilReady(b, timeout); err != nil { 312 // If a hook is failed, checkout the annotation of the hook to determine whether the hook should be deleted 313 // under failed condition. If so, then clear the corresponding resource object in the hook 314 if err := deleteHookByPolicy(u.cfg, h, hooks.HookFailed); err != nil { 315 return err 316 } 317 return err 318 } 319 } 320 321 // If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted 322 // under succeeded condition. If so, then clear the corresponding resource object in each hook 323 for _, h := range executingHooks { 324 if err := deleteHookByPolicy(u.cfg, h, hooks.HookSucceeded); err != nil { 325 return err 326 } 327 h.LastRun = time.Now() 328 } 329 330 return nil 331 }