github.com/latiif/helm@v2.15.0+incompatible/cmd/helm/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 main
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"strings"
    23  
    24  	"github.com/spf13/cobra"
    25  
    26  	"k8s.io/helm/pkg/chartutil"
    27  	"k8s.io/helm/pkg/helm"
    28  	"k8s.io/helm/pkg/renderutil"
    29  	storageerrors "k8s.io/helm/pkg/storage/errors"
    30  )
    31  
    32  const upgradeDesc = `
    33  This command upgrades a release to a specified version of a chart and/or updates chart values.
    34  
    35  Required arguments are release and chart. The chart argument can be one of:
    36   - a chart reference('stable/mariadb'); use '--version' and '--devel' flags for versions other than latest,
    37   - a path to a chart directory,
    38   - a packaged chart,
    39   - a fully qualified URL.
    40  
    41  To customize the chart values, use any of
    42   - '--values'/'-f' to pass in a yaml file holding settings,
    43   - '--set' to provide one or more key=val pairs directly,
    44   - '--set-string' to provide key=val forcing val to be stored as a string,
    45   - '--set-file' to provide key=path to read a single large value from a file at path.
    46  
    47  To edit or append to the existing customized values, add the
    48   '--reuse-values' flag, otherwise any existing customized values are ignored.
    49  
    50  If no chart value arguments are provided on the command line, any existing customized values are carried
    51  forward. If you want to revert to just the values provided in the chart, use the '--reset-values' flag.
    52  
    53  You can specify any of the chart value flags multiple times. The priority will be given to the last
    54  (right-most) value specified. For example, if both myvalues.yaml and override.yaml contained a key
    55  called 'Test', the value set in override.yaml would take precedence:
    56  
    57  	$ helm upgrade -f myvalues.yaml -f override.yaml redis ./redis
    58  
    59  Note that the key name provided to the '--set', '--set-string' and '--set-file' flags can reference
    60  structure elements. Examples:
    61    - mybool=TRUE
    62    - livenessProbe.timeoutSeconds=10
    63    - metrics.annotations[0]=hey,metrics.annotations[1]=ho
    64  
    65  which sets the top level key mybool to true, the nested timeoutSeconds to 10, and two array values, respectively.
    66  
    67  Note that the value side of the key=val provided to '--set' and '--set-string' flags will pass through
    68  shell evaluation followed by yaml type parsing to produce the final value. This may alter inputs with
    69  special characters in unexpected ways, for example
    70  
    71  	$ helm upgrade --set pwd=3jk$o2,z=f\30.e redis ./redis
    72  
    73  results in "pwd: 3jk" and "z: f30.e". Use single quotes to avoid shell evaluation and argument delimiters,
    74  and use backslash to escape yaml special characters:
    75  
    76  	$ helm upgrade --set pwd='3jk$o2z=f\\30.e' redis ./redis
    77  
    78  which results in the expected "pwd: 3jk$o2z=f\30.e". If a single quote occurs in your value then follow
    79  your shell convention for escaping it; for example in bash:
    80  
    81  	$ helm upgrade --set pwd='3jk$o2z=f\\30with'\''quote'
    82  
    83  which results in "pwd: 3jk$o2z=f\30with'quote".
    84  `
    85  
    86  type upgradeCmd struct {
    87  	release       string
    88  	chart         string
    89  	out           io.Writer
    90  	client        helm.Interface
    91  	dryRun        bool
    92  	recreate      bool
    93  	force         bool
    94  	disableHooks  bool
    95  	valueFiles    valueFiles
    96  	values        []string
    97  	stringValues  []string
    98  	fileValues    []string
    99  	verify        bool
   100  	keyring       string
   101  	install       bool
   102  	namespace     string
   103  	version       string
   104  	timeout       int64
   105  	resetValues   bool
   106  	reuseValues   bool
   107  	wait          bool
   108  	atomic        bool
   109  	repoURL       string
   110  	username      string
   111  	password      string
   112  	devel         bool
   113  	subNotes      bool
   114  	description   string
   115  	cleanupOnFail bool
   116  
   117  	certFile string
   118  	keyFile  string
   119  	caFile   string
   120  	output   string
   121  }
   122  
   123  func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
   124  
   125  	upgrade := &upgradeCmd{
   126  		out:    out,
   127  		client: client,
   128  	}
   129  
   130  	cmd := &cobra.Command{
   131  		Use:     "upgrade [RELEASE] [CHART]",
   132  		Short:   "Upgrade a release",
   133  		Long:    upgradeDesc,
   134  		PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
   135  		RunE: func(cmd *cobra.Command, args []string) error {
   136  			if err := checkArgsLength(len(args), "release name", "chart path"); err != nil {
   137  				return err
   138  			}
   139  
   140  			if upgrade.version == "" && upgrade.devel {
   141  				debug("setting version to >0.0.0-0")
   142  				upgrade.version = ">0.0.0-0"
   143  			}
   144  
   145  			upgrade.release = args[0]
   146  			upgrade.chart = args[1]
   147  			upgrade.client = ensureHelmClient(upgrade.client)
   148  			upgrade.wait = upgrade.wait || upgrade.atomic
   149  
   150  			return upgrade.run()
   151  		},
   152  	}
   153  
   154  	f := cmd.Flags()
   155  	settings.AddFlagsTLS(f)
   156  	f.VarP(&upgrade.valueFiles, "values", "f", "Specify values in a YAML file or a URL(can specify multiple)")
   157  	f.BoolVar(&upgrade.dryRun, "dry-run", false, "Simulate an upgrade")
   158  	f.BoolVar(&upgrade.recreate, "recreate-pods", false, "Performs pods restart for the resource if applicable")
   159  	f.BoolVar(&upgrade.force, "force", false, "Force resource update through delete/recreate if needed")
   160  	f.StringArrayVar(&upgrade.values, "set", []string{}, "Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
   161  	f.StringArrayVar(&upgrade.stringValues, "set-string", []string{}, "Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
   162  	f.StringArrayVar(&upgrade.fileValues, "set-file", []string{}, "Set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
   163  	f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "Disable pre/post upgrade hooks. DEPRECATED. Use no-hooks")
   164  	f.BoolVar(&upgrade.disableHooks, "no-hooks", false, "Disable pre/post upgrade hooks")
   165  	f.BoolVar(&upgrade.verify, "verify", false, "Verify the provenance of the chart before upgrading")
   166  	f.StringVar(&upgrade.keyring, "keyring", defaultKeyring(), "Path to the keyring that contains public signing keys")
   167  	f.BoolVarP(&upgrade.install, "install", "i", false, "If a release by this name doesn't already exist, run an install")
   168  	f.StringVar(&upgrade.namespace, "namespace", "", "Namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace")
   169  	f.StringVar(&upgrade.version, "version", "", "Specify the exact chart version to use. If this is not specified, the latest version is used")
   170  	f.Int64Var(&upgrade.timeout, "timeout", 300, "Time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
   171  	f.BoolVar(&upgrade.resetValues, "reset-values", false, "When upgrading, reset the values to the ones built into the chart")
   172  	f.BoolVar(&upgrade.reuseValues, "reuse-values", false, "When upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored.")
   173  	f.BoolVar(&upgrade.wait, "wait", false, "If set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
   174  	f.BoolVar(&upgrade.atomic, "atomic", false, "If set, upgrade process rolls back changes made in case of failed upgrade, also sets --wait flag")
   175  	f.StringVar(&upgrade.repoURL, "repo", "", "Chart repository url where to locate the requested chart")
   176  	f.StringVar(&upgrade.username, "username", "", "Chart repository username where to locate the requested chart")
   177  	f.StringVar(&upgrade.password, "password", "", "Chart repository password where to locate the requested chart")
   178  	f.StringVar(&upgrade.certFile, "cert-file", "", "Identify HTTPS client using this SSL certificate file")
   179  	f.StringVar(&upgrade.keyFile, "key-file", "", "Identify HTTPS client using this SSL key file")
   180  	f.StringVar(&upgrade.caFile, "ca-file", "", "Verify certificates of HTTPS-enabled servers using this CA bundle")
   181  	f.BoolVar(&upgrade.devel, "devel", false, "Use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
   182  	f.BoolVar(&upgrade.subNotes, "render-subchart-notes", false, "Render subchart notes along with parent")
   183  	f.StringVar(&upgrade.description, "description", "", "Specify the description to use for the upgrade, rather than the default")
   184  	f.BoolVar(&upgrade.cleanupOnFail, "cleanup-on-fail", false, "Allow deletion of new resources created in this upgrade when upgrade failed")
   185  	bindOutputFlag(cmd, &upgrade.output)
   186  
   187  	f.MarkDeprecated("disable-hooks", "Use --no-hooks instead")
   188  
   189  	// set defaults from environment
   190  	settings.InitTLS(f)
   191  
   192  	return cmd
   193  }
   194  
   195  func (u *upgradeCmd) run() error {
   196  	chartPath, err := locateChartPath(u.repoURL, u.username, u.password, u.chart, u.version, u.verify, u.keyring, u.certFile, u.keyFile, u.caFile)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1))
   202  
   203  	if u.install {
   204  		// If a release does not exist, install it. If another error occurs during
   205  		// the check, ignore the error and continue with the upgrade.
   206  		//
   207  		// The returned error is a grpc.rpcError that wraps the message from the original error.
   208  		// So we're stuck doing string matching against the wrapped error, which is nested somewhere
   209  		// inside of the grpc.rpcError message.
   210  
   211  		if err == nil {
   212  			if u.namespace == "" {
   213  				u.namespace = defaultNamespace()
   214  			}
   215  			previousReleaseNamespace := releaseHistory.Releases[0].Namespace
   216  			if previousReleaseNamespace != u.namespace {
   217  				fmt.Fprintf(u.out,
   218  					"WARNING: Namespace %q doesn't match with previous. Release will be deployed to %s\n",
   219  					u.namespace, previousReleaseNamespace,
   220  				)
   221  			}
   222  		}
   223  
   224  		if err != nil && strings.Contains(err.Error(), storageerrors.ErrReleaseNotFound(u.release).Error()) {
   225  			fmt.Fprintf(u.out, "Release %q does not exist. Installing it now.\n", u.release)
   226  			ic := &installCmd{
   227  				chartPath:    chartPath,
   228  				client:       u.client,
   229  				out:          u.out,
   230  				name:         u.release,
   231  				valueFiles:   u.valueFiles,
   232  				dryRun:       u.dryRun,
   233  				verify:       u.verify,
   234  				disableHooks: u.disableHooks,
   235  				keyring:      u.keyring,
   236  				values:       u.values,
   237  				stringValues: u.stringValues,
   238  				fileValues:   u.fileValues,
   239  				namespace:    u.namespace,
   240  				timeout:      u.timeout,
   241  				wait:         u.wait,
   242  				description:  u.description,
   243  				atomic:       u.atomic,
   244  			}
   245  			return ic.run()
   246  		}
   247  	}
   248  
   249  	rawVals, err := vals(u.valueFiles, u.values, u.stringValues, u.fileValues, u.certFile, u.keyFile, u.caFile)
   250  	if err != nil {
   251  		return err
   252  	}
   253  
   254  	// Check chart requirements to make sure all dependencies are present in /charts
   255  	ch, err := chartutil.Load(chartPath)
   256  	if err == nil {
   257  		if req, err := chartutil.LoadRequirements(ch); err == nil {
   258  			if err := renderutil.CheckDependencies(ch, req); err != nil {
   259  				return err
   260  			}
   261  		} else if err != chartutil.ErrRequirementsNotFound {
   262  			return fmt.Errorf("cannot load requirements: %v", err)
   263  		}
   264  	} else {
   265  		return prettyError(err)
   266  	}
   267  
   268  	resp, err := u.client.UpdateReleaseFromChart(
   269  		u.release,
   270  		ch,
   271  		helm.UpdateValueOverrides(rawVals),
   272  		helm.UpgradeDryRun(u.dryRun),
   273  		helm.UpgradeRecreate(u.recreate),
   274  		helm.UpgradeForce(u.force),
   275  		helm.UpgradeDisableHooks(u.disableHooks),
   276  		helm.UpgradeTimeout(u.timeout),
   277  		helm.ResetValues(u.resetValues),
   278  		helm.ReuseValues(u.reuseValues),
   279  		helm.UpgradeSubNotes(u.subNotes),
   280  		helm.UpgradeWait(u.wait),
   281  		helm.UpgradeDescription(u.description),
   282  		helm.UpgradeCleanupOnFail(u.cleanupOnFail))
   283  	if err != nil {
   284  		fmt.Fprintf(u.out, "UPGRADE FAILED\nError: %v\n", prettyError(err))
   285  		if u.atomic {
   286  			fmt.Fprint(u.out, "ROLLING BACK")
   287  			rollback := &rollbackCmd{
   288  				out:           u.out,
   289  				client:        u.client,
   290  				name:          u.release,
   291  				dryRun:        u.dryRun,
   292  				recreate:      u.recreate,
   293  				force:         u.force,
   294  				timeout:       u.timeout,
   295  				wait:          u.wait,
   296  				description:   "",
   297  				revision:      releaseHistory.Releases[0].Version,
   298  				disableHooks:  u.disableHooks,
   299  				cleanupOnFail: u.cleanupOnFail,
   300  			}
   301  			if err := rollback.run(); err != nil {
   302  				return err
   303  			}
   304  		}
   305  		return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err))
   306  	}
   307  
   308  	if settings.Debug {
   309  		printRelease(u.out, resp.Release)
   310  	}
   311  
   312  	if outputFormat(u.output) == outputTable {
   313  		fmt.Fprintf(u.out, "Release %q has been upgraded.\n", u.release)
   314  	}
   315  	// Print the status like status command does
   316  	status, err := u.client.ReleaseStatus(u.release)
   317  	if err != nil {
   318  		return prettyError(err)
   319  	}
   320  
   321  	return write(u.out, &statusWriter{status}, outputFormat(u.output))
   322  }