github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/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  	"context"
    21  	"fmt"
    22  	"io"
    23  	"log"
    24  	"os"
    25  	"os/signal"
    26  	"syscall"
    27  	"time"
    28  
    29  	"github.com/pkg/errors"
    30  	"github.com/spf13/cobra"
    31  
    32  	"github.com/stefanmcshane/helm/cmd/helm/require"
    33  	"github.com/stefanmcshane/helm/pkg/action"
    34  	"github.com/stefanmcshane/helm/pkg/chart/loader"
    35  	"github.com/stefanmcshane/helm/pkg/cli/output"
    36  	"github.com/stefanmcshane/helm/pkg/cli/values"
    37  	"github.com/stefanmcshane/helm/pkg/downloader"
    38  	"github.com/stefanmcshane/helm/pkg/getter"
    39  	"github.com/stefanmcshane/helm/pkg/storage/driver"
    40  )
    41  
    42  const upgradeDesc = `
    43  This command upgrades a release to a new version of a chart.
    44  
    45  The upgrade arguments must be a release and chart. The chart
    46  argument can be either: a chart reference('example/mariadb'), a path to a chart directory,
    47  a packaged chart, or a fully qualified URL. For chart references, the latest
    48  version will be specified unless the '--version' flag is set.
    49  
    50  To override values in a chart, use either the '--values' flag and pass in a file
    51  or use the '--set' flag and pass configuration from the command line, to force string
    52  values, use '--set-string'. You can use '--set-file' to set individual
    53  values from a file when the value itself is too long for the command line
    54  or is dynamically generated. You can also use '--set-json' to set json values
    55  (scalars/objects/arrays) from the command line.
    56  
    57  You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
    58  last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
    59  contained a key called 'Test', the value set in override.yaml would take precedence:
    60  
    61      $ helm upgrade -f myvalues.yaml -f override.yaml redis ./redis
    62  
    63  You can specify the '--set' flag multiple times. The priority will be given to the
    64  last (right-most) set specified. For example, if both 'bar' and 'newbar' values are
    65  set for a key called 'foo', the 'newbar' value would take precedence:
    66  
    67      $ helm upgrade --set foo=bar --set foo=newbar redis ./redis
    68  `
    69  
    70  func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
    71  	client := action.NewUpgrade(cfg)
    72  	valueOpts := &values.Options{}
    73  	var outfmt output.Format
    74  	var createNamespace bool
    75  
    76  	cmd := &cobra.Command{
    77  		Use:   "upgrade [RELEASE] [CHART]",
    78  		Short: "upgrade a release",
    79  		Long:  upgradeDesc,
    80  		Args:  require.ExactArgs(2),
    81  		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    82  			if len(args) == 0 {
    83  				return compListReleases(toComplete, args, cfg)
    84  			}
    85  			if len(args) == 1 {
    86  				return compListCharts(toComplete, true)
    87  			}
    88  			return nil, cobra.ShellCompDirectiveNoFileComp
    89  		},
    90  		RunE: func(cmd *cobra.Command, args []string) error {
    91  			client.Namespace = settings.Namespace()
    92  
    93  			// Fixes #7002 - Support reading values from STDIN for `upgrade` command
    94  			// Must load values AFTER determining if we have to call install so that values loaded from stdin are are not read twice
    95  			if client.Install {
    96  				// If a release does not exist, install it.
    97  				histClient := action.NewHistory(cfg)
    98  				histClient.Max = 1
    99  				if _, err := histClient.Run(args[0]); err == driver.ErrReleaseNotFound {
   100  					// Only print this to stdout for table output
   101  					if outfmt == output.Table {
   102  						fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0])
   103  					}
   104  					instClient := action.NewInstall(cfg)
   105  					instClient.CreateNamespace = createNamespace
   106  					instClient.ChartPathOptions = client.ChartPathOptions
   107  					instClient.DryRun = client.DryRun
   108  					instClient.DisableHooks = client.DisableHooks
   109  					instClient.SkipCRDs = client.SkipCRDs
   110  					instClient.Timeout = client.Timeout
   111  					instClient.Wait = client.Wait
   112  					instClient.WaitForJobs = client.WaitForJobs
   113  					instClient.Devel = client.Devel
   114  					instClient.Namespace = client.Namespace
   115  					instClient.Atomic = client.Atomic
   116  					instClient.PostRenderer = client.PostRenderer
   117  					instClient.DisableOpenAPIValidation = client.DisableOpenAPIValidation
   118  					instClient.SubNotes = client.SubNotes
   119  					instClient.Description = client.Description
   120  					instClient.DependencyUpdate = client.DependencyUpdate
   121  
   122  					rel, err := runInstall(args, instClient, valueOpts, out)
   123  					if err != nil {
   124  						return err
   125  					}
   126  					return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false})
   127  				} else if err != nil {
   128  					return err
   129  				}
   130  			}
   131  
   132  			if client.Version == "" && client.Devel {
   133  				debug("setting version to >0.0.0-0")
   134  				client.Version = ">0.0.0-0"
   135  			}
   136  
   137  			chartPath, err := client.ChartPathOptions.LocateChart(args[1], settings)
   138  			if err != nil {
   139  				return err
   140  			}
   141  
   142  			p := getter.All(settings)
   143  			vals, err := valueOpts.MergeValues(p)
   144  			if err != nil {
   145  				return err
   146  			}
   147  
   148  			// Check chart dependencies to make sure all are present in /charts
   149  			ch, err := loader.Load(chartPath)
   150  			if err != nil {
   151  				return err
   152  			}
   153  			if req := ch.Metadata.Dependencies; req != nil {
   154  				if err := action.CheckDependencies(ch, req); err != nil {
   155  					err = errors.Wrap(err, "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies")
   156  					if client.DependencyUpdate {
   157  						man := &downloader.Manager{
   158  							Out:              out,
   159  							ChartPath:        chartPath,
   160  							Keyring:          client.ChartPathOptions.Keyring,
   161  							SkipUpdate:       false,
   162  							Getters:          p,
   163  							RepositoryConfig: settings.RepositoryConfig,
   164  							RepositoryCache:  settings.RepositoryCache,
   165  							Debug:            settings.Debug,
   166  						}
   167  						if err := man.Update(); err != nil {
   168  							return err
   169  						}
   170  						// Reload the chart with the updated Chart.lock file.
   171  						if ch, err = loader.Load(chartPath); err != nil {
   172  							return errors.Wrap(err, "failed reloading chart after repo update")
   173  						}
   174  					} else {
   175  						return err
   176  					}
   177  				}
   178  			}
   179  
   180  			if ch.Metadata.Deprecated {
   181  				warning("This chart is deprecated")
   182  			}
   183  
   184  			// Create context and prepare the handle of SIGTERM
   185  			ctx := context.Background()
   186  			ctx, cancel := context.WithCancel(ctx)
   187  
   188  			// Set up channel on which to send signal notifications.
   189  			// We must use a buffered channel or risk missing the signal
   190  			// if we're not ready to receive when the signal is sent.
   191  			cSignal := make(chan os.Signal, 2)
   192  			signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM)
   193  			go func() {
   194  				<-cSignal
   195  				fmt.Fprintf(out, "Release %s has been cancelled.\n", args[0])
   196  				cancel()
   197  			}()
   198  
   199  			rel, err := client.RunWithContext(ctx, args[0], ch, vals)
   200  			if err != nil {
   201  				return errors.Wrap(err, "UPGRADE FAILED")
   202  			}
   203  
   204  			if outfmt == output.Table {
   205  				fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", args[0])
   206  			}
   207  
   208  			return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false})
   209  		},
   210  	}
   211  
   212  	f := cmd.Flags()
   213  	f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present")
   214  	f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
   215  	f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
   216  	f.BoolVar(&client.DryRun, "dry-run", false, "simulate an upgrade")
   217  	f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
   218  	f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods")
   219  	f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy")
   220  	f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks")
   221  	f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the upgrade process will not validate rendered templates against the Kubernetes OpenAPI Schema")
   222  	f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed when an upgrade is performed with install flag enabled. By default, CRDs are installed if not already present, when an upgrade is performed with install flag enabled")
   223  	f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
   224  	f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart")
   225  	f.BoolVar(&client.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")
   226  	f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout")
   227  	f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout")
   228  	f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used")
   229  	f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit")
   230  	f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails")
   231  	f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
   232  	f.StringVar(&client.Description, "description", "", "add a custom description")
   233  	f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
   234  	addChartPathOptionsFlags(f, &client.ChartPathOptions)
   235  	addValueOptionsFlags(f, valueOpts)
   236  	bindOutputFlag(cmd, &outfmt)
   237  	bindPostRenderFlag(cmd, &client.PostRenderer)
   238  
   239  	err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   240  		if len(args) != 2 {
   241  			return nil, cobra.ShellCompDirectiveNoFileComp
   242  		}
   243  		return compVersionFlag(args[1], toComplete)
   244  	})
   245  
   246  	if err != nil {
   247  		log.Fatal(err)
   248  	}
   249  
   250  	return cmd
   251  }