github.com/koderover/helm@v2.17.0+incompatible/pkg/tiller/release_update.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 tiller 18 19 import ( 20 "fmt" 21 "strings" 22 23 ctx "golang.org/x/net/context" 24 25 "k8s.io/helm/pkg/chartutil" 26 "k8s.io/helm/pkg/hooks" 27 "k8s.io/helm/pkg/proto/hapi/release" 28 "k8s.io/helm/pkg/proto/hapi/services" 29 "k8s.io/helm/pkg/timeconv" 30 ) 31 32 // UpdateRelease takes an existing release and new information, and upgrades the release. 33 func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { 34 if err := validateReleaseName(req.Name); err != nil { 35 s.Log("updateRelease: Release name is invalid: %s", req.Name) 36 return nil, err 37 } 38 s.Log("preparing update for %s", req.Name) 39 currentRelease, updatedRelease, err := s.prepareUpdate(req) 40 if err != nil { 41 s.Log("failed to prepare update: %s", err) 42 if req.Force { 43 // Use the --force, Luke. 44 s.Log("performing force update for %s", req.Name) 45 return s.performUpdateForce(req) 46 } 47 return nil, err 48 } 49 50 if !req.DryRun { 51 s.Log("creating updated release for %s", req.Name) 52 if err := s.env.Releases.Create(updatedRelease); err != nil { 53 return nil, err 54 } 55 } 56 57 s.Log("performing update for %s", req.Name) 58 res, err := s.performUpdate(currentRelease, updatedRelease, req) 59 if err != nil { 60 return res, err 61 } 62 63 if !req.DryRun { 64 s.Log("updating status for updated release for %s", req.Name) 65 if err := s.env.Releases.Update(updatedRelease); err != nil { 66 return res, err 67 } 68 } 69 70 return res, nil 71 } 72 73 // prepareUpdate builds an updated release for an update operation. 74 func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) { 75 if req.Chart == nil { 76 return nil, nil, errMissingChart 77 } 78 79 // finds the deployed release with the given name 80 currentRelease, err := s.env.Releases.Deployed(req.Name) 81 if err != nil { 82 return nil, nil, err 83 } 84 85 // determine if values will be reused 86 if err := s.reuseValues(req, currentRelease); err != nil { 87 return nil, nil, err 88 } 89 90 // finds the non-deleted release with the given name 91 lastRelease, err := s.env.Releases.Last(req.Name) 92 if err != nil { 93 return nil, nil, err 94 } 95 96 // Concurrent `helm upgrade`s will either fail here with `errPending` or 97 // when creating the release with "already exists". This should act as a 98 // pessimistic lock. 99 sc := lastRelease.Info.Status.Code 100 if sc == release.Status_PENDING_INSTALL || sc == release.Status_PENDING_UPGRADE || sc == release.Status_PENDING_ROLLBACK { 101 return nil, nil, errPending 102 } 103 104 // Increment revision count. This is passed to templates, and also stored on 105 // the release object. 106 revision := lastRelease.Version + 1 107 108 ts := timeconv.Now() 109 options := chartutil.ReleaseOptions{ 110 Name: req.Name, 111 Time: ts, 112 Namespace: currentRelease.Namespace, 113 IsUpgrade: true, 114 Revision: int(revision), 115 } 116 117 caps, err := capabilities(s.clientset.Discovery()) 118 if err != nil { 119 return nil, nil, err 120 } 121 valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) 122 if err != nil { 123 return nil, nil, err 124 } 125 126 hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, req.SubNotes, caps.APIVersions) 127 if err != nil { 128 return nil, nil, err 129 } 130 131 // Store an updated release. 132 updatedRelease := &release.Release{ 133 Name: req.Name, 134 Namespace: currentRelease.Namespace, 135 Chart: req.Chart, 136 Config: req.Values, 137 Info: &release.Info{ 138 FirstDeployed: currentRelease.Info.FirstDeployed, 139 LastDeployed: ts, 140 Status: &release.Status{Code: release.Status_PENDING_UPGRADE}, 141 Description: "Preparing upgrade", // This should be overwritten later. 142 }, 143 Version: revision, 144 Manifest: manifestDoc.String(), 145 Hooks: hooks, 146 } 147 148 if len(notesTxt) > 0 { 149 updatedRelease.Info.Status.Notes = notesTxt 150 } 151 err = validateManifest(s.env.KubeClient, currentRelease.Namespace, manifestDoc.Bytes()) 152 return currentRelease, updatedRelease, err 153 } 154 155 // performUpdateForce performs the same action as a `helm delete && helm install --replace`. 156 func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { 157 // find the last release with the given name 158 oldRelease, err := s.env.Releases.Last(req.Name) 159 if err != nil { 160 return nil, err 161 } 162 163 res := &services.UpdateReleaseResponse{} 164 165 newRelease, err := s.prepareRelease(&services.InstallReleaseRequest{ 166 Chart: req.Chart, 167 Values: req.Values, 168 DryRun: req.DryRun, 169 Name: req.Name, 170 DisableHooks: req.DisableHooks, 171 Namespace: oldRelease.Namespace, 172 ReuseName: true, 173 Timeout: req.Timeout, 174 Wait: req.Wait, 175 }) 176 if err != nil { 177 s.Log("failed update prepare step: %s", err) 178 // On dry run, append the manifest contents to a failed release. This is 179 // a stop-gap until we can revisit an error backchannel post-2.0. 180 if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") { 181 err = fmt.Errorf("%s\n%s", err, newRelease.Manifest) 182 } 183 return res, err 184 } 185 186 // update new release with next revision number so as to append to the old release's history 187 newRelease.Version = oldRelease.Version + 1 188 res.Release = newRelease 189 190 if req.DryRun { 191 s.Log("dry run for %s", newRelease.Name) 192 res.Release.Info.Description = "Dry run complete" 193 return res, nil 194 } 195 196 // From here on out, the release is considered to be in Status_DELETING or Status_DELETED 197 // state. There is no turning back. 198 oldRelease.Info.Status.Code = release.Status_DELETING 199 oldRelease.Info.Deleted = timeconv.Now() 200 oldRelease.Info.Description = "Deletion in progress (or silently failed)" 201 s.recordRelease(oldRelease, true) 202 203 // pre-delete hooks 204 if !req.DisableHooks { 205 if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PreDelete, req.Timeout); err != nil { 206 return res, err 207 } 208 } else { 209 s.Log("hooks disabled for %s", req.Name) 210 } 211 212 // delete manifests from the old release 213 _, errs := s.ReleaseModule.Delete(oldRelease, nil, s.env) 214 215 oldRelease.Info.Status.Code = release.Status_DELETED 216 oldRelease.Info.Description = "Deletion complete" 217 s.recordRelease(oldRelease, true) 218 219 if len(errs) > 0 { 220 es := make([]string, 0, len(errs)) 221 for _, e := range errs { 222 s.Log("error: %v", e) 223 es = append(es, e.Error()) 224 } 225 return res, fmt.Errorf("Upgrade --force successfully deleted the previous release, but encountered %d error(s) and cannot continue: %s", len(es), strings.Join(es, "; ")) 226 } 227 228 // post-delete hooks 229 if !req.DisableHooks { 230 if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PostDelete, req.Timeout); err != nil { 231 return res, err 232 } 233 } 234 235 // pre-install hooks 236 if !req.DisableHooks { 237 if err := s.execHook(newRelease.Hooks, newRelease.Name, newRelease.Namespace, hooks.PreInstall, req.Timeout); err != nil { 238 return res, err 239 } 240 } 241 242 s.recordRelease(newRelease, false) 243 if err := s.ReleaseModule.Update(oldRelease, newRelease, req, s.env); err != nil { 244 msg := fmt.Sprintf("Upgrade %q failed: %s", newRelease.Name, err) 245 s.Log("warning: %s", msg) 246 newRelease.Info.Status.Code = release.Status_FAILED 247 newRelease.Info.Description = msg 248 s.recordRelease(newRelease, true) 249 return res, err 250 } 251 252 // post-install hooks 253 if !req.DisableHooks { 254 if err := s.execHook(newRelease.Hooks, newRelease.Name, newRelease.Namespace, hooks.PostInstall, req.Timeout); err != nil { 255 msg := fmt.Sprintf("Release %q failed post-install: %s", newRelease.Name, err) 256 s.Log("warning: %s", msg) 257 newRelease.Info.Status.Code = release.Status_FAILED 258 newRelease.Info.Description = msg 259 s.recordRelease(newRelease, true) 260 return res, err 261 } 262 } 263 264 newRelease.Info.Status.Code = release.Status_DEPLOYED 265 if req.Description == "" { 266 newRelease.Info.Description = "Upgrade complete" 267 } else { 268 newRelease.Info.Description = req.Description 269 } 270 s.recordRelease(newRelease, true) 271 272 return res, nil 273 } 274 275 func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { 276 res := &services.UpdateReleaseResponse{Release: updatedRelease} 277 278 if req.DryRun { 279 s.Log("dry run for %s", updatedRelease.Name) 280 res.Release.Info.Description = "Dry run complete" 281 return res, nil 282 } 283 284 // pre-upgrade hooks 285 if !req.DisableHooks { 286 if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil { 287 return res, err 288 } 289 } else { 290 s.Log("update hooks disabled for %s", req.Name) 291 } 292 if err := s.ReleaseModule.Update(originalRelease, updatedRelease, req, s.env); err != nil { 293 msg := fmt.Sprintf("Upgrade %q failed: %s", updatedRelease.Name, err) 294 s.Log("warning: %s", msg) 295 updatedRelease.Info.Status.Code = release.Status_FAILED 296 updatedRelease.Info.Description = msg 297 s.recordRelease(originalRelease, true) 298 s.recordRelease(updatedRelease, true) 299 return res, err 300 } 301 302 // post-upgrade hooks 303 if !req.DisableHooks { 304 if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PostUpgrade, req.Timeout); err != nil { 305 return res, err 306 } 307 } 308 309 originalRelease.Info.Status.Code = release.Status_SUPERSEDED 310 s.recordRelease(originalRelease, true) 311 312 updatedRelease.Info.Status.Code = release.Status_DEPLOYED 313 if req.Description == "" { 314 updatedRelease.Info.Description = "Upgrade complete" 315 } else { 316 updatedRelease.Info.Description = req.Description 317 } 318 319 return res, nil 320 }