github.com/sgoings/helm@v2.0.0-alpha.2.0.20170406211108-734e92851ac3+incompatible/pkg/tiller/release_server.go (about)

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