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  }