github.com/cloudposse/helm@v2.2.3+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) { 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 362 } 363 // If req.Values is empty, but current. config is not, copy current into the 364 // request. 365 if (req.Values == nil || req.Values.Raw == "" || req.Values.Raw == "{}\n") && 366 current.Config != nil && 367 current.Config.Raw != "" && 368 current.Config.Raw != "{}\n" { 369 log.Printf("Copying values from %s (v%d) to new release.", current.Name, current.Version) 370 req.Values = current.Config 371 } 372 } 373 374 // prepareUpdate builds an updated release for an update operation. 375 func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) { 376 if !ValidName.MatchString(req.Name) { 377 return nil, nil, errMissingRelease 378 } 379 380 if req.Chart == nil { 381 return nil, nil, errMissingChart 382 } 383 384 // finds the non-deleted release with the given name 385 currentRelease, err := s.env.Releases.Last(req.Name) 386 if err != nil { 387 return nil, nil, err 388 } 389 390 // If new values were not supplied in the upgrade, re-use the existing values. 391 s.reuseValues(req, currentRelease) 392 393 // Increment revision count. This is passed to templates, and also stored on 394 // the release object. 395 revision := currentRelease.Version + 1 396 397 ts := timeconv.Now() 398 options := chartutil.ReleaseOptions{ 399 Name: req.Name, 400 Time: ts, 401 Namespace: currentRelease.Namespace, 402 IsUpgrade: true, 403 Revision: int(revision), 404 } 405 406 caps, err := capabilities(s.clientset.Discovery()) 407 if err != nil { 408 return nil, nil, err 409 } 410 valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) 411 if err != nil { 412 return nil, nil, err 413 } 414 415 hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) 416 if err != nil { 417 return nil, nil, err 418 } 419 420 // Store an updated release. 421 updatedRelease := &release.Release{ 422 Name: req.Name, 423 Namespace: currentRelease.Namespace, 424 Chart: req.Chart, 425 Config: req.Values, 426 Info: &release.Info{ 427 FirstDeployed: currentRelease.Info.FirstDeployed, 428 LastDeployed: ts, 429 Status: &release.Status{Code: release.Status_UNKNOWN}, 430 Description: "Preparing upgrade", // This should be overwritten later. 431 }, 432 Version: revision, 433 Manifest: manifestDoc.String(), 434 Hooks: hooks, 435 } 436 437 if len(notesTxt) > 0 { 438 updatedRelease.Info.Status.Notes = notesTxt 439 } 440 err = validateManifest(s.env.KubeClient, currentRelease.Namespace, manifestDoc.Bytes()) 441 return currentRelease, updatedRelease, err 442 } 443 444 // RollbackRelease rolls back to a previous version of the given release. 445 func (s *ReleaseServer) RollbackRelease(c ctx.Context, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) { 446 currentRelease, targetRelease, err := s.prepareRollback(req) 447 if err != nil { 448 return nil, err 449 } 450 451 res, err := s.performRollback(currentRelease, targetRelease, req) 452 if err != nil { 453 return res, err 454 } 455 456 if !req.DryRun { 457 if err := s.env.Releases.Create(targetRelease); err != nil { 458 return res, err 459 } 460 } 461 462 return res, nil 463 } 464 465 func (s *ReleaseServer) performRollback(currentRelease, targetRelease *release.Release, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) { 466 res := &services.RollbackReleaseResponse{Release: targetRelease} 467 468 if req.DryRun { 469 log.Printf("Dry run for %s", targetRelease.Name) 470 return res, nil 471 } 472 473 // pre-rollback hooks 474 if !req.DisableHooks { 475 if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PreRollback, req.Timeout); err != nil { 476 return res, err 477 } 478 } 479 480 if err := s.performKubeUpdate(currentRelease, targetRelease, req.Recreate, req.Timeout, req.Wait); err != nil { 481 msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) 482 log.Printf("warning: %s", msg) 483 currentRelease.Info.Status.Code = release.Status_SUPERSEDED 484 targetRelease.Info.Status.Code = release.Status_FAILED 485 targetRelease.Info.Description = msg 486 s.recordRelease(currentRelease, true) 487 s.recordRelease(targetRelease, false) 488 return res, err 489 } 490 491 // post-rollback hooks 492 if !req.DisableHooks { 493 if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PostRollback, req.Timeout); err != nil { 494 return res, err 495 } 496 } 497 498 currentRelease.Info.Status.Code = release.Status_SUPERSEDED 499 s.recordRelease(currentRelease, true) 500 501 targetRelease.Info.Status.Code = release.Status_DEPLOYED 502 503 return res, nil 504 } 505 506 func (s *ReleaseServer) performKubeUpdate(currentRelease, targetRelease *release.Release, recreate bool, timeout int64, shouldWait bool) error { 507 kubeCli := s.env.KubeClient 508 current := bytes.NewBufferString(currentRelease.Manifest) 509 target := bytes.NewBufferString(targetRelease.Manifest) 510 return kubeCli.Update(targetRelease.Namespace, current, target, recreate, timeout, shouldWait) 511 } 512 513 // prepareRollback finds the previous release and prepares a new release object with 514 // the previous release's configuration 515 func (s *ReleaseServer) prepareRollback(req *services.RollbackReleaseRequest) (*release.Release, *release.Release, error) { 516 switch { 517 case !ValidName.MatchString(req.Name): 518 return nil, nil, errMissingRelease 519 case req.Version < 0: 520 return nil, nil, errInvalidRevision 521 } 522 523 crls, err := s.env.Releases.Last(req.Name) 524 if err != nil { 525 return nil, nil, err 526 } 527 528 rbv := req.Version 529 if req.Version == 0 { 530 rbv = crls.Version - 1 531 } 532 533 log.Printf("rolling back %s (current: v%d, target: v%d)", req.Name, crls.Version, rbv) 534 535 prls, err := s.env.Releases.Get(req.Name, rbv) 536 if err != nil { 537 return nil, nil, err 538 } 539 540 // Store a new release object with previous release's configuration 541 target := &release.Release{ 542 Name: req.Name, 543 Namespace: crls.Namespace, 544 Chart: prls.Chart, 545 Config: prls.Config, 546 Info: &release.Info{ 547 FirstDeployed: crls.Info.FirstDeployed, 548 LastDeployed: timeconv.Now(), 549 Status: &release.Status{ 550 Code: release.Status_UNKNOWN, 551 Notes: prls.Info.Status.Notes, 552 }, 553 // Because we lose the reference to rbv elsewhere, we set the 554 // message here, and only override it later if we experience failure. 555 Description: fmt.Sprintf("Rollback to %d", rbv), 556 }, 557 Version: crls.Version + 1, 558 Manifest: prls.Manifest, 559 Hooks: prls.Hooks, 560 } 561 562 return crls, target, nil 563 } 564 565 func (s *ReleaseServer) uniqName(start string, reuse bool) (string, error) { 566 567 // If a name is supplied, we check to see if that name is taken. If not, it 568 // is granted. If reuse is true and a deleted release with that name exists, 569 // we re-grant it. Otherwise, an error is returned. 570 if start != "" { 571 572 if len(start) > releaseNameMaxLen { 573 return "", fmt.Errorf("release name %q exceeds max length of %d", start, releaseNameMaxLen) 574 } 575 576 h, err := s.env.Releases.History(start) 577 if err != nil || len(h) < 1 { 578 return start, nil 579 } 580 relutil.Reverse(h, relutil.SortByRevision) 581 rel := h[0] 582 583 if st := rel.Info.Status.Code; reuse && (st == release.Status_DELETED || st == release.Status_FAILED) { 584 // Allowe re-use of names if the previous release is marked deleted. 585 log.Printf("reusing name %q", start) 586 return start, nil 587 } else if reuse { 588 return "", errors.New("cannot re-use a name that is still in use") 589 } 590 591 return "", fmt.Errorf("a release named %q already exists", start) 592 } 593 594 maxTries := 5 595 for i := 0; i < maxTries; i++ { 596 namer := moniker.New() 597 name := namer.NameSep("-") 598 if len(name) > releaseNameMaxLen { 599 name = name[:releaseNameMaxLen] 600 } 601 if _, err := s.env.Releases.Get(name, 1); err == driver.ErrReleaseNotFound { 602 return name, nil 603 } 604 log.Printf("info: Name %q is taken. Searching again.", name) 605 } 606 log.Printf("warning: No available release names found after %d tries", maxTries) 607 return "ERROR", errors.New("no available release name found") 608 } 609 610 func (s *ReleaseServer) engine(ch *chart.Chart) environment.Engine { 611 renderer := s.env.EngineYard.Default() 612 if ch.Metadata.Engine != "" { 613 if r, ok := s.env.EngineYard.Get(ch.Metadata.Engine); ok { 614 renderer = r 615 } else { 616 log.Printf("warning: %s requested non-existent template engine %s", ch.Metadata.Name, ch.Metadata.Engine) 617 } 618 } 619 return renderer 620 } 621 622 // InstallRelease installs a release and stores the release record. 623 func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { 624 rel, err := s.prepareRelease(req) 625 if err != nil { 626 log.Printf("Failed install prepare step: %s", err) 627 res := &services.InstallReleaseResponse{Release: rel} 628 629 // On dry run, append the manifest contents to a failed release. This is 630 // a stop-gap until we can revisit an error backchannel post-2.0. 631 if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") { 632 err = fmt.Errorf("%s\n%s", err, rel.Manifest) 633 } 634 return res, err 635 } 636 637 res, err := s.performRelease(rel, req) 638 if err != nil { 639 log.Printf("Failed install perform step: %s", err) 640 } 641 return res, err 642 } 643 644 // capabilities builds a Capabilities from discovery information. 645 func capabilities(disc discovery.DiscoveryInterface) (*chartutil.Capabilities, error) { 646 sv, err := disc.ServerVersion() 647 if err != nil { 648 return nil, err 649 } 650 vs, err := getVersionSet(disc) 651 if err != nil { 652 return nil, fmt.Errorf("Could not get apiVersions from Kubernetes: %s", err) 653 } 654 return &chartutil.Capabilities{ 655 APIVersions: vs, 656 KubeVersion: sv, 657 TillerVersion: version.GetVersionProto(), 658 }, nil 659 } 660 661 // prepareRelease builds a release for an install operation. 662 func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*release.Release, error) { 663 if req.Chart == nil { 664 return nil, errMissingChart 665 } 666 667 name, err := s.uniqName(req.Name, req.ReuseName) 668 if err != nil { 669 return nil, err 670 } 671 672 caps, err := capabilities(s.clientset.Discovery()) 673 if err != nil { 674 return nil, err 675 } 676 677 revision := 1 678 ts := timeconv.Now() 679 options := chartutil.ReleaseOptions{ 680 Name: name, 681 Time: ts, 682 Namespace: req.Namespace, 683 Revision: revision, 684 IsInstall: true, 685 } 686 valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) 687 if err != nil { 688 return nil, err 689 } 690 691 hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) 692 if err != nil { 693 // Return a release with partial data so that client can show debugging 694 // information. 695 rel := &release.Release{ 696 Name: name, 697 Namespace: req.Namespace, 698 Chart: req.Chart, 699 Config: req.Values, 700 Info: &release.Info{ 701 FirstDeployed: ts, 702 LastDeployed: ts, 703 Status: &release.Status{Code: release.Status_UNKNOWN}, 704 Description: fmt.Sprintf("Install failed: %s", err), 705 }, 706 Version: 0, 707 } 708 if manifestDoc != nil { 709 rel.Manifest = manifestDoc.String() 710 } 711 return rel, err 712 } 713 714 // Store a release. 715 rel := &release.Release{ 716 Name: name, 717 Namespace: req.Namespace, 718 Chart: req.Chart, 719 Config: req.Values, 720 Info: &release.Info{ 721 FirstDeployed: ts, 722 LastDeployed: ts, 723 Status: &release.Status{Code: release.Status_UNKNOWN}, 724 Description: "Initial install underway", // Will be overwritten. 725 }, 726 Manifest: manifestDoc.String(), 727 Hooks: hooks, 728 Version: int32(revision), 729 } 730 if len(notesTxt) > 0 { 731 rel.Info.Status.Notes = notesTxt 732 } 733 734 err = validateManifest(s.env.KubeClient, req.Namespace, manifestDoc.Bytes()) 735 return rel, err 736 } 737 738 func getVersionSet(client discovery.ServerGroupsInterface) (chartutil.VersionSet, error) { 739 groups, err := client.ServerGroups() 740 if err != nil { 741 return chartutil.DefaultVersionSet, err 742 } 743 744 // FIXME: The Kubernetes test fixture for cli appears to always return nil 745 // for calls to Discovery().ServerGroups(). So in this case, we return 746 // the default API list. This is also a safe value to return in any other 747 // odd-ball case. 748 if groups == nil { 749 return chartutil.DefaultVersionSet, nil 750 } 751 752 versions := unversioned.ExtractGroupVersions(groups) 753 return chartutil.NewVersionSet(versions...), nil 754 } 755 756 func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values, vs chartutil.VersionSet) ([]*release.Hook, *bytes.Buffer, string, error) { 757 renderer := s.engine(ch) 758 files, err := renderer.Render(ch, values) 759 if err != nil { 760 return nil, nil, "", err 761 } 762 763 // NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource, 764 // pull it out of here into a separate file so that we can actually use the output of the rendered 765 // text file. We have to spin through this map because the file contains path information, so we 766 // look for terminating NOTES.txt. We also remove it from the files so that we don't have to skip 767 // it in the sortHooks. 768 notes := "" 769 for k, v := range files { 770 if strings.HasSuffix(k, notesFileSuffix) { 771 // Only apply the notes if it belongs to the parent chart 772 // Note: Do not use filePath.Join since it creates a path with \ which is not expected 773 if k == path.Join(ch.Metadata.Name, "templates", notesFileSuffix) { 774 notes = v 775 } 776 delete(files, k) 777 } 778 } 779 780 // Sort hooks, manifests, and partials. Only hooks and manifests are returned, 781 // as partials are not used after renderer.Render. Empty manifests are also 782 // removed here. 783 hooks, manifests, err := sortManifests(files, vs, InstallOrder) 784 if err != nil { 785 // By catching parse errors here, we can prevent bogus releases from going 786 // to Kubernetes. 787 // 788 // We return the files as a big blob of data to help the user debug parser 789 // errors. 790 b := bytes.NewBuffer(nil) 791 for name, content := range files { 792 if len(strings.TrimSpace(content)) == 0 { 793 continue 794 } 795 b.WriteString("\n---\n# Source: " + name + "\n") 796 b.WriteString(content) 797 } 798 return nil, b, "", err 799 } 800 801 // Aggregate all valid manifests into one big doc. 802 b := bytes.NewBuffer(nil) 803 for _, m := range manifests { 804 b.WriteString("\n---\n# Source: " + m.name + "\n") 805 b.WriteString(m.content) 806 } 807 808 return hooks, b, notes, nil 809 } 810 811 func (s *ReleaseServer) recordRelease(r *release.Release, reuse bool) { 812 if reuse { 813 if err := s.env.Releases.Update(r); err != nil { 814 log.Printf("warning: Failed to update release %q: %s", r.Name, err) 815 } 816 } else if err := s.env.Releases.Create(r); err != nil { 817 log.Printf("warning: Failed to record release %q: %s", r.Name, err) 818 } 819 } 820 821 // performRelease runs a release. 822 func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { 823 res := &services.InstallReleaseResponse{Release: r} 824 825 if req.DryRun { 826 log.Printf("Dry run for %s", r.Name) 827 res.Release.Info.Description = "Dry run complete" 828 return res, nil 829 } 830 831 // pre-install hooks 832 if !req.DisableHooks { 833 if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil { 834 return res, err 835 } 836 } 837 838 switch h, err := s.env.Releases.History(req.Name); { 839 // if this is a replace operation, append to the release history 840 case req.ReuseName && err == nil && len(h) >= 1: 841 // get latest release revision 842 relutil.Reverse(h, relutil.SortByRevision) 843 844 // old release 845 old := h[0] 846 847 // update old release status 848 old.Info.Status.Code = release.Status_SUPERSEDED 849 s.recordRelease(old, true) 850 851 // update new release with next revision number 852 // so as to append to the old release's history 853 r.Version = old.Version + 1 854 855 if err := s.performKubeUpdate(old, r, false, req.Timeout, req.Wait); err != nil { 856 msg := fmt.Sprintf("Release replace %q failed: %s", r.Name, err) 857 log.Printf("warning: %s", msg) 858 old.Info.Status.Code = release.Status_SUPERSEDED 859 r.Info.Status.Code = release.Status_FAILED 860 r.Info.Description = msg 861 s.recordRelease(old, true) 862 s.recordRelease(r, false) 863 return res, err 864 } 865 866 default: 867 // nothing to replace, create as normal 868 // regular manifests 869 b := bytes.NewBufferString(r.Manifest) 870 if err := s.env.KubeClient.Create(r.Namespace, b, req.Timeout, req.Wait); err != nil { 871 msg := fmt.Sprintf("Release %q failed: %s", r.Name, err) 872 log.Printf("warning: %s", msg) 873 r.Info.Status.Code = release.Status_FAILED 874 r.Info.Description = msg 875 s.recordRelease(r, false) 876 return res, fmt.Errorf("release %s failed: %s", r.Name, err) 877 } 878 } 879 880 // post-install hooks 881 if !req.DisableHooks { 882 if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PostInstall, req.Timeout); err != nil { 883 msg := fmt.Sprintf("Release %q failed post-install: %s", r.Name, err) 884 log.Printf("warning: %s", msg) 885 r.Info.Status.Code = release.Status_FAILED 886 r.Info.Description = msg 887 s.recordRelease(r, false) 888 return res, err 889 } 890 } 891 892 r.Info.Status.Code = release.Status_DEPLOYED 893 r.Info.Description = "Install complete" 894 // This is a tricky case. The release has been created, but the result 895 // cannot be recorded. The truest thing to tell the user is that the 896 // release was created. However, the user will not be able to do anything 897 // further with this release. 898 // 899 // One possible strategy would be to do a timed retry to see if we can get 900 // this stored in the future. 901 s.recordRelease(r, false) 902 903 return res, nil 904 } 905 906 func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook string, timeout int64) error { 907 kubeCli := s.env.KubeClient 908 code, ok := events[hook] 909 if !ok { 910 return fmt.Errorf("unknown hook %q", hook) 911 } 912 913 log.Printf("Executing %s hooks for %s", hook, name) 914 for _, h := range hs { 915 found := false 916 for _, e := range h.Events { 917 if e == code { 918 found = true 919 } 920 } 921 // If this doesn't implement the hook, skip it. 922 if !found { 923 continue 924 } 925 926 b := bytes.NewBufferString(h.Manifest) 927 if err := kubeCli.Create(namespace, b, timeout, false); err != nil { 928 log.Printf("warning: Release %q %s %s failed: %s", name, hook, h.Path, err) 929 return err 930 } 931 // No way to rewind a bytes.Buffer()? 932 b.Reset() 933 b.WriteString(h.Manifest) 934 if err := kubeCli.WatchUntilReady(namespace, b, timeout, false); err != nil { 935 log.Printf("warning: Release %q %s %s could not complete: %s", name, hook, h.Path, err) 936 return err 937 } 938 h.LastRun = timeconv.Now() 939 } 940 log.Printf("Hooks complete for %s %s", hook, name) 941 return nil 942 } 943 944 func (s *ReleaseServer) purgeReleases(rels ...*release.Release) error { 945 for _, rel := range rels { 946 if _, err := s.env.Releases.Delete(rel.Name, rel.Version); err != nil { 947 return err 948 } 949 } 950 return nil 951 } 952 953 // UninstallRelease deletes all of the resources associated with this release, and marks the release DELETED. 954 func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { 955 if !ValidName.MatchString(req.Name) { 956 log.Printf("uninstall: Release not found: %s", req.Name) 957 return nil, errMissingRelease 958 } 959 960 if len(req.Name) > releaseNameMaxLen { 961 return nil, fmt.Errorf("release name %q exceeds max length of %d", req.Name, releaseNameMaxLen) 962 } 963 964 rels, err := s.env.Releases.History(req.Name) 965 if err != nil { 966 log.Printf("uninstall: Release not loaded: %s", req.Name) 967 return nil, err 968 } 969 if len(rels) < 1 { 970 return nil, errMissingRelease 971 } 972 973 relutil.SortByRevision(rels) 974 rel := rels[len(rels)-1] 975 976 // TODO: Are there any cases where we want to force a delete even if it's 977 // already marked deleted? 978 if rel.Info.Status.Code == release.Status_DELETED { 979 if req.Purge { 980 if err := s.purgeReleases(rels...); err != nil { 981 log.Printf("uninstall: Failed to purge the release: %s", err) 982 return nil, err 983 } 984 return &services.UninstallReleaseResponse{Release: rel}, nil 985 } 986 return nil, fmt.Errorf("the release named %q is already deleted", req.Name) 987 } 988 989 log.Printf("uninstall: Deleting %s", req.Name) 990 rel.Info.Status.Code = release.Status_DELETING 991 rel.Info.Deleted = timeconv.Now() 992 rel.Info.Description = "Deletion in progress (or silently failed)" 993 res := &services.UninstallReleaseResponse{Release: rel} 994 995 if !req.DisableHooks { 996 if err := s.execHook(rel.Hooks, rel.Name, rel.Namespace, hooks.PreDelete, req.Timeout); err != nil { 997 return res, err 998 } 999 } 1000 1001 vs, err := getVersionSet(s.clientset.Discovery()) 1002 if err != nil { 1003 return nil, fmt.Errorf("Could not get apiVersions from Kubernetes: %s", err) 1004 } 1005 1006 // From here on out, the release is currently considered to be in Status_DELETING 1007 // state. 1008 if err := s.env.Releases.Update(rel); err != nil { 1009 log.Printf("uninstall: Failed to store updated release: %s", err) 1010 } 1011 1012 manifests := relutil.SplitManifests(rel.Manifest) 1013 _, files, err := sortManifests(manifests, vs, UninstallOrder) 1014 if err != nil { 1015 // We could instead just delete everything in no particular order. 1016 // FIXME: One way to delete at this point would be to try a label-based 1017 // deletion. The problem with this is that we could get a false positive 1018 // and delete something that was not legitimately part of this release. 1019 return nil, fmt.Errorf("corrupted release record. You must manually delete the resources: %s", err) 1020 } 1021 1022 filesToKeep, filesToDelete := filterManifestsToKeep(files) 1023 if len(filesToKeep) > 0 { 1024 res.Info = summarizeKeptManifests(filesToKeep) 1025 } 1026 1027 // Collect the errors, and return them later. 1028 es := []string{} 1029 for _, file := range filesToDelete { 1030 b := bytes.NewBufferString(file.content) 1031 if err := s.env.KubeClient.Delete(rel.Namespace, b); err != nil { 1032 log.Printf("uninstall: Failed deletion of %q: %s", req.Name, err) 1033 if err == kube.ErrNoObjectsVisited { 1034 // Rewrite the message from "no objects visited" 1035 err = errors.New("object not found, skipping delete") 1036 } 1037 es = append(es, err.Error()) 1038 } 1039 } 1040 1041 if !req.DisableHooks { 1042 if err := s.execHook(rel.Hooks, rel.Name, rel.Namespace, hooks.PostDelete, req.Timeout); err != nil { 1043 es = append(es, err.Error()) 1044 } 1045 } 1046 1047 rel.Info.Status.Code = release.Status_DELETED 1048 rel.Info.Description = "Deletion complete" 1049 1050 if req.Purge { 1051 err := s.purgeReleases(rels...) 1052 if err != nil { 1053 log.Printf("uninstall: Failed to purge the release: %s", err) 1054 } 1055 return res, err 1056 } 1057 1058 if err := s.env.Releases.Update(rel); err != nil { 1059 log.Printf("uninstall: Failed to store updated release: %s", err) 1060 } 1061 1062 if len(es) > 0 { 1063 return res, fmt.Errorf("deletion completed with %d error(s): %s", len(es), strings.Join(es, "; ")) 1064 } 1065 return res, nil 1066 } 1067 1068 func validateManifest(c environment.KubeClient, ns string, manifest []byte) error { 1069 r := bytes.NewReader(manifest) 1070 _, err := c.BuildUnstructured(ns, r) 1071 return err 1072 } 1073 1074 // RunReleaseTest runs pre-defined tests stored as hooks on a given release 1075 func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream services.ReleaseService_RunReleaseTestServer) error { 1076 1077 if !ValidName.MatchString(req.Name) { 1078 return errMissingRelease 1079 } 1080 1081 // finds the non-deleted release with the given name 1082 rel, err := s.env.Releases.Last(req.Name) 1083 if err != nil { 1084 return err 1085 } 1086 1087 testEnv := &reltesting.Environment{ 1088 Namespace: rel.Namespace, 1089 KubeClient: s.env.KubeClient, 1090 Timeout: req.Timeout, 1091 Stream: stream, 1092 } 1093 1094 tSuite, err := reltesting.NewTestSuite(rel) 1095 if err != nil { 1096 log.Printf("Error creating test suite for %s", rel.Name) 1097 return err 1098 } 1099 1100 if err := tSuite.Run(testEnv); err != nil { 1101 log.Printf("Error running test suite for %s", rel.Name) 1102 return err 1103 } 1104 1105 rel.Info.Status.LastTestSuiteRun = &release.TestSuite{ 1106 StartedAt: tSuite.StartedAt, 1107 CompletedAt: tSuite.CompletedAt, 1108 Results: tSuite.Results, 1109 } 1110 1111 if req.Cleanup { 1112 testEnv.DeleteTestPods(tSuite.TestManifests) 1113 } 1114 1115 return s.env.Releases.Update(rel) 1116 }