github.com/koderover/helm@v2.17.0+incompatible/pkg/tiller/release_install.go (about)

     1  /*
     2  Copyright The Helm Authors.
     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  	"fmt"
    21  	"strings"
    22  
    23  	ctx "golang.org/x/net/context"
    24  
    25  	"k8s.io/helm/pkg/chartutil"
    26  	"k8s.io/helm/pkg/hooks"
    27  	"k8s.io/helm/pkg/proto/hapi/release"
    28  	"k8s.io/helm/pkg/proto/hapi/services"
    29  	relutil "k8s.io/helm/pkg/releaseutil"
    30  	"k8s.io/helm/pkg/timeconv"
    31  )
    32  
    33  // InstallRelease installs a release and stores the release record.
    34  func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) {
    35  	s.Log("preparing install for %s", req.Name)
    36  	rel, err := s.prepareRelease(req)
    37  	if err != nil {
    38  		s.Log("failed install prepare step: %s", err)
    39  		res := &services.InstallReleaseResponse{Release: rel}
    40  
    41  		// On dry run, append the manifest contents to a failed release. This is
    42  		// a stop-gap until we can revisit an error backchannel post-2.0.
    43  		if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") {
    44  			err = fmt.Errorf("%s\n%s", err, rel.Manifest)
    45  		}
    46  		return res, err
    47  	}
    48  
    49  	s.Log("performing install for %s", req.Name)
    50  	res, err := s.performRelease(rel, req)
    51  	if err != nil {
    52  		s.Log("failed install perform step: %s", err)
    53  	}
    54  	return res, err
    55  }
    56  
    57  // prepareRelease builds a release for an install operation.
    58  func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*release.Release, error) {
    59  	if req.Chart == nil {
    60  		return nil, errMissingChart
    61  	}
    62  
    63  	name, err := s.uniqName(req.Name, req.ReuseName)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	caps, err := capabilities(s.clientset.Discovery())
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	revision := 1
    74  	ts := timeconv.Now()
    75  	options := chartutil.ReleaseOptions{
    76  		Name:      name,
    77  		Time:      ts,
    78  		Namespace: req.Namespace,
    79  		Revision:  revision,
    80  		IsInstall: true,
    81  	}
    82  	valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, req.SubNotes, caps.APIVersions)
    88  	if err != nil {
    89  		// Return a release with partial data so that client can show debugging
    90  		// information.
    91  		rel := &release.Release{
    92  			Name:      name,
    93  			Namespace: req.Namespace,
    94  			Chart:     req.Chart,
    95  			Config:    req.Values,
    96  			Info: &release.Info{
    97  				FirstDeployed: ts,
    98  				LastDeployed:  ts,
    99  				Status:        &release.Status{Code: release.Status_UNKNOWN},
   100  				Description:   fmt.Sprintf("Install failed: %s", err),
   101  			},
   102  			Version: 0,
   103  		}
   104  		if manifestDoc != nil {
   105  			rel.Manifest = manifestDoc.String()
   106  		}
   107  		return rel, err
   108  	}
   109  
   110  	// Store a release.
   111  	rel := &release.Release{
   112  		Name:      name,
   113  		Namespace: req.Namespace,
   114  		Chart:     req.Chart,
   115  		Config:    req.Values,
   116  		Info: &release.Info{
   117  			FirstDeployed: ts,
   118  			LastDeployed:  ts,
   119  			Status:        &release.Status{Code: release.Status_PENDING_INSTALL},
   120  			Description:   "Initial install underway", // Will be overwritten.
   121  		},
   122  		Manifest: manifestDoc.String(),
   123  		Hooks:    hooks,
   124  		Version:  int32(revision),
   125  	}
   126  	if len(notesTxt) > 0 {
   127  		rel.Info.Status.Notes = notesTxt
   128  	}
   129  
   130  	return rel, nil
   131  }
   132  
   133  func hasCRDHook(hs []*release.Hook) bool {
   134  	for _, h := range hs {
   135  		for _, e := range h.Events {
   136  			if e == events[hooks.CRDInstall] {
   137  				return true
   138  			}
   139  		}
   140  	}
   141  	return false
   142  }
   143  
   144  // performRelease runs a release.
   145  func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) {
   146  	res := &services.InstallReleaseResponse{Release: r}
   147  	manifestDoc := []byte(r.Manifest)
   148  
   149  	if req.DryRun {
   150  		s.Log("dry run for %s", r.Name)
   151  
   152  		if !req.DisableCrdHook && hasCRDHook(r.Hooks) {
   153  			s.Log("validation skipped because CRD hook is present")
   154  			res.Release.Info.Description = "Validation skipped because CRDs are not installed"
   155  			return res, nil
   156  		}
   157  
   158  		// Here's the problem with dry runs and CRDs: We can't install a CRD
   159  		// during a dry run, which means it cannot be validated.
   160  		if err := validateManifest(s.env.KubeClient, req.Namespace, manifestDoc); err != nil {
   161  			return res, err
   162  		}
   163  
   164  		res.Release.Info.Description = "Dry run complete"
   165  		return res, nil
   166  	}
   167  
   168  	// crd-install hooks
   169  	if !req.DisableHooks && !req.DisableCrdHook {
   170  		if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.CRDInstall, req.Timeout); err != nil {
   171  			fmt.Printf("Finished installing CRD: %s", err)
   172  			return res, err
   173  		}
   174  	} else {
   175  		s.Log("CRD install hooks disabled for %s", req.Name)
   176  	}
   177  
   178  	// Because the CRDs are installed, they are used for validation during this step.
   179  	if err := validateManifest(s.env.KubeClient, req.Namespace, manifestDoc); err != nil {
   180  		return res, fmt.Errorf("validation failed: %s", err)
   181  	}
   182  
   183  	// pre-install hooks
   184  	if !req.DisableHooks {
   185  		if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil {
   186  			return res, err
   187  		}
   188  	} else {
   189  		s.Log("install hooks disabled for %s", req.Name)
   190  	}
   191  
   192  	switch h, err := s.env.Releases.History(req.Name); {
   193  	// if this is a replace operation, append to the release history
   194  	case req.ReuseName && err == nil && len(h) >= 1:
   195  		s.Log("name reuse for %s requested, replacing release", req.Name)
   196  		// get latest release revision
   197  		relutil.Reverse(h, relutil.SortByRevision)
   198  
   199  		// old release
   200  		old := h[0]
   201  
   202  		// update old release status
   203  		old.Info.Status.Code = release.Status_SUPERSEDED
   204  		s.recordRelease(old, true)
   205  
   206  		// update new release with next revision number
   207  		// so as to append to the old release's history
   208  		r.Version = old.Version + 1
   209  		updateReq := &services.UpdateReleaseRequest{
   210  			Wait:     req.Wait,
   211  			Recreate: false,
   212  			Timeout:  req.Timeout,
   213  		}
   214  		s.recordRelease(r, false)
   215  		if err := s.ReleaseModule.Update(old, r, updateReq, s.env); err != nil {
   216  			msg := fmt.Sprintf("Release replace %q failed: %s", r.Name, err)
   217  			s.Log("warning: %s", msg)
   218  			old.Info.Status.Code = release.Status_SUPERSEDED
   219  			r.Info.Status.Code = release.Status_FAILED
   220  			r.Info.Description = msg
   221  			s.recordRelease(old, true)
   222  			s.recordRelease(r, true)
   223  			return res, err
   224  		}
   225  
   226  	default:
   227  		// nothing to replace, create as normal
   228  		// regular manifests
   229  		s.recordRelease(r, false)
   230  		if err := s.ReleaseModule.Create(r, req, s.env); err != nil {
   231  			msg := fmt.Sprintf("Release %q failed: %s", r.Name, err)
   232  			s.Log("warning: %s", msg)
   233  			r.Info.Status.Code = release.Status_FAILED
   234  			r.Info.Description = msg
   235  			s.recordRelease(r, true)
   236  			return res, fmt.Errorf("release %s failed: %s", r.Name, err)
   237  		}
   238  	}
   239  
   240  	// post-install hooks
   241  	if !req.DisableHooks {
   242  		if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PostInstall, req.Timeout); err != nil {
   243  			msg := fmt.Sprintf("Release %q failed post-install: %s", r.Name, err)
   244  			s.Log("warning: %s", msg)
   245  			r.Info.Status.Code = release.Status_FAILED
   246  			r.Info.Description = msg
   247  			s.recordRelease(r, true)
   248  			return res, err
   249  		}
   250  	}
   251  
   252  	r.Info.Status.Code = release.Status_DEPLOYED
   253  	if req.Description == "" {
   254  		r.Info.Description = "Install complete"
   255  	} else {
   256  		r.Info.Description = req.Description
   257  	}
   258  	// This is a tricky case. The release has been created, but the result
   259  	// cannot be recorded. The truest thing to tell the user is that the
   260  	// release was created. However, the user will not be able to do anything
   261  	// further with this release.
   262  	//
   263  	// One possible strategy would be to do a timed retry to see if we can get
   264  	// this stored in the future.
   265  	s.recordRelease(r, true)
   266  
   267  	return res, nil
   268  }