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