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