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  }