github.com/azlyth/helm@v2.8.2+incompatible/pkg/tiller/release_update.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  	"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  	"k8s.io/helm/pkg/timeconv"
    30  )
    31  
    32  // UpdateRelease takes an existing release and new information, and upgrades the release.
    33  func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
    34  	if err := validateReleaseName(req.Name); err != nil {
    35  		s.Log("updateRelease: Release name is invalid: %s", req.Name)
    36  		return nil, err
    37  	}
    38  	s.Log("preparing update for %s", req.Name)
    39  	currentRelease, updatedRelease, err := s.prepareUpdate(req)
    40  	if err != nil {
    41  		if req.Force {
    42  			// Use the --force, Luke.
    43  			return s.performUpdateForce(req)
    44  		}
    45  		return nil, err
    46  	}
    47  
    48  	if !req.DryRun {
    49  		s.Log("creating updated release for %s", req.Name)
    50  		if err := s.env.Releases.Create(updatedRelease); err != nil {
    51  			return nil, err
    52  		}
    53  	}
    54  
    55  	s.Log("performing update for %s", req.Name)
    56  	res, err := s.performUpdate(currentRelease, updatedRelease, req)
    57  	if err != nil {
    58  		return res, err
    59  	}
    60  
    61  	if !req.DryRun {
    62  		s.Log("updating status for updated release for %s", req.Name)
    63  		if err := s.env.Releases.Update(updatedRelease); err != nil {
    64  			return res, err
    65  		}
    66  	}
    67  
    68  	return res, nil
    69  }
    70  
    71  // prepareUpdate builds an updated release for an update operation.
    72  func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) {
    73  	if req.Chart == nil {
    74  		return nil, nil, errMissingChart
    75  	}
    76  
    77  	// finds the deployed release with the given name
    78  	currentRelease, err := s.env.Releases.Deployed(req.Name)
    79  	if err != nil {
    80  		return nil, nil, err
    81  	}
    82  
    83  	// If new values were not supplied in the upgrade, re-use the existing values.
    84  	if err := s.reuseValues(req, currentRelease); err != nil {
    85  		return nil, nil, err
    86  	}
    87  
    88  	// finds the non-deleted release with the given name
    89  	lastRelease, err := s.env.Releases.Last(req.Name)
    90  	if err != nil {
    91  		return nil, nil, err
    92  	}
    93  
    94  	// Increment revision count. This is passed to templates, and also stored on
    95  	// the release object.
    96  	revision := lastRelease.Version + 1
    97  
    98  	ts := timeconv.Now()
    99  	options := chartutil.ReleaseOptions{
   100  		Name:      req.Name,
   101  		Time:      ts,
   102  		Namespace: currentRelease.Namespace,
   103  		IsUpgrade: true,
   104  		Revision:  int(revision),
   105  	}
   106  
   107  	caps, err := capabilities(s.clientset.Discovery())
   108  	if err != nil {
   109  		return nil, nil, err
   110  	}
   111  	valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps)
   112  	if err != nil {
   113  		return nil, nil, err
   114  	}
   115  
   116  	hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions)
   117  	if err != nil {
   118  		return nil, nil, err
   119  	}
   120  
   121  	// Store an updated release.
   122  	updatedRelease := &release.Release{
   123  		Name:      req.Name,
   124  		Namespace: currentRelease.Namespace,
   125  		Chart:     req.Chart,
   126  		Config:    req.Values,
   127  		Info: &release.Info{
   128  			FirstDeployed: currentRelease.Info.FirstDeployed,
   129  			LastDeployed:  ts,
   130  			Status:        &release.Status{Code: release.Status_PENDING_UPGRADE},
   131  			Description:   "Preparing upgrade", // This should be overwritten later.
   132  		},
   133  		Version:  revision,
   134  		Manifest: manifestDoc.String(),
   135  		Hooks:    hooks,
   136  	}
   137  
   138  	if len(notesTxt) > 0 {
   139  		updatedRelease.Info.Status.Notes = notesTxt
   140  	}
   141  	err = validateManifest(s.env.KubeClient, currentRelease.Namespace, manifestDoc.Bytes())
   142  	return currentRelease, updatedRelease, err
   143  }
   144  
   145  // performUpdateForce performs the same action as a `helm delete && helm install --replace`.
   146  func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
   147  	// find the last release with the given name
   148  	oldRelease, err := s.env.Releases.Last(req.Name)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	newRelease, err := s.prepareRelease(&services.InstallReleaseRequest{
   154  		Chart:        req.Chart,
   155  		Values:       req.Values,
   156  		DryRun:       req.DryRun,
   157  		Name:         req.Name,
   158  		DisableHooks: req.DisableHooks,
   159  		Namespace:    oldRelease.Namespace,
   160  		ReuseName:    true,
   161  		Timeout:      req.Timeout,
   162  		Wait:         req.Wait,
   163  	})
   164  	res := &services.UpdateReleaseResponse{Release: newRelease}
   165  	if err != nil {
   166  		s.Log("failed update prepare step: %s", err)
   167  		// On dry run, append the manifest contents to a failed release. This is
   168  		// a stop-gap until we can revisit an error backchannel post-2.0.
   169  		if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") {
   170  			err = fmt.Errorf("%s\n%s", err, newRelease.Manifest)
   171  		}
   172  		return res, err
   173  	}
   174  
   175  	// From here on out, the release is considered to be in Status_DELETING or Status_DELETED
   176  	// state. There is no turning back.
   177  	oldRelease.Info.Status.Code = release.Status_DELETING
   178  	oldRelease.Info.Deleted = timeconv.Now()
   179  	oldRelease.Info.Description = "Deletion in progress (or silently failed)"
   180  	s.recordRelease(oldRelease, true)
   181  
   182  	// pre-delete hooks
   183  	if !req.DisableHooks {
   184  		if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PreDelete, req.Timeout); err != nil {
   185  			return res, err
   186  		}
   187  	} else {
   188  		s.Log("hooks disabled for %s", req.Name)
   189  	}
   190  
   191  	// delete manifests from the old release
   192  	_, errs := s.ReleaseModule.Delete(oldRelease, nil, s.env)
   193  
   194  	oldRelease.Info.Status.Code = release.Status_DELETED
   195  	oldRelease.Info.Description = "Deletion complete"
   196  	s.recordRelease(oldRelease, true)
   197  
   198  	if len(errs) > 0 {
   199  		es := make([]string, 0, len(errs))
   200  		for _, e := range errs {
   201  			s.Log("error: %v", e)
   202  			es = append(es, e.Error())
   203  		}
   204  		return res, fmt.Errorf("Upgrade --force successfully deleted the previous release, but encountered %d error(s) and cannot continue: %s", len(es), strings.Join(es, "; "))
   205  	}
   206  
   207  	// post-delete hooks
   208  	if !req.DisableHooks {
   209  		if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PostDelete, req.Timeout); err != nil {
   210  			return res, err
   211  		}
   212  	}
   213  
   214  	// pre-install hooks
   215  	if !req.DisableHooks {
   216  		if err := s.execHook(newRelease.Hooks, newRelease.Name, newRelease.Namespace, hooks.PreInstall, req.Timeout); err != nil {
   217  			return res, err
   218  		}
   219  	}
   220  
   221  	// update new release with next revision number so as to append to the old release's history
   222  	newRelease.Version = oldRelease.Version + 1
   223  	s.recordRelease(newRelease, false)
   224  	if err := s.ReleaseModule.Update(oldRelease, newRelease, req, s.env); err != nil {
   225  		msg := fmt.Sprintf("Upgrade %q failed: %s", newRelease.Name, err)
   226  		s.Log("warning: %s", msg)
   227  		newRelease.Info.Status.Code = release.Status_FAILED
   228  		newRelease.Info.Description = msg
   229  		s.recordRelease(newRelease, true)
   230  		return res, err
   231  	}
   232  
   233  	// post-install hooks
   234  	if !req.DisableHooks {
   235  		if err := s.execHook(newRelease.Hooks, newRelease.Name, newRelease.Namespace, hooks.PostInstall, req.Timeout); err != nil {
   236  			msg := fmt.Sprintf("Release %q failed post-install: %s", newRelease.Name, err)
   237  			s.Log("warning: %s", msg)
   238  			newRelease.Info.Status.Code = release.Status_FAILED
   239  			newRelease.Info.Description = msg
   240  			s.recordRelease(newRelease, true)
   241  			return res, err
   242  		}
   243  	}
   244  
   245  	newRelease.Info.Status.Code = release.Status_DEPLOYED
   246  	newRelease.Info.Description = "Upgrade complete"
   247  	s.recordRelease(newRelease, true)
   248  
   249  	return res, nil
   250  }
   251  
   252  func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
   253  	res := &services.UpdateReleaseResponse{Release: updatedRelease}
   254  
   255  	if req.DryRun {
   256  		s.Log("dry run for %s", updatedRelease.Name)
   257  		res.Release.Info.Description = "Dry run complete"
   258  		return res, nil
   259  	}
   260  
   261  	// pre-upgrade hooks
   262  	if !req.DisableHooks {
   263  		if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil {
   264  			return res, err
   265  		}
   266  	} else {
   267  		s.Log("update hooks disabled for %s", req.Name)
   268  	}
   269  	if err := s.ReleaseModule.Update(originalRelease, updatedRelease, req, s.env); err != nil {
   270  		msg := fmt.Sprintf("Upgrade %q failed: %s", updatedRelease.Name, err)
   271  		s.Log("warning: %s", msg)
   272  		updatedRelease.Info.Status.Code = release.Status_FAILED
   273  		updatedRelease.Info.Description = msg
   274  		s.recordRelease(originalRelease, true)
   275  		s.recordRelease(updatedRelease, true)
   276  		return res, err
   277  	}
   278  
   279  	// post-upgrade hooks
   280  	if !req.DisableHooks {
   281  		if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PostUpgrade, req.Timeout); err != nil {
   282  			return res, err
   283  		}
   284  	}
   285  
   286  	originalRelease.Info.Status.Code = release.Status_SUPERSEDED
   287  	s.recordRelease(originalRelease, true)
   288  
   289  	updatedRelease.Info.Status.Code = release.Status_DEPLOYED
   290  	updatedRelease.Info.Description = "Upgrade complete"
   291  
   292  	return res, nil
   293  }