github.com/appscode/helm@v3.0.0-alpha.1+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  )
    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  	ValueOptions
    42  
    43  	Install   bool
    44  	Devel     bool
    45  	Namespace string
    46  	Timeout   time.Duration
    47  	Wait      bool
    48  	// Values is a string containing (unparsed) YAML values.
    49  	Values       map[string]interface{}
    50  	DisableHooks bool
    51  	DryRun       bool
    52  	Force        bool
    53  	ResetValues  bool
    54  	ReuseValues  bool
    55  	// Recreate will (if true) recreate pods after a rollback.
    56  	Recreate bool
    57  	// MaxHistory limits the maximum number of revisions saved per release
    58  	MaxHistory int
    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.Values); err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	if err := validateReleaseName(name); err != nil {
    75  		return nil, errors.Errorf("upgradeRelease: Release name is invalid: %s", name)
    76  	}
    77  	u.cfg.Log("preparing upgrade for %s", name)
    78  	currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	u.cfg.Releases.MaxHistory = u.MaxHistory
    84  
    85  	if !u.DryRun {
    86  		u.cfg.Log("creating upgraded release for %s", name)
    87  		if err := u.cfg.Releases.Create(upgradedRelease); err != nil {
    88  			return nil, err
    89  		}
    90  	}
    91  
    92  	u.cfg.Log("performing update for %s", name)
    93  	res, err := u.performUpgrade(currentRelease, upgradedRelease)
    94  	if err != nil {
    95  		return res, err
    96  	}
    97  
    98  	if !u.DryRun {
    99  		u.cfg.Log("updating status for upgraded release for %s", name)
   100  		if err := u.cfg.Releases.Update(upgradedRelease); err != nil {
   101  			return res, err
   102  		}
   103  	}
   104  
   105  	return res, nil
   106  }
   107  
   108  func validateReleaseName(releaseName string) error {
   109  	if releaseName == "" {
   110  		return errMissingRelease
   111  	}
   112  
   113  	if !ValidName.MatchString(releaseName) || (len(releaseName) > releaseNameMaxLen) {
   114  		return errInvalidName
   115  	}
   116  
   117  	return nil
   118  }
   119  
   120  // prepareUpgrade builds an upgraded release for an upgrade operation.
   121  func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Release, *release.Release, error) {
   122  	if chart == nil {
   123  		return nil, nil, errMissingChart
   124  	}
   125  
   126  	// finds the deployed release with the given name
   127  	currentRelease, err := u.cfg.Releases.Deployed(name)
   128  	if err != nil {
   129  		return nil, nil, err
   130  	}
   131  
   132  	// determine if values will be reused
   133  	if err := u.reuseValues(chart, currentRelease); err != nil {
   134  		return nil, nil, err
   135  	}
   136  
   137  	// finds the non-deleted release with the given name
   138  	lastRelease, err := u.cfg.Releases.Last(name)
   139  	if err != nil {
   140  		return nil, nil, err
   141  	}
   142  
   143  	// Increment revision count. This is passed to templates, and also stored on
   144  	// the release object.
   145  	revision := lastRelease.Version + 1
   146  
   147  	options := chartutil.ReleaseOptions{
   148  		Name:      name,
   149  		Namespace: currentRelease.Namespace,
   150  		IsUpgrade: true,
   151  	}
   152  
   153  	caps, err := u.cfg.getCapabilities()
   154  	if err != nil {
   155  		return nil, nil, err
   156  	}
   157  	valuesToRender, err := chartutil.ToRenderValues(chart, u.Values, options, caps)
   158  	if err != nil {
   159  		return nil, nil, err
   160  	}
   161  
   162  	hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender)
   163  	if err != nil {
   164  		return nil, nil, err
   165  	}
   166  
   167  	// Store an upgraded release.
   168  	upgradedRelease := &release.Release{
   169  		Name:      name,
   170  		Namespace: currentRelease.Namespace,
   171  		Chart:     chart,
   172  		Config:    u.Values,
   173  		Info: &release.Info{
   174  			FirstDeployed: currentRelease.Info.FirstDeployed,
   175  			LastDeployed:  Timestamper(),
   176  			Status:        release.StatusPendingUpgrade,
   177  			Description:   "Preparing upgrade", // This should be overwritten later.
   178  		},
   179  		Version:  revision,
   180  		Manifest: manifestDoc.String(),
   181  		Hooks:    hooks,
   182  	}
   183  
   184  	if len(notesTxt) > 0 {
   185  		upgradedRelease.Info.Notes = notesTxt
   186  	}
   187  	err = validateManifest(u.cfg.KubeClient, manifestDoc.Bytes())
   188  	return currentRelease, upgradedRelease, err
   189  }
   190  
   191  func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Release) (*release.Release, error) {
   192  	if u.DryRun {
   193  		u.cfg.Log("dry run for %s", upgradedRelease.Name)
   194  		upgradedRelease.Info.Description = "Dry run complete"
   195  		return upgradedRelease, nil
   196  	}
   197  
   198  	// pre-upgrade hooks
   199  	if !u.DisableHooks {
   200  		if err := u.execHook(upgradedRelease.Hooks, hooks.PreUpgrade); err != nil {
   201  			return upgradedRelease, err
   202  		}
   203  	} else {
   204  		u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name)
   205  	}
   206  	if err := u.upgradeRelease(originalRelease, upgradedRelease); err != nil {
   207  		msg := fmt.Sprintf("Upgrade %q failed: %s", upgradedRelease.Name, err)
   208  		u.cfg.Log("warning: %s", msg)
   209  		upgradedRelease.Info.Status = release.StatusFailed
   210  		upgradedRelease.Info.Description = msg
   211  		u.cfg.recordRelease(originalRelease)
   212  		u.cfg.recordRelease(upgradedRelease)
   213  		return upgradedRelease, err
   214  	}
   215  
   216  	// post-upgrade hooks
   217  	if !u.DisableHooks {
   218  		if err := u.execHook(upgradedRelease.Hooks, hooks.PostUpgrade); err != nil {
   219  			return upgradedRelease, err
   220  		}
   221  	}
   222  
   223  	originalRelease.Info.Status = release.StatusSuperseded
   224  	u.cfg.recordRelease(originalRelease)
   225  
   226  	upgradedRelease.Info.Status = release.StatusDeployed
   227  	upgradedRelease.Info.Description = "Upgrade complete"
   228  
   229  	return upgradedRelease, nil
   230  }
   231  
   232  // upgradeRelease performs an upgrade from current to target release
   233  func (u *Upgrade) upgradeRelease(current, target *release.Release) error {
   234  	cm := bytes.NewBufferString(current.Manifest)
   235  	tm := bytes.NewBufferString(target.Manifest)
   236  	// TODO add wait
   237  	return u.cfg.KubeClient.Update(cm, tm, u.Force, u.Recreate)
   238  }
   239  
   240  // reuseValues copies values from the current release to a new release if the
   241  // new release does not have any values.
   242  //
   243  // If the request already has values, or if there are no values in the current
   244  // release, this does nothing.
   245  //
   246  // This is skipped if the u.ResetValues flag is set, in which case the
   247  // request values are not altered.
   248  func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release) error {
   249  	if u.ResetValues {
   250  		// If ResetValues is set, we comletely ignore current.Config.
   251  		u.cfg.Log("resetting values to the chart's original version")
   252  		return nil
   253  	}
   254  
   255  	// If the ReuseValues flag is set, we always copy the old values over the new config's values.
   256  	if u.ReuseValues {
   257  		u.cfg.Log("reusing the old release's values")
   258  
   259  		// We have to regenerate the old coalesced values:
   260  		oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config)
   261  		if err != nil {
   262  			return errors.Wrap(err, "failed to rebuild old values")
   263  		}
   264  
   265  		u.Values = chartutil.CoalesceTables(current.Config, u.Values)
   266  
   267  		chart.Values = oldVals
   268  
   269  		return nil
   270  	}
   271  
   272  	if len(u.Values) == 0 && len(current.Config) > 0 {
   273  		u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version)
   274  		u.Values = current.Config
   275  	}
   276  	return nil
   277  }
   278  
   279  func validateManifest(c kube.Interface, manifest []byte) error {
   280  	_, err := c.Build(bytes.NewReader(manifest))
   281  	return err
   282  }
   283  
   284  // execHook executes all of the hooks for the given hook event.
   285  func (u *Upgrade) execHook(hs []*release.Hook, hook string) error {
   286  	timeout := u.Timeout
   287  	executingHooks := []*release.Hook{}
   288  
   289  	for _, h := range hs {
   290  		for _, e := range h.Events {
   291  			if string(e) == hook {
   292  				executingHooks = append(executingHooks, h)
   293  			}
   294  		}
   295  	}
   296  
   297  	sort.Sort(hookByWeight(executingHooks))
   298  
   299  	for _, h := range executingHooks {
   300  		if err := deleteHookByPolicy(u.cfg, h, hooks.BeforeHookCreation); err != nil {
   301  			return err
   302  		}
   303  
   304  		b := bytes.NewBufferString(h.Manifest)
   305  		if err := u.cfg.KubeClient.Create(b); err != nil {
   306  			return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path)
   307  		}
   308  		b.Reset()
   309  		b.WriteString(h.Manifest)
   310  
   311  		if err := u.cfg.KubeClient.WatchUntilReady(b, timeout); err != nil {
   312  			// If a hook is failed, checkout the annotation of the hook to determine whether the hook should be deleted
   313  			// under failed condition. If so, then clear the corresponding resource object in the hook
   314  			if err := deleteHookByPolicy(u.cfg, h, hooks.HookFailed); err != nil {
   315  				return err
   316  			}
   317  			return err
   318  		}
   319  	}
   320  
   321  	// If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted
   322  	// under succeeded condition. If so, then clear the corresponding resource object in each hook
   323  	for _, h := range executingHooks {
   324  		if err := deleteHookByPolicy(u.cfg, h, hooks.HookSucceeded); err != nil {
   325  			return err
   326  		}
   327  		h.LastRun = time.Now()
   328  	}
   329  
   330  	return nil
   331  }