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