github.com/Beeketing/helm@v2.12.1+incompatible/pkg/tiller/release_update.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  	"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  	// determine if values will be reused
    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  	res := &services.UpdateReleaseResponse{}
   154  
   155  	newRelease, err := s.prepareRelease(&services.InstallReleaseRequest{
   156  		Chart:        req.Chart,
   157  		Values:       req.Values,
   158  		DryRun:       req.DryRun,
   159  		Name:         req.Name,
   160  		DisableHooks: req.DisableHooks,
   161  		Namespace:    oldRelease.Namespace,
   162  		ReuseName:    true,
   163  		Timeout:      req.Timeout,
   164  		Wait:         req.Wait,
   165  	})
   166  	if err != nil {
   167  		s.Log("failed update prepare step: %s", err)
   168  		// On dry run, append the manifest contents to a failed release. This is
   169  		// a stop-gap until we can revisit an error backchannel post-2.0.
   170  		if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") {
   171  			err = fmt.Errorf("%s\n%s", err, newRelease.Manifest)
   172  		}
   173  		return res, err
   174  	}
   175  
   176  	// update new release with next revision number so as to append to the old release's history
   177  	newRelease.Version = oldRelease.Version + 1
   178  	res.Release = newRelease
   179  
   180  	if req.DryRun {
   181  		s.Log("dry run for %s", newRelease.Name)
   182  		res.Release.Info.Description = "Dry run complete"
   183  		return res, nil
   184  	}
   185  
   186  	// From here on out, the release is considered to be in Status_DELETING or Status_DELETED
   187  	// state. There is no turning back.
   188  	oldRelease.Info.Status.Code = release.Status_DELETING
   189  	oldRelease.Info.Deleted = timeconv.Now()
   190  	oldRelease.Info.Description = "Deletion in progress (or silently failed)"
   191  	s.recordRelease(oldRelease, true)
   192  
   193  	// pre-delete hooks
   194  	if !req.DisableHooks {
   195  		if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PreDelete, req.Timeout); err != nil {
   196  			return res, err
   197  		}
   198  	} else {
   199  		s.Log("hooks disabled for %s", req.Name)
   200  	}
   201  
   202  	// delete manifests from the old release
   203  	_, errs := s.ReleaseModule.Delete(oldRelease, nil, s.env)
   204  
   205  	oldRelease.Info.Status.Code = release.Status_DELETED
   206  	oldRelease.Info.Description = "Deletion complete"
   207  	s.recordRelease(oldRelease, true)
   208  
   209  	if len(errs) > 0 {
   210  		es := make([]string, 0, len(errs))
   211  		for _, e := range errs {
   212  			s.Log("error: %v", e)
   213  			es = append(es, e.Error())
   214  		}
   215  		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, "; "))
   216  	}
   217  
   218  	// post-delete hooks
   219  	if !req.DisableHooks {
   220  		if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PostDelete, req.Timeout); err != nil {
   221  			return res, err
   222  		}
   223  	}
   224  
   225  	// pre-install hooks
   226  	if !req.DisableHooks {
   227  		if err := s.execHook(newRelease.Hooks, newRelease.Name, newRelease.Namespace, hooks.PreInstall, req.Timeout); err != nil {
   228  			return res, err
   229  		}
   230  	}
   231  
   232  	s.recordRelease(newRelease, false)
   233  	if err := s.ReleaseModule.Update(oldRelease, newRelease, req, s.env); err != nil {
   234  		msg := fmt.Sprintf("Upgrade %q failed: %s", newRelease.Name, err)
   235  		s.Log("warning: %s", msg)
   236  		newRelease.Info.Status.Code = release.Status_FAILED
   237  		newRelease.Info.Description = msg
   238  		s.recordRelease(newRelease, true)
   239  		return res, err
   240  	}
   241  
   242  	// post-install hooks
   243  	if !req.DisableHooks {
   244  		if err := s.execHook(newRelease.Hooks, newRelease.Name, newRelease.Namespace, hooks.PostInstall, req.Timeout); err != nil {
   245  			msg := fmt.Sprintf("Release %q failed post-install: %s", newRelease.Name, err)
   246  			s.Log("warning: %s", msg)
   247  			newRelease.Info.Status.Code = release.Status_FAILED
   248  			newRelease.Info.Description = msg
   249  			s.recordRelease(newRelease, true)
   250  			return res, err
   251  		}
   252  	}
   253  
   254  	newRelease.Info.Status.Code = release.Status_DEPLOYED
   255  	if req.Description == "" {
   256  		newRelease.Info.Description = "Upgrade complete"
   257  	} else {
   258  		newRelease.Info.Description = req.Description
   259  	}
   260  	s.recordRelease(newRelease, true)
   261  
   262  	return res, nil
   263  }
   264  
   265  func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
   266  	res := &services.UpdateReleaseResponse{Release: updatedRelease}
   267  
   268  	if req.DryRun {
   269  		s.Log("dry run for %s", updatedRelease.Name)
   270  		res.Release.Info.Description = "Dry run complete"
   271  		return res, nil
   272  	}
   273  
   274  	// pre-upgrade hooks
   275  	if !req.DisableHooks {
   276  		if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil {
   277  			return res, err
   278  		}
   279  	} else {
   280  		s.Log("update hooks disabled for %s", req.Name)
   281  	}
   282  	if err := s.ReleaseModule.Update(originalRelease, updatedRelease, req, s.env); err != nil {
   283  		msg := fmt.Sprintf("Upgrade %q failed: %s", updatedRelease.Name, err)
   284  		s.Log("warning: %s", msg)
   285  		updatedRelease.Info.Status.Code = release.Status_FAILED
   286  		updatedRelease.Info.Description = msg
   287  		s.recordRelease(originalRelease, true)
   288  		s.recordRelease(updatedRelease, true)
   289  		return res, err
   290  	}
   291  
   292  	// post-upgrade hooks
   293  	if !req.DisableHooks {
   294  		if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PostUpgrade, req.Timeout); err != nil {
   295  			return res, err
   296  		}
   297  	}
   298  
   299  	originalRelease.Info.Status.Code = release.Status_SUPERSEDED
   300  	s.recordRelease(originalRelease, true)
   301  
   302  	updatedRelease.Info.Status.Code = release.Status_DEPLOYED
   303  	if req.Description == "" {
   304  		updatedRelease.Info.Description = "Upgrade complete"
   305  	} else {
   306  		updatedRelease.Info.Description = req.Description
   307  	}
   308  
   309  	return res, nil
   310  }