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