diff --git a/cmd/helm.go b/cmd/helm.go index 4281aa51..e71d1f0a 100644 --- a/cmd/helm.go +++ b/cmd/helm.go @@ -214,7 +214,7 @@ func (d *diffCmd) template(isUpgrade bool) ([]byte, error) { if d.insecureSkipTLSVerify { flags = append(flags, "--insecure-skip-tls-verify") } - // Helm automatically enable --reuse-values when there's no --set, --set-string, --set-json, --set-values, --set-file present. + // Helm automatically reuses the user-supplied values of the last release when there's no --set, --set-string, --set-json, --set-values, --set-file present. // Let's simulate that in helm-diff. // See https://medium.com/@kcatstack/understand-helm-upgrade-flags-reset-values-reuse-values-6e58ac8f127e shouldDefaultReusingValues := len(d.values) == 0 && len(d.stringValues) == 0 && len(d.stringLiteralValues) == 0 && len(d.jsonValues) == 0 && len(d.valueFiles) == 0 && len(d.fileValues) == 0 @@ -240,12 +240,23 @@ func (d *diffCmd) template(isUpgrade bool) ([]byte, error) { } err = d.writeExistingValues(tmpfile, false) } else { - err = d.writeExistingValues(tmpfile, true) + // Only --reuse-values also reuses the values of the chart that the release was installed from. + // By default, Helm reuses just the user-supplied values, so that changes to the values.yaml + // of the chart take effect. See https://github.com/databus23/helm-diff/issues/464 + err = d.writeExistingValues(tmpfile, d.reuseValues) } if err != nil { return nil, err } - flags = append(flags, "--values", tmpfile.Name()) + existingValues, err := os.ReadFile(tmpfile.Name()) + if err != nil { + return nil, err + } + // `helm get values` prints "null" for a release without user-supplied values. + // There is nothing to reuse then. + if v := bytes.TrimSpace(existingValues); len(v) > 0 && !bytes.Equal(v, []byte("null")) { + flags = append(flags, "--values", tmpfile.Name()) + } } for _, value := range d.values { flags = append(flags, "--set", value) diff --git a/cmd/main_test.go b/cmd/main_test.go index 3b4fc58e..5d25b27b 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -32,13 +32,18 @@ func printFakeHelmVersion() { } // printFakeHelmOutput prints the output for a fake helm invocation. -// A `helm version` call prints helm version build info; any other +// A `helm version` call prints helm version build info; a `helm get values` +// call prints HELM_DIFF_FAKE_VALUES_OUTPUT if that is set; any other // invocation prints HELM_DIFF_FAKE_OUTPUT. func printFakeHelmOutput() { if len(os.Args) > 1 && os.Args[1] == "version" { printFakeHelmVersion() return } + if v, ok := os.LookupEnv("HELM_DIFF_FAKE_VALUES_OUTPUT"); ok && len(os.Args) > 2 && os.Args[1] == "get" && os.Args[2] == "values" { + fmt.Print(v) + return + } fmt.Print(os.Getenv("HELM_DIFF_FAKE_OUTPUT")) } diff --git a/cmd/upgrade_test.go b/cmd/upgrade_test.go index e7a79151..d59de7db 100644 --- a/cmd/upgrade_test.go +++ b/cmd/upgrade_test.go @@ -401,35 +401,60 @@ data: fakeHelmMode string args []string useUpgradeDryRun bool + // existingValues is what `helm get values` prints for an existing release. + // Empty means that it prints the manifest, which stands in for some values. + existingValues string // wantGetValues is the expected `helm get values` call. // Empty means that the values must not be fetched at all. wantGetValues string + // wantValues is whether the fetched values must be passed on with --values. + wantValues bool }{ { name: "existing release with --reuse-values", fakeHelmMode: "capture_args", args: []string{"--reuse-values"}, wantGetValues: "get values my-release --output yaml --all --namespace prod-apps\n", + wantValues: true, }, { name: "existing release with --reset-then-reuse-values", fakeHelmMode: "capture_args", args: []string{"--reset-then-reuse-values"}, wantGetValues: "get values my-release --output yaml --namespace prod-apps\n", + wantValues: true, }, { name: "existing release with --install --reuse-values", fakeHelmMode: "capture_args", args: []string{"--install", "--reuse-values"}, wantGetValues: "get values my-release --output yaml --all --namespace prod-apps\n", + wantValues: true, }, { - // Helm enables --reuse-values by default when there is no --set, --set-string, - // --set-literal, --set-json, --values or --set-file flag, so the values of an - // existing release are fetched even without any of the reuse flags. + // Helm reuses the user-supplied values of an existing release when there is no + // --set, --set-string, --set-literal, --set-json, --values or --set-file flag. + // Unlike with --reuse-values, the values of the chart that the release was installed + // from are not reused, so the values are fetched without --all. + // See https://github.com/databus23/helm-diff/issues/464 name: "existing release without value flags", fakeHelmMode: "capture_args", - wantGetValues: "get values my-release --output yaml --all --namespace prod-apps\n", + wantGetValues: "get values my-release --output yaml --namespace prod-apps\n", + wantValues: true, + }, + { + name: "existing release without value flags and HELM_DIFF_USE_UPGRADE_DRY_RUN", + fakeHelmMode: "capture_args", + useUpgradeDryRun: true, + wantGetValues: "get values my-release --output yaml --namespace prod-apps\n", + wantValues: true, + }, + { + // `helm get values` prints "null" for a release without user-supplied values. + name: "existing release without value flags and without user-supplied values", + fakeHelmMode: "capture_args", + existingValues: "null\n", + wantGetValues: "get values my-release --output yaml --namespace prod-apps\n", }, { name: "unreleased with --install", @@ -468,6 +493,9 @@ data: t.Run(tc.name, func(t *testing.T) { argsFile := t.TempDir() + "/args" setupFakeHelm(t, tc.fakeHelmMode, manifestYAML, argsFile, "") + if tc.existingValues != "" { + t.Setenv("HELM_DIFF_FAKE_VALUES_OUTPUT", tc.existingValues) + } renderSubcmd := "template" if tc.useUpgradeDryRun { t.Setenv("HELM_DIFF_USE_UPGRADE_DRY_RUN", "true") @@ -499,6 +527,9 @@ data: } else if !strings.Contains(argsContent, tc.wantGetValues) { t.Errorf("expected 'helm %s', got:\n%s", strings.TrimSpace(tc.wantGetValues), argsContent) } + if strings.Contains(argsContent, " --values ") != tc.wantValues { + t.Errorf("expected the existing values to be passed with --values: %t, got:\n%s", tc.wantValues, argsContent) + } }) } }