Repository: helm/helm (go)

Issue:
Add --dependency-update flag to the helm upgrade command
<!-- If you need help or think you have found a bug, please help us with your issue by entering the following information (otherwise you can delete this text): -->

Output of `helm version`:v3.0.0

Output of `kubectl version`:v1.15.0

Cloud Provider/Platform (AKS, GKE, Minikube etc.): EKS




Pull request:
Feature/upgrade dep up
<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/main/CONTRIBUTING.md
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->

**What this PR does / why we need it**:

closes #7162

Requested multiple times.

**Special notes for your reviewer**:

This somewhat duplicates https://github.com/helm/helm/pull/9399 but this does not want to fix the behaviour of the flag, just copy it from install.
I don't think it's possible to deduplicate the code in install and upgrade since it is just a single call and error handling with a lot of parameters, so even if there was a common function to call, it would just have the same arguments and similar error handling on both calling sites. If you believe it would be still worthwhile, it can be changed.

I do not know how documentation is handled, but I have changed the description of --dependency-update both in upgrade and install to reflect the actual behaviour. Is a change needed elsewhere or does it propagate? Does the new flag need to be documented somewhere or does that also propagate?

**If applicable**:
- [x] this PR contains documentation
- [x] this PR contains unit tests
- [ ] this PR has been tested for backwards compatibility


Patch:
diff --git a/cmd/helm/install.go b/cmd/helm/install.go
index fac2131c1..04419d730 100644
--- a/cmd/helm/install.go
+++ b/cmd/helm/install.go
@@ -145,7 +145,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
 	f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release")
 	f.StringVar(&client.Description, "description", "", "add a custom description")
 	f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
-	f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart")
+	f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
 	f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema")
 	f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used")
 	f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present")
diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go
index 1952b8421..3bd392d1d 100644
--- a/cmd/helm/upgrade.go
+++ b/cmd/helm/upgrade.go
@@ -30,6 +30,7 @@ import (
 	"helm.sh/helm/v3/pkg/chart/loader"
 	"helm.sh/helm/v3/pkg/cli/output"
 	"helm.sh/helm/v3/pkg/cli/values"
+	"helm.sh/helm/v3/pkg/downloader"
 	"helm.sh/helm/v3/pkg/getter"
 	"helm.sh/helm/v3/pkg/storage/driver"
 )
@@ -132,7 +133,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 				return err
 			}
 
-			vals, err := valueOpts.MergeValues(getter.All(settings))
+			p := getter.All(settings)
+			vals, err := valueOpts.MergeValues(p)
 			if err != nil {
 				return err
 			}
@@ -144,7 +146,27 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 			}
 			if req := ch.Metadata.Dependencies; req != nil {
 				if err := action.CheckDependencies(ch, req); err != nil {
-					return err
+					if client.DependencyUpdate {
+						man := &downloader.Manager{
+							Out:              out,
+							ChartPath:        chartPath,
+							Keyring:          client.ChartPathOptions.Keyring,
+							SkipUpdate:       false,
+							Getters:          p,
+							RepositoryConfig: settings.RepositoryConfig,
+							RepositoryCache:  settings.RepositoryCache,
+							Debug:            settings.Debug,
+						}
+						if err := man.Update(); err != nil {
+							return err
+						}
+						// Reload the chart with the updated Chart.lock file.
+						if ch, err = loader.Load(chartPath); err != nil {
+							return errors.Wrap(err, "failed reloading chart after repo update")
+						}
+					} else {
+						return err
+					}
 				}
 			}
 
@@ -186,6 +208,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 	f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails")
 	f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
 	f.StringVar(&client.Description, "description", "", "add a custom description")
+	f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
 	addChartPathOptionsFlags(f, &client.ChartPathOptions)
 	addValueOptionsFlags(f, valueOpts)
 	bindOutputFlag(cmd, &outfmt)
diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go
index 3b3dd3f1c..07d9cb40e 100644
--- a/pkg/action/upgrade.go
+++ b/pkg/action/upgrade.go
@@ -98,6 +98,8 @@ type Upgrade struct {
 	PostRenderer postrender.PostRenderer
 	// DisableOpenAPIValidation controls whether OpenAPI validation is enforced.
 	DisableOpenAPIValidation bool
+	// Get missing dependencies
+	DependencyUpdate bool
 }
 
 // NewUpgrade creates a new Upgrade object with the given configuration.


Test patch:
diff --git a/cmd/helm/testdata/output/upgrade-with-dependency-update.txt b/cmd/helm/testdata/output/upgrade-with-dependency-update.txt
new file mode 100644
index 000000000..0e7e5842e
--- /dev/null
+++ b/cmd/helm/testdata/output/upgrade-with-dependency-update.txt
@@ -0,0 +1,9 @@
+Release "funny-bunny" has been upgraded. Happy Helming!
+NAME: funny-bunny
+LAST DEPLOYED: Fri Sep  2 22:04:05 1977
+NAMESPACE: default
+STATUS: deployed
+REVISION: 3
+TEST SUITE: None
+NOTES:
+PARENT NOTES
diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-update/Chart.lock b/cmd/helm/testdata/testcharts/chart-with-subchart-update/Chart.lock
new file mode 100644
index 000000000..31cda6bd6
--- /dev/null
+++ b/cmd/helm/testdata/testcharts/chart-with-subchart-update/Chart.lock
@@ -0,0 +1,6 @@
+dependencies:
+- name: subchart-with-notes
+  repository: file://../chart-with-subchart-notes/charts/subchart-with-notes
+  version: 0.0.1
+digest: sha256:8ca45f73ae3f6170a09b64a967006e98e13cd91eb51e5ab0599bb87296c7df0a
+generated: "2021-05-02T15:07:22.1099921+02:00"
diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go
index e952a5933..fc2a22d7d 100644
--- a/cmd/helm/upgrade_test.go
+++ b/cmd/helm/upgrade_test.go
@@ -32,6 +32,7 @@ import (
 )
 
 func TestUpgradeCmd(t *testing.T) {
+
 	tmpChart := ensure.TempDir(t)
 	cfile := &chart.Chart{
 		Metadata: &chart.Metadata{
@@ -79,6 +80,7 @@ func TestUpgradeCmd(t *testing.T) {
 
 	missingDepsPath := "testdata/testcharts/chart-missing-deps"
 	badDepsPath := "testdata/testcharts/chart-bad-requirements"
+	presentDepsPath := "testdata/testcharts/chart-with-subchart-update"
 
 	relWithStatusMock := func(n string, v int, ch *chart.Chart, status release.Status) *release.Release {
 		return release.Mock(&release.MockReleaseOptions{Name: n, Version: v, Chart: ch, Status: status})
@@ -149,6 +151,12 @@ func TestUpgradeCmd(t *testing.T) {
 			golden:    "output/upgrade-with-bad-dependencies.txt",
 			wantError: true,
 		},
+		{
+			name:   "upgrade a release with resolving missing dependencies",
+			cmd:    fmt.Sprintf("upgrade --dependency-update funny-bunny %s", presentDepsPath),
+			golden: "output/upgrade-with-dependency-update.txt",
+			rels:   []*release.Release{relMock("funny-bunny", 2, ch2)},
+		},
 		{
 			name:      "upgrade a non-existent release",
 			cmd:       fmt.Sprintf("upgrade funny-bunny '%s'", chartPath),