github.com/sgoings/helm@v2.0.0-alpha.2.0.20170406211108-734e92851ac3+incompatible/pkg/tiller/release_server.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors All rights reserved. 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 "bytes" 21 "errors" 22 "fmt" 23 "log" 24 "path" 25 "regexp" 26 "strings" 27 28 "github.com/technosophos/moniker" 29 ctx "golang.org/x/net/context" 30 "k8s.io/kubernetes/pkg/api/unversioned" 31 "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" 32 "k8s.io/kubernetes/pkg/client/typed/discovery" 33 34 "k8s.io/helm/pkg/chartutil" 35 "k8s.io/helm/pkg/hooks" 36 "k8s.io/helm/pkg/kube" 37 "k8s.io/helm/pkg/proto/hapi/chart" 38 "k8s.io/helm/pkg/proto/hapi/release" 39 "k8s.io/helm/pkg/proto/hapi/services" 40 reltesting "k8s.io/helm/pkg/releasetesting" 41 relutil "k8s.io/helm/pkg/releaseutil" 42 "k8s.io/helm/pkg/storage/driver" 43 "k8s.io/helm/pkg/tiller/environment" 44 "k8s.io/helm/pkg/timeconv" 45 "k8s.io/helm/pkg/version" 46 ) 47 48 // releaseNameMaxLen is the maximum length of a release name. 49 // 50 // As of Kubernetes 1.4, the max limit on a name is 63 chars. We reserve 10 for 51 // charts to add data. Effectively, that gives us 53 chars. 52 // See https://github.com/kubernetes/helm/issues/1528 53 const releaseNameMaxLen = 53 54 55 // NOTESFILE_SUFFIX that we want to treat special. It goes through the templating engine 56 // but it's not a yaml file (resource) hence can't have hooks, etc. And the user actually 57 // wants to see this file after rendering in the status command. However, it must be a suffix 58 // since there can be filepath in front of it. 59 const notesFileSuffix = "NOTES.txt" 60 61 var ( 62 // errMissingChart indicates that a chart was not provided. 63 errMissingChart = errors.New("no chart provided") 64 // errMissingRelease indicates that a release (name) was not provided. 65 errMissingRelease = errors.New("no release provided") 66 // errInvalidRevision indicates that an invalid release revision number was provided. 67 errInvalidRevision = errors.New("invalid release revision") 68 ) 69 70 // ListDefaultLimit is the default limit for number of items returned in a list. 71 var ListDefaultLimit int64 = 512 72 73 // ValidName is a regular expression for names. 74 // 75 // According to the Kubernetes help text, the regular expression it uses is: 76 // 77 // (([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])? 78 // 79 // We modified that. First, we added start and end delimiters. Second, we changed 80 // the final ? to + to require that the pattern match at least once. This modification 81 // prevents an empty string from matching. 82 var ValidName = regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$") 83 84 // ReleaseServer implements the server-side gRPC endpoint for the HAPI services. 85 type ReleaseServer struct { 86 env *environment.Environment 87 clientset internalclientset.Interface 88 } 89 90 // NewReleaseServer creates a new release server. 91 func NewReleaseServer(env *environment.Environment, clientset internalclientset.Interface) *ReleaseServer { 92 return &ReleaseServer{ 93 env: env, 94 clientset: clientset, 95 } 96 } 97 98 // ListReleases lists the releases found by the server. 99 func (s *ReleaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error { 100 if len(req.StatusCodes) == 0 { 101 req.StatusCodes = []release.Status_Code{release.Status_DEPLOYED} 102 } 103 104 //rels, err := s.env.Releases.ListDeployed() 105 rels, err := s.env.Releases.ListFilterAll(func(r *release.Release) bool { 106 for _, sc := range req.StatusCodes { 107 if sc == r.Info.Status.Code { 108 return true 109 } 110 } 111 return false 112 }) 113 if err != nil { 114 return err 115 } 116 117 if req.Namespace != "" { 118 rels, err = filterByNamespace(req.Namespace, rels) 119 if err != nil { 120 return err 121 } 122 } 123 124 if len(req.Filter) != 0 { 125 rels, err = filterReleases(req.Filter, rels) 126 if err != nil { 127 return err 128 } 129 } 130 131 total := int64(len(rels)) 132 133 switch req.SortBy { 134 case services.ListSort_NAME: 135 relutil.SortByName(rels) 136 case services.ListSort_LAST_RELEASED: 137 relutil.SortByDate(rels) 138 } 139 140 if req.SortOrder == services.ListSort_DESC { 141 ll := len(rels) 142 rr := make([]*release.Release, ll) 143 for i, item := range rels { 144 rr[ll-i-1] = item 145 } 146 rels = rr 147 } 148 149 l := int64(len(rels)) 150 if req.Offset != "" { 151 152 i := -1 153 for ii, cur := range rels { 154 if cur.Name == req.Offset { 155 i = ii 156 } 157 } 158 if i == -1 { 159 return fmt.Errorf("offset %q not found", req.Offset) 160 } 161 162 if len(rels) < i { 163 return fmt.Errorf("no items after %q", req.Offset) 164 } 165 166 rels = rels[i:] 167 l = int64(len(rels)) 168 } 169 170 if req.Limit == 0 { 171 req.Limit = ListDefaultLimit 172 } 173 174 next := "" 175 if l > req.Limit { 176 next = rels[req.Limit].Name 177 rels = rels[0:req.Limit] 178 l = int64(len(rels)) 179 } 180 181 res := &services.ListReleasesResponse{ 182 Next: next, 183 Count: l, 184 Total: total, 185 Releases: rels, 186 } 187 return stream.Send(res) 188 } 189 190 func filterByNamespace(namespace string, rels []*release.Release) ([]*release.Release, error) { 191 matches := []*release.Release{} 192 for _, r := range rels { 193 if namespace == r.Namespace { 194 matches = append(matches, r) 195 } 196 } 197 return matches, nil 198 } 199 200 func filterReleases(filter string, rels []*release.Release) ([]*release.Release, error) { 201 preg, err := regexp.Compile(filter) 202 if err != nil { 203 return rels, err 204 } 205 matches := []*release.Release{} 206 for _, r := range rels { 207 if preg.MatchString(r.Name) { 208 matches = append(matches, r) 209 } 210 } 211 return matches, nil 212 } 213 214 // GetVersion sends the server version. 215 func (s *ReleaseServer) GetVersion(c ctx.Context, req *services.GetVersionRequest) (*services.GetVersionResponse, error) { 216 v := version.GetVersionProto() 217 return &services.GetVersionResponse{Version: v}, nil 218 } 219 220 // GetReleaseStatus gets the status information for a named release. 221 func (s *ReleaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) { 222 if !ValidName.MatchString(req.Name) { 223 return nil, errMissingRelease 224 } 225 226 var rel *release.Release 227 228 if req.Version <= 0 { 229 var err error 230 rel, err = s.env.Releases.Last(req.Name) 231 if err != nil { 232 return nil, fmt.Errorf("getting deployed release %q: %s", req.Name, err) 233 } 234 } else { 235 var err error 236 if rel, err = s.env.Releases.Get(req.Name, req.Version); err != nil { 237 return nil, fmt.Errorf("getting release '%s' (v%d): %s", req.Name, req.Version, err) 238 } 239 } 240 241 if rel.Info == nil { 242 return nil, errors.New("release info is missing") 243 } 244 if rel.Chart == nil { 245 return nil, errors.New("release chart is missing") 246 } 247 248 sc := rel.Info.Status.Code 249 statusResp := &services.GetReleaseStatusResponse{ 250 Name: rel.Name, 251 Namespace: rel.Namespace, 252 Info: rel.Info, 253 } 254 255 // Ok, we got the status of the release as we had jotted down, now we need to match the 256 // manifest we stashed away with reality from the cluster. 257 kubeCli := s.env.KubeClient 258 resp, err := kubeCli.Get(rel.Namespace, bytes.NewBufferString(rel.Manifest)) 259 if sc == release.Status_DELETED || sc == release.Status_FAILED { 260 // Skip errors if this is already deleted or failed. 261 return statusResp, nil 262 } else if err != nil { 263 log.Printf("warning: Get for %s failed: %v", rel.Name, err) 264 return nil, err 265 } 266 rel.Info.Status.Resources = resp 267 return statusResp, nil 268 } 269 270 // GetReleaseContent gets all of the stored information for the given release. 271 func (s *ReleaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) { 272 if !ValidName.MatchString(req.Name) { 273 return nil, errMissingRelease 274 } 275 276 if req.Version <= 0 { 277 rel, err := s.env.Releases.Deployed(req.Name) 278 return &services.GetReleaseContentResponse{Release: rel}, err 279 } 280 281 rel, err := s.env.Releases.Get(req.Name, req.Version) 282 return &services.GetReleaseContentResponse{Release: rel}, err 283 } 284 285 // UpdateRelease takes an existing release and new information, and upgrades the release. 286 func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { 287 currentRelease, updatedRelease, err := s.prepareUpdate(req) 288 if err != nil { 289 return nil, err 290 } 291 292 res, err := s.performUpdate(currentRelease, updatedRelease, req) 293 if err != nil { 294 return res, err 295 } 296 297 if !req.DryRun { 298 if err := s.env.Releases.Create(updatedRelease); err != nil { 299 return res, err 300 } 301 } 302 303 return res, nil 304 } 305 306 func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { 307 res := &services.UpdateReleaseResponse{Release: updatedRelease} 308 309 if req.DryRun { 310 log.Printf("Dry run for %s", updatedRelease.Name) 311 res.Release.Info.Description = "Dry run complete" 312 return res, nil 313 } 314 315 // pre-upgrade hooks 316 if !req.DisableHooks { 317 if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil { 318 return res, err 319 } 320 } 321 322 if err := s.performKubeUpdate(originalRelease, updatedRelease, req.Recreate, req.Timeout, req.Wait); err != nil { 323 msg := fmt.Sprintf("Upgrade %q failed: %s", updatedRelease.Name, err) 324 log.Printf("warning: %s", msg) 325 originalRelease.Info.Status.Code = release.Status_SUPERSEDED 326 updatedRelease.Info.Status.Code = release.Status_FAILED 327 updatedRelease.Info.Description = msg 328 s.recordRelease(originalRelease, true) 329 s.recordRelease(updatedRelease, false) 330 return res, err 331 } 332 333 // post-upgrade hooks 334 if !req.DisableHooks { 335 if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PostUpgrade, req.Timeout); err != nil { 336 return res, err 337 } 338 } 339 340 originalRelease.Info.Status.Code = release.Status_SUPERSEDED 341 s.recordRelease(originalRelease, true) 342 343 updatedRelease.Info.Status.Code = release.Status_DEPLOYED 344 updatedRelease.Info.Description = "Upgrade complete" 345 346 return res, nil 347 } 348 349 // reuseValues copies values from the current release to a new release if the 350 // new release does not have any values. 351 // 352 // If the request already has values, or if there are no values in the current 353 // release, this does nothing. 354 // 355 // This is skipped if the req.ResetValues flag is set, in which case the 356 // request values are not altered. 357 func (s *ReleaseServer) reuseValues(req *services.UpdateReleaseRequest, current *release.Release) error { 358 if req.ResetValues { 359 // If ResetValues is set, we comletely ignore current.Config. 360 log.Print("Reset values to the chart's original version.") 361 return nil 362 } 363 364 // If the ReuseValues flag is set, we always copy the old values over the new config's values. 365 if req.ReuseValues { 366 log.Print("Reusing the old release's values") 367 368 // We have to regenerate the old coalesced values: 369 oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) 370 if err != nil { 371 err := fmt.Errorf("failed to rebuild old values: %s", err) 372 log.Print(err) 373 return err 374 } 375 nv, err := oldVals.YAML() 376 if err != nil { 377 return err 378 } 379 req.Chart.Values = &chart.Config{Raw: nv} 380 return nil 381 } 382 383 // If req.Values is empty, but current.Config is not, copy current into the 384 // request. 385 if (req.Values == nil || req.Values.Raw == "" || req.Values.Raw == "{}\n") && 386 current.Config != nil && 387 current.Config.Raw != "" && 388 current.Config.Raw != "{}\n" { 389 log.Printf("Copying values from %s (v%d) to new release.", current.Name, current.Version) 390 req.Values = current.Config 391 } 392 return nil 393 } 394 395 // prepareUpdate builds an updated release for an update operation. 396 func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) { 397 if !ValidName.MatchString(req.Name) { 398 return nil, nil, errMissingRelease 399 } 400 401 if req.Chart == nil { 402 return nil, nil, errMissingChart 403 } 404 405 // finds the non-deleted release with the given name 406 currentRelease, err := s.env.Releases.Last(req.Name) 407 if err != nil { 408 return nil, nil, err 409 } 410 411 // If new values were not supplied in the upgrade, re-use the existing values. 412 if err := s.reuseValues(req, currentRelease); err != nil { 413 return nil, nil, err 414 } 415 416 // Increment revision count. This is passed to templates, and also stored on 417 // the release object. 418 revision := currentRelease.Version + 1 419 420 ts := timeconv.Now() 421 options := chartutil.ReleaseOptions{ 422 Name: req.Name, 423 Time: ts, 424 Namespace: currentRelease.Namespace, 425 IsUpgrade: true, 426 Revision: int(revision), 427 } 428 429 caps, err := capabilities(s.clientset.Discovery()) 430 if err != nil { 431 return nil, nil, err 432 } 433 valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) 434 if err != nil { 435 return nil, nil, err 436 } 437 438 hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) 439 if err != nil { 440 return nil, nil, err 441 } 442 443 // Store an updated release. 444 updatedRelease := &release.Release{ 445 Name: req.Name, 446 Namespace: currentRelease.Namespace, 447 Chart: req.Chart, 448 Config: req.Values, 449 Info: &release.Info{ 450 FirstDeployed: currentRelease.Info.FirstDeployed, 451 LastDeployed: ts, 452 Status: &release.Status{Code: release.Status_UNKNOWN}, 453 Description: "Preparing upgrade", // This should be overwritten later. 454 }, 455 Version: revision, 456 Manifest: manifestDoc.String(), 457 Hooks: hooks, 458 } 459 460 if len(notesTxt) > 0 { 461 updatedRelease.Info.Status.Notes = notesTxt 462 } 463 err = validateManifest(s.env.KubeClient, currentRelease.Namespace, manifestDoc.Bytes()) 464 return currentRelease, updatedRelease, err 465 } 466 467 // RollbackRelease rolls back to a previous version of the given release. 468 func (s *ReleaseServer) RollbackRelease(c ctx.Context, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) { 469 currentRelease, targetRelease, err := s.prepareRollback(req) 470 if err != nil { 471 return nil, err 472 } 473 474 res, err := s.performRollback(currentRelease, targetRelease, req) 475 if err != nil { 476 return res, err 477 } 478 479 if !req.DryRun { 480 if err := s.env.Releases.Create(targetRelease); err != nil { 481 return res, err 482 } 483 } 484 485 return res, nil 486 } 487 488 func (s *ReleaseServer) performRollback(currentRelease, targetRelease *release.Release, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) { 489 res := &services.RollbackReleaseResponse{Release: targetRelease} 490 491 if req.DryRun { 492 log.Printf("Dry run for %s", targetRelease.Name) 493 return res, nil 494 } 495 496 // pre-rollback hooks 497 if !req.DisableHooks { 498 if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PreRollback, req.Timeout); err != nil { 499 return res, err 500 } 501 } 502 503 if err := s.performKubeUpdate(currentRelease, targetRelease, req.Recreate, req.Timeout, req.Wait); err != nil { 504 msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) 505 log.Printf("warning: %s", msg) 506 currentRelease.Info.Status.Code = release.Status_SUPERSEDED 507 targetRelease.Info.Status.Code = release.Status_FAILED 508 targetRelease.Info.Description = msg 509 s.recordRelease(currentRelease, true) 510 s.recordRelease(targetRelease, false) 511 return res, err 512 } 513 514 // post-rollback hooks 515 if !req.DisableHooks { 516 if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PostRollback, req.Timeout); err != nil { 517 return res, err 518 } 519 } 520 521 currentRelease.Info.Status.Code = release.Status_SUPERSEDED 522 s.recordRelease(currentRelease, true) 523 524 targetRelease.Info.Status.Code = release.Status_DEPLOYED 525 526 return res, nil 527 } 528 529 func (s *ReleaseServer) performKubeUpdate(currentRelease, targetRelease *release.Release, recreate bool, timeout int64, shouldWait bool) error { 530 kubeCli := s.env.KubeClient 531 current := bytes.NewBufferString(currentRelease.Manifest) 532 target := bytes.NewBufferString(targetRelease.Manifest) 533 return kubeCli.Update(targetRelease.Namespace, current, target, recreate, timeout, shouldWait) 534 } 535 536 // prepareRollback finds the previous release and prepares a new release object with 537 // the previous release's configuration 538 func (s *ReleaseServer) prepareRollback(req *services.RollbackReleaseRequest) (*release.Release, *release.Release, error) { 539 switch { 540 case !ValidName.MatchString(req.Name): 541 return nil, nil, errMissingRelease 542 case req.Version < 0: 543 return nil, nil, errInvalidRevision 544 } 545 546 crls, err := s.env.Releases.Last(req.Name) 547 if err != nil { 548 return nil, nil, err 549 } 550 551 rbv := req.Version 552 if req.Version == 0 { 553 rbv = crls.Version - 1 554 } 555 556 log.Printf("rolling back %s (current: v%d, target: v%d)", req.Name, crls.Version, rbv) 557 558 prls, err := s.env.Releases.Get(req.Name, rbv) 559 if err != nil { 560 return nil, nil, err 561 } 562 563 // Store a new release object with previous release's configuration 564 target := &release.Release{ 565 Name: req.Name, 566 Namespace: crls.Namespace, 567 Chart: prls.Chart, 568 Config: prls.Config, 569 Info: &release.Info{ 570 FirstDeployed: crls.Info.FirstDeployed, 571 LastDeployed: timeconv.Now(), 572 Status: &release.Status{ 573 Code: release.Status_UNKNOWN, 574 Notes: prls.Info.Status.Notes, 575 }, 576 // Because we lose the reference to rbv elsewhere, we set the 577 // message here, and only override it later if we experience failure. 578 Description: fmt.Sprintf("Rollback to %d", rbv), 579 }, 580 Version: crls.Version + 1, 581 Manifest: prls.Manifest, 582 Hooks: prls.Hooks, 583 } 584 585 return crls, target, nil 586 } 587 588 func (s *ReleaseServer) uniqName(start string, reuse bool) (string, error) { 589 590 // If a name is supplied, we check to see if that name is taken. If not, it 591 // is granted. If reuse is true and a deleted release with that name exists, 592 // we re-grant it. Otherwise, an error is returned. 593 if start != "" { 594 595 if len(start) > releaseNameMaxLen { 596 return "", fmt.Errorf("release name %q exceeds max length of %d", start, releaseNameMaxLen) 597 } 598 599 h, err := s.env.Releases.History(start) 600 if err != nil || len(h) < 1 { 601 return start, nil 602 } 603 relutil.Reverse(h, relutil.SortByRevision) 604 rel := h[0] 605 606 if st := rel.Info.Status.Code; reuse && (st == release.Status_DELETED || st == release.Status_FAILED) { 607 // Allowe re-use of names if the previous release is marked deleted. 608 log.Printf("reusing name %q", start) 609 return start, nil 610 } else if reuse { 611 return "", errors.New("cannot re-use a name that is still in use") 612 } 613 614 return "", fmt.Errorf("a release named %q already exists.\nPlease run: helm ls --all %q; helm del --help", start, start) 615 } 616 617 maxTries := 5 618 for i := 0; i < maxTries; i++ { 619 namer := moniker.New() 620 name := namer.NameSep("-") 621 if len(name) > releaseNameMaxLen { 622 name = name[:releaseNameMaxLen] 623 } 624 if _, err := s.env.Releases.Get(name, 1); err == driver.ErrReleaseNotFound { 625 return name, nil 626 } 627 log.Printf("info: Name %q is taken. Searching again.", name) 628 } 629 log.Printf("warning: No available release names found after %d tries", maxTries) 630 return "ERROR", errors.New("no available release name found") 631 } 632 633 func (s *ReleaseServer) engine(ch *chart.Chart) environment.Engine { 634 renderer := s.env.EngineYard.Default() 635 if ch.Metadata.Engine != "" { 636 if r, ok := s.env.EngineYard.Get(ch.Metadata.Engine); ok { 637 renderer = r 638 } else { 639 log.Printf("warning: %s requested non-existent template engine %s", ch.Metadata.Name, ch.Metadata.Engine) 640 } 641 } 642 return renderer 643 } 644 645 // InstallRelease installs a release and stores the release record. 646 func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { 647 rel, err := s.prepareRelease(req) 648 if err != nil { 649 log.Printf("Failed install prepare step: %s", err) 650 res := &services.InstallReleaseResponse{Release: rel} 651 652 // On dry run, append the manifest contents to a failed release. This is 653 // a stop-gap until we can revisit an error backchannel post-2.0. 654 if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") { 655 err = fmt.Errorf("%s\n%s", err, rel.Manifest) 656 } 657 return res, err 658 } 659 660 res, err := s.performRelease(rel, req) 661 if err != nil { 662 log.Printf("Failed install perform step: %s", err) 663 } 664 return res, err 665 } 666 667 // capabilities builds a Capabilities from discovery information. 668 func capabilities(disc discovery.DiscoveryInterface) (*chartutil.Capabilities, error) { 669 sv, err := disc.ServerVersion() 670 if err != nil { 671 return nil, err 672 } 673 vs, err := getVersionSet(disc) 674 if err != nil { 675 return nil, fmt.Errorf("Could not get apiVersions from Kubernetes: %s", err) 676 } 677 return &chartutil.Capabilities{ 678 APIVersions: vs, 679 KubeVersion: sv, 680 TillerVersion: version.GetVersionProto(), 681 }, nil 682 } 683 684 // prepareRelease builds a release for an install operation. 685 func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*release.Release, error) { 686 if req.Chart == nil { 687 return nil, errMissingChart 688 } 689 690 name, err := s.uniqName(req.Name, req.ReuseName) 691 if err != nil { 692 return nil, err 693 } 694 695 caps, err := capabilities(s.clientset.Discovery()) 696 if err != nil { 697 return nil, err 698 } 699 700 revision := 1 701 ts := timeconv.Now() 702 options := chartutil.ReleaseOptions{ 703 Name: name, 704 Time: ts, 705 Namespace: req.Namespace, 706 Revision: revision, 707 IsInstall: true, 708 } 709 valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) 710 if err != nil { 711 return nil, err 712 } 713 714 hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) 715 if err != nil { 716 // Return a release with partial data so that client can show debugging 717 // information. 718 rel := &release.Release{ 719 Name: name, 720 Namespace: req.Namespace, 721 Chart: req.Chart, 722 Config: req.Values, 723 Info: &release.Info{ 724 FirstDeployed: ts, 725 LastDeployed: ts, 726 Status: &release.Status{Code: release.Status_UNKNOWN}, 727 Description: fmt.Sprintf("Install failed: %s", err), 728 }, 729 Version: 0, 730 } 731 if manifestDoc != nil { 732 rel.Manifest = manifestDoc.String() 733 } 734 return rel, err 735 } 736 737 // Store a release. 738 rel := &release.Release{ 739 Name: name, 740 Namespace: req.Namespace, 741 Chart: req.Chart, 742 Config: req.Values, 743 Info: &release.Info{ 744 FirstDeployed: ts, 745 LastDeployed: ts, 746 Status: &release.Status{Code: release.Status_UNKNOWN}, 747 Description: "Initial install underway", // Will be overwritten. 748 }, 749 Manifest: manifestDoc.String(), 750 Hooks: hooks, 751 Version: int32(revision), 752 } 753 if len(notesTxt) > 0 { 754 rel.Info.Status.Notes = notesTxt 755 } 756 757 err = validateManifest(s.env.KubeClient, req.Namespace, manifestDoc.Bytes()) 758 return rel, err 759 } 760 761 func getVersionSet(client discovery.ServerGroupsInterface) (chartutil.VersionSet, error) { 762 groups, err := client.ServerGroups() 763 if err != nil { 764 return chartutil.DefaultVersionSet, err 765 } 766 767 // FIXME: The Kubernetes test fixture for cli appears to always return nil 768 // for calls to Discovery().ServerGroups(). So in this case, we return 769 // the default API list. This is also a safe value to return in any other 770 // odd-ball case. 771 if groups == nil { 772 return chartutil.DefaultVersionSet, nil 773 } 774 775 versions := unversioned.ExtractGroupVersions(groups) 776 return chartutil.NewVersionSet(versions...), nil 777 } 778 779 func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values, vs chartutil.VersionSet) ([]*release.Hook, *bytes.Buffer, string, error) { 780 renderer := s.engine(ch) 781 files, err := renderer.Render(ch, values) 782 if err != nil { 783 return nil, nil, "", err 784 } 785 786 // NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource, 787 // pull it out of here into a separate file so that we can actually use the output of the rendered 788 // text file. We have to spin through this map because the file contains path information, so we 789 // look for terminating NOTES.txt. We also remove it from the files so that we don't have to skip 790 // it in the sortHooks. 791 notes := "" 792 for k, v := range files { 793 if strings.HasSuffix(k, notesFileSuffix) { 794 // Only apply the notes if it belongs to the parent chart 795 // Note: Do not use filePath.Join since it creates a path with \ which is not expected 796 if k == path.Join(ch.Metadata.Name, "templates", notesFileSuffix) { 797 notes = v 798 } 799 delete(files, k) 800 } 801 } 802 803 // Sort hooks, manifests, and partials. Only hooks and manifests are returned, 804 // as partials are not used after renderer.Render. Empty manifests are also 805 // removed here. 806 hooks, manifests, err := sortManifests(files, vs, InstallOrder) 807 if err != nil { 808 // By catching parse errors here, we can prevent bogus releases from going 809 // to Kubernetes. 810 // 811 // We return the files as a big blob of data to help the user debug parser 812 // errors. 813 b := bytes.NewBuffer(nil) 814 for name, content := range files { 815 if len(strings.TrimSpace(content)) == 0 { 816 continue 817 } 818 b.WriteString("\n---\n# Source: " + name + "\n") 819 b.WriteString(content) 820 } 821 return nil, b, "", err 822 } 823 824 // Aggregate all valid manifests into one big doc. 825 b := bytes.NewBuffer(nil) 826 for _, m := range manifests { 827 b.WriteString("\n---\n# Source: " + m.name + "\n") 828 b.WriteString(m.content) 829 } 830 831 return hooks, b, notes, nil 832 } 833 834 func (s *ReleaseServer) recordRelease(r *release.Release, reuse bool) { 835 if reuse { 836 if err := s.env.Releases.Update(r); err != nil { 837 log.Printf("warning: Failed to update release %q: %s", r.Name, err) 838 } 839 } else if err := s.env.Releases.Create(r); err != nil { 840 log.Printf("warning: Failed to record release %q: %s", r.Name, err) 841 } 842 } 843 844 // performRelease runs a release. 845 func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { 846 res := &services.InstallReleaseResponse{Release: r} 847 848 if req.DryRun { 849 log.Printf("Dry run for %s", r.Name) 850 res.Release.Info.Description = "Dry run complete" 851 return res, nil 852 } 853 854 // pre-install hooks 855 if !req.DisableHooks { 856 if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil { 857 return res, err 858 } 859 } 860 861 switch h, err := s.env.Releases.History(req.Name); { 862 // if this is a replace operation, append to the release history 863 case req.ReuseName && err == nil && len(h) >= 1: 864 // get latest release revision 865 relutil.Reverse(h, relutil.SortByRevision) 866 867 // old release 868 old := h[0] 869 870 // update old release status 871 old.Info.Status.Code = release.Status_SUPERSEDED 872 s.recordRelease(old, true) 873 874 // update new release with next revision number 875 // so as to append to the old release's history 876 r.Version = old.Version + 1 877 878 if err := s.performKubeUpdate(old, r, false, req.Timeout, req.Wait); err != nil { 879 msg := fmt.Sprintf("Release replace %q failed: %s", r.Name, err) 880 log.Printf("warning: %s", msg) 881 old.Info.Status.Code = release.Status_SUPERSEDED 882 r.Info.Status.Code = release.Status_FAILED 883 r.Info.Description = msg 884 s.recordRelease(old, true) 885 s.recordRelease(r, false) 886 return res, err 887 } 888 889 default: 890 // nothing to replace, create as normal 891 // regular manifests 892 b := bytes.NewBufferString(r.Manifest) 893 if err := s.env.KubeClient.Create(r.Namespace, b, req.Timeout, req.Wait); err != nil { 894 msg := fmt.Sprintf("Release %q failed: %s", r.Name, err) 895 log.Printf("warning: %s", msg) 896 r.Info.Status.Code = release.Status_FAILED 897 r.Info.Description = msg 898 s.recordRelease(r, false) 899 return res, fmt.Errorf("release %s failed: %s", r.Name, err) 900 } 901 } 902 903 // post-install hooks 904 if !req.DisableHooks { 905 if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PostInstall, req.Timeout); err != nil { 906 msg := fmt.Sprintf("Release %q failed post-install: %s", r.Name, err) 907 log.Printf("warning: %s", msg) 908 r.Info.Status.Code = release.Status_FAILED 909 r.Info.Description = msg 910 s.recordRelease(r, false) 911 return res, err 912 } 913 } 914 915 r.Info.Status.Code = release.Status_DEPLOYED 916 r.Info.Description = "Install complete" 917 // This is a tricky case. The release has been created, but the result 918 // cannot be recorded. The truest thing to tell the user is that the 919 // release was created. However, the user will not be able to do anything 920 // further with this release. 921 // 922 // One possible strategy would be to do a timed retry to see if we can get 923 // this stored in the future. 924 s.recordRelease(r, false) 925 926 return res, nil 927 } 928 929 func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook string, timeout int64) error { 930 kubeCli := s.env.KubeClient 931 code, ok := events[hook] 932 if !ok { 933 return fmt.Errorf("unknown hook %q", hook) 934 } 935 936 log.Printf("Executing %s hooks for %s", hook, name) 937 executingHooks := []*release.Hook{} 938 for _, h := range hs { 939 for _, e := range h.Events { 940 if e == code { 941 executingHooks = append(executingHooks, h) 942 } 943 } 944 } 945 946 executingHooks = sortByHookWeight(executingHooks) 947 948 for _, h := range executingHooks { 949 950 b := bytes.NewBufferString(h.Manifest) 951 if err := kubeCli.Create(namespace, b, timeout, false); err != nil { 952 log.Printf("warning: Release %q %s %s failed: %s", name, hook, h.Path, err) 953 return err 954 } 955 // No way to rewind a bytes.Buffer()? 956 b.Reset() 957 b.WriteString(h.Manifest) 958 if err := kubeCli.WatchUntilReady(namespace, b, timeout, false); err != nil { 959 log.Printf("warning: Release %q %s %s could not complete: %s", name, hook, h.Path, err) 960 return err 961 } 962 h.LastRun = timeconv.Now() 963 } 964 965 log.Printf("Hooks complete for %s %s", hook, name) 966 return nil 967 } 968 969 func (s *ReleaseServer) purgeReleases(rels ...*release.Release) error { 970 for _, rel := range rels { 971 if _, err := s.env.Releases.Delete(rel.Name, rel.Version); err != nil { 972 return err 973 } 974 } 975 return nil 976 } 977 978 // UninstallRelease deletes all of the resources associated with this release, and marks the release DELETED. 979 func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { 980 if !ValidName.MatchString(req.Name) { 981 log.Printf("uninstall: Release not found: %s", req.Name) 982 return nil, errMissingRelease 983 } 984 985 if len(req.Name) > releaseNameMaxLen { 986 return nil, fmt.Errorf("release name %q exceeds max length of %d", req.Name, releaseNameMaxLen) 987 } 988 989 rels, err := s.env.Releases.History(req.Name) 990 if err != nil { 991 log.Printf("uninstall: Release not loaded: %s", req.Name) 992 return nil, err 993 } 994 if len(rels) < 1 { 995 return nil, errMissingRelease 996 } 997 998 relutil.SortByRevision(rels) 999 rel := rels[len(rels)-1] 1000 1001 // TODO: Are there any cases where we want to force a delete even if it's 1002 // already marked deleted? 1003 if rel.Info.Status.Code == release.Status_DELETED { 1004 if req.Purge { 1005 if err := s.purgeReleases(rels...); err != nil { 1006 log.Printf("uninstall: Failed to purge the release: %s", err) 1007 return nil, err 1008 } 1009 return &services.UninstallReleaseResponse{Release: rel}, nil 1010 } 1011 return nil, fmt.Errorf("the release named %q is already deleted", req.Name) 1012 } 1013 1014 log.Printf("uninstall: Deleting %s", req.Name) 1015 rel.Info.Status.Code = release.Status_DELETING 1016 rel.Info.Deleted = timeconv.Now() 1017 rel.Info.Description = "Deletion in progress (or silently failed)" 1018 res := &services.UninstallReleaseResponse{Release: rel} 1019 1020 if !req.DisableHooks { 1021 if err := s.execHook(rel.Hooks, rel.Name, rel.Namespace, hooks.PreDelete, req.Timeout); err != nil { 1022 return res, err 1023 } 1024 } 1025 1026 vs, err := getVersionSet(s.clientset.Discovery()) 1027 if err != nil { 1028 return nil, fmt.Errorf("Could not get apiVersions from Kubernetes: %s", err) 1029 } 1030 1031 // From here on out, the release is currently considered to be in Status_DELETING 1032 // state. 1033 if err := s.env.Releases.Update(rel); err != nil { 1034 log.Printf("uninstall: Failed to store updated release: %s", err) 1035 } 1036 1037 manifests := relutil.SplitManifests(rel.Manifest) 1038 _, files, err := sortManifests(manifests, vs, UninstallOrder) 1039 if err != nil { 1040 // We could instead just delete everything in no particular order. 1041 // FIXME: One way to delete at this point would be to try a label-based 1042 // deletion. The problem with this is that we could get a false positive 1043 // and delete something that was not legitimately part of this release. 1044 return nil, fmt.Errorf("corrupted release record. You must manually delete the resources: %s", err) 1045 } 1046 1047 filesToKeep, filesToDelete := filterManifestsToKeep(files) 1048 if len(filesToKeep) > 0 { 1049 res.Info = summarizeKeptManifests(filesToKeep) 1050 } 1051 1052 // Collect the errors, and return them later. 1053 es := []string{} 1054 for _, file := range filesToDelete { 1055 b := bytes.NewBufferString(file.content) 1056 if err := s.env.KubeClient.Delete(rel.Namespace, b); err != nil { 1057 log.Printf("uninstall: Failed deletion of %q: %s", req.Name, err) 1058 if err == kube.ErrNoObjectsVisited { 1059 // Rewrite the message from "no objects visited" 1060 err = errors.New("object not found, skipping delete") 1061 } 1062 es = append(es, err.Error()) 1063 } 1064 } 1065 1066 if !req.DisableHooks { 1067 if err := s.execHook(rel.Hooks, rel.Name, rel.Namespace, hooks.PostDelete, req.Timeout); err != nil { 1068 es = append(es, err.Error()) 1069 } 1070 } 1071 1072 rel.Info.Status.Code = release.Status_DELETED 1073 rel.Info.Description = "Deletion complete" 1074 1075 if req.Purge { 1076 err := s.purgeReleases(rels...) 1077 if err != nil { 1078 log.Printf("uninstall: Failed to purge the release: %s", err) 1079 } 1080 return res, err 1081 } 1082 1083 if err := s.env.Releases.Update(rel); err != nil { 1084 log.Printf("uninstall: Failed to store updated release: %s", err) 1085 } 1086 1087 if len(es) > 0 { 1088 return res, fmt.Errorf("deletion completed with %d error(s): %s", len(es), strings.Join(es, "; ")) 1089 } 1090 return res, nil 1091 } 1092 1093 func validateManifest(c environment.KubeClient, ns string, manifest []byte) error { 1094 r := bytes.NewReader(manifest) 1095 _, err := c.BuildUnstructured(ns, r) 1096 return err 1097 } 1098 1099 // RunReleaseTest runs pre-defined tests stored as hooks on a given release 1100 func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream services.ReleaseService_RunReleaseTestServer) error { 1101 1102 if !ValidName.MatchString(req.Name) { 1103 return errMissingRelease 1104 } 1105 1106 // finds the non-deleted release with the given name 1107 rel, err := s.env.Releases.Last(req.Name) 1108 if err != nil { 1109 return err 1110 } 1111 1112 testEnv := &reltesting.Environment{ 1113 Namespace: rel.Namespace, 1114 KubeClient: s.env.KubeClient, 1115 Timeout: req.Timeout, 1116 Stream: stream, 1117 } 1118 1119 tSuite, err := reltesting.NewTestSuite(rel) 1120 if err != nil { 1121 log.Printf("Error creating test suite for %s", rel.Name) 1122 return err 1123 } 1124 1125 if err := tSuite.Run(testEnv); err != nil { 1126 log.Printf("Error running test suite for %s", rel.Name) 1127 return err 1128 } 1129 1130 rel.Info.Status.LastTestSuiteRun = &release.TestSuite{ 1131 StartedAt: tSuite.StartedAt, 1132 CompletedAt: tSuite.CompletedAt, 1133 Results: tSuite.Results, 1134 } 1135 1136 if req.Cleanup { 1137 testEnv.DeleteTestPods(tSuite.TestManifests) 1138 } 1139 1140 return s.env.Releases.Update(rel) 1141 }