github.com/x-helm/helm@v3.0.0-beta.3+incompatible/pkg/action/upgrade.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 action
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/pkg/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  
    27  	"helm.sh/helm/pkg/chart"
    28  	"helm.sh/helm/pkg/chartutil"
    29  	"helm.sh/helm/pkg/kube"
    30  	"helm.sh/helm/pkg/release"
    31  	"helm.sh/helm/pkg/releaseutil"
    32  )
    33  
    34  // Upgrade is the action for upgrading releases.
    35  //
    36  // It provides the implementation of 'helm upgrade'.
    37  type Upgrade struct {
    38  	cfg *Configuration
    39  
    40  	ChartPathOptions
    41  
    42  	Install      bool
    43  	Devel        bool
    44  	Namespace    string
    45  	Timeout      time.Duration
    46  	Wait         bool
    47  	DisableHooks bool
    48  	DryRun       bool
    49  	Force        bool
    50  	ResetValues  bool
    51  	ReuseValues  bool
    52  	// Recreate will (if true) recreate pods after a rollback.
    53  	Recreate bool
    54  	// MaxHistory limits the maximum number of revisions saved per release
    55  	MaxHistory int
    56  	Atomic     bool
    57  }
    58  
    59  // NewUpgrade creates a new Upgrade object with the given configuration.
    60  func NewUpgrade(cfg *Configuration) *Upgrade {
    61  	return &Upgrade{
    62  		cfg: cfg,
    63  	}
    64  }
    65  
    66  // Run executes the upgrade on the given release.
    67  func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
    68  	if err := chartutil.ProcessDependencies(chart, vals); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	// Make sure if Atomic is set, that wait is set as well. This makes it so
    73  	// the user doesn't have to specify both
    74  	u.Wait = u.Wait || u.Atomic
    75  
    76  	if err := validateReleaseName(name); err != nil {
    77  		return nil, errors.Errorf("release name is invalid: %s", name)
    78  	}
    79  	u.cfg.Log("preparing upgrade for %s", name)
    80  	currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	u.cfg.Releases.MaxHistory = u.MaxHistory
    86  
    87  	if !u.DryRun {
    88  		u.cfg.Log("creating upgraded release for %s", name)
    89  		if err := u.cfg.Releases.Create(upgradedRelease); err != nil {
    90  			return nil, err
    91  		}
    92  	}
    93  
    94  	u.cfg.Log("performing update for %s", name)
    95  	res, err := u.performUpgrade(currentRelease, upgradedRelease)
    96  	if err != nil {
    97  		return res, err
    98  	}
    99  
   100  	if !u.DryRun {
   101  		u.cfg.Log("updating status for upgraded release for %s", name)
   102  		if err := u.cfg.Releases.Update(upgradedRelease); err != nil {
   103  			return res, err
   104  		}
   105  	}
   106  
   107  	return res, nil
   108  }
   109  
   110  func validateReleaseName(releaseName string) error {
   111  	if releaseName == "" {
   112  		return errMissingRelease
   113  	}
   114  
   115  	if !ValidName.MatchString(releaseName) || (len(releaseName) > releaseNameMaxLen) {
   116  		return errInvalidName
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  // prepareUpgrade builds an upgraded release for an upgrade operation.
   123  func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, error) {
   124  	if chart == nil {
   125  		return nil, nil, errMissingChart
   126  	}
   127  
   128  	// finds the deployed release with the given name
   129  	currentRelease, err := u.cfg.Releases.Deployed(name)
   130  	if err != nil {
   131  		return nil, nil, err
   132  	}
   133  
   134  	// determine if values will be reused
   135  	vals, err = u.reuseValues(chart, currentRelease, vals)
   136  	if err != nil {
   137  		return nil, nil, err
   138  	}
   139  
   140  	// finds the non-deleted release with the given name
   141  	lastRelease, err := u.cfg.Releases.Last(name)
   142  	if err != nil {
   143  		return nil, nil, err
   144  	}
   145  
   146  	// Increment revision count. This is passed to templates, and also stored on
   147  	// the release object.
   148  	revision := lastRelease.Version + 1
   149  
   150  	options := chartutil.ReleaseOptions{
   151  		Name:      name,
   152  		Namespace: currentRelease.Namespace,
   153  		IsUpgrade: true,
   154  	}
   155  
   156  	caps, err := u.cfg.getCapabilities()
   157  	if err != nil {
   158  		return nil, nil, err
   159  	}
   160  	valuesToRender, err := chartutil.ToRenderValues(chart, vals, options, caps)
   161  	if err != nil {
   162  		return nil, nil, err
   163  	}
   164  
   165  	hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "")
   166  	if err != nil {
   167  		return nil, nil, err
   168  	}
   169  
   170  	// Store an upgraded release.
   171  	upgradedRelease := &release.Release{
   172  		Name:      name,
   173  		Namespace: currentRelease.Namespace,
   174  		Chart:     chart,
   175  		Config:    vals,
   176  		Info: &release.Info{
   177  			FirstDeployed: currentRelease.Info.FirstDeployed,
   178  			LastDeployed:  Timestamper(),
   179  			Status:        release.StatusPendingUpgrade,
   180  			Description:   "Preparing upgrade", // This should be overwritten later.
   181  		},
   182  		Version:  revision,
   183  		Manifest: manifestDoc.String(),
   184  		Hooks:    hooks,
   185  	}
   186  
   187  	if len(notesTxt) > 0 {
   188  		upgradedRelease.Info.Notes = notesTxt
   189  	}
   190  	err = validateManifest(u.cfg.KubeClient, manifestDoc.Bytes())
   191  	return currentRelease, upgradedRelease, err
   192  }
   193  
   194  func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Release) (*release.Release, error) {
   195  	if u.DryRun {
   196  		u.cfg.Log("dry run for %s", upgradedRelease.Name)
   197  		upgradedRelease.Info.Description = "Dry run complete"
   198  		return upgradedRelease, nil
   199  	}
   200  
   201  	current, err := u.cfg.KubeClient.Build(bytes.NewBufferString(originalRelease.Manifest))
   202  	if err != nil {
   203  		return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest")
   204  	}
   205  	target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest))
   206  	if err != nil {
   207  		return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest")
   208  	}
   209  
   210  	// pre-upgrade hooks
   211  	if !u.DisableHooks {
   212  		if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout); err != nil {
   213  			return u.failRelease(upgradedRelease, fmt.Errorf("pre-upgrade hooks failed: %s", err))
   214  		}
   215  	} else {
   216  		u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name)
   217  	}
   218  
   219  	results, err := u.cfg.KubeClient.Update(current, target, u.Force)
   220  	if err != nil {
   221  		u.cfg.recordRelease(originalRelease)
   222  		return u.failRelease(upgradedRelease, err)
   223  	}
   224  
   225  	if u.Recreate {
   226  		// NOTE: Because this is not critical for a release to succeed, we just
   227  		// log if an error occurs and continue onward. If we ever introduce log
   228  		// levels, we should make these error level logs so users are notified
   229  		// that they'll need to go do the cleanup on their own
   230  		if err := recreate(u.cfg, results.Updated); err != nil {
   231  			u.cfg.Log(err.Error())
   232  		}
   233  	}
   234  
   235  	if u.Wait {
   236  		if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil {
   237  			u.cfg.recordRelease(originalRelease)
   238  			return u.failRelease(upgradedRelease, err)
   239  		}
   240  	}
   241  
   242  	// post-upgrade hooks
   243  	if !u.DisableHooks {
   244  		if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout); err != nil {
   245  			return u.failRelease(upgradedRelease, fmt.Errorf("post-upgrade hooks failed: %s", err))
   246  		}
   247  	}
   248  
   249  	originalRelease.Info.Status = release.StatusSuperseded
   250  	u.cfg.recordRelease(originalRelease)
   251  
   252  	upgradedRelease.Info.Status = release.StatusDeployed
   253  	upgradedRelease.Info.Description = "Upgrade complete"
   254  
   255  	return upgradedRelease, nil
   256  }
   257  
   258  func (u *Upgrade) failRelease(rel *release.Release, err error) (*release.Release, error) {
   259  	msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err)
   260  	u.cfg.Log("warning: %s", msg)
   261  
   262  	rel.Info.Status = release.StatusFailed
   263  	rel.Info.Description = msg
   264  	u.cfg.recordRelease(rel)
   265  	if u.Atomic {
   266  		u.cfg.Log("Upgrade failed and atomic is set, rolling back to last successful release")
   267  
   268  		// As a protection, get the last successful release before rollback.
   269  		// If there are no successful releases, bail out
   270  		hist := NewHistory(u.cfg)
   271  		fullHistory, herr := hist.Run(rel.Name)
   272  		if herr != nil {
   273  			return rel, errors.Wrapf(herr, "an error occurred while finding last successful release. original upgrade error: %s", err)
   274  		}
   275  
   276  		// There isn't a way to tell if a previous release was successful, but
   277  		// generally failed releases do not get superseded unless the next
   278  		// release is successful, so this should be relatively safe
   279  		filteredHistory := releaseutil.FilterFunc(func(r *release.Release) bool {
   280  			return r.Info.Status == release.StatusSuperseded || r.Info.Status == release.StatusDeployed
   281  		}).Filter(fullHistory)
   282  		if len(filteredHistory) == 0 {
   283  			return rel, errors.Wrap(err, "unable to find a previously successful release when attempting to rollback. original upgrade error")
   284  		}
   285  
   286  		releaseutil.Reverse(filteredHistory, releaseutil.SortByRevision)
   287  
   288  		rollin := NewRollback(u.cfg)
   289  		rollin.Version = filteredHistory[0].Version
   290  		rollin.Wait = true
   291  		rollin.DisableHooks = u.DisableHooks
   292  		rollin.Recreate = u.Recreate
   293  		rollin.Force = u.Force
   294  		rollin.Timeout = u.Timeout
   295  		if rollErr := rollin.Run(rel.Name); rollErr != nil {
   296  			return rel, errors.Wrapf(rollErr, "an error occurred while rolling back the release. original upgrade error: %s", err)
   297  		}
   298  		return rel, errors.Wrapf(err, "release %s failed, and has been rolled back due to atomic being set", rel.Name)
   299  	}
   300  
   301  	return rel, err
   302  }
   303  
   304  // reuseValues copies values from the current release to a new release if the
   305  // new release does not have any values.
   306  //
   307  // If the request already has values, or if there are no values in the current
   308  // release, this does nothing.
   309  //
   310  // This is skipped if the u.ResetValues flag is set, in which case the
   311  // request values are not altered.
   312  func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) {
   313  	if u.ResetValues {
   314  		// If ResetValues is set, we completely ignore current.Config.
   315  		u.cfg.Log("resetting values to the chart's original version")
   316  		return newVals, nil
   317  	}
   318  
   319  	// If the ReuseValues flag is set, we always copy the old values over the new config's values.
   320  	if u.ReuseValues {
   321  		u.cfg.Log("reusing the old release's values")
   322  
   323  		// We have to regenerate the old coalesced values:
   324  		oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config)
   325  		if err != nil {
   326  			return nil, errors.Wrap(err, "failed to rebuild old values")
   327  		}
   328  
   329  		newVals = chartutil.CoalesceTables(newVals, current.Config)
   330  
   331  		chart.Values = oldVals
   332  
   333  		return newVals, nil
   334  	}
   335  
   336  	if len(newVals) == 0 && len(current.Config) > 0 {
   337  		u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version)
   338  		newVals = current.Config
   339  	}
   340  	return newVals, nil
   341  }
   342  
   343  func validateManifest(c kube.Interface, manifest []byte) error {
   344  	_, err := c.Build(bytes.NewReader(manifest))
   345  	return err
   346  }
   347  
   348  // recreate captures all the logic for recreating pods for both upgrade and
   349  // rollback. If we end up refactoring rollback to use upgrade, this can just be
   350  // made an unexported method on the upgrade action.
   351  func recreate(cfg *Configuration, resources kube.ResourceList) error {
   352  	for _, res := range resources {
   353  		versioned := kube.AsVersioned(res)
   354  		selector, err := kube.SelectorsForObject(versioned)
   355  		if err != nil {
   356  			// If no selector is returned, it means this object is
   357  			// definitely not a pod, so continue onward
   358  			continue
   359  		}
   360  
   361  		client, err := cfg.KubernetesClientSet()
   362  		if err != nil {
   363  			return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name)
   364  		}
   365  
   366  		pods, err := client.CoreV1().Pods(res.Namespace).List(metav1.ListOptions{
   367  			LabelSelector: selector.String(),
   368  		})
   369  		if err != nil {
   370  			return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name)
   371  		}
   372  
   373  		// Restart pods
   374  		for _, pod := range pods.Items {
   375  			// Delete each pod for get them restarted with changed spec.
   376  			if err := client.CoreV1().Pods(pod.Namespace).Delete(pod.Name, metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil {
   377  				return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name)
   378  			}
   379  		}
   380  	}
   381  	return nil
   382  }