Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions cmd/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,10 @@ func (d *diffCmd) template(isUpgrade bool) ([]byte, error) {
// Helm automatically enable --reuse-values 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 := isUpgrade && len(d.values) == 0 && len(d.stringValues) == 0 && len(d.stringLiteralValues) == 0 && len(d.jsonValues) == 0 && len(d.valueFiles) == 0 && len(d.fileValues) == 0
if (d.reuseValues || d.resetThenReuseValues || shouldDefaultReusingValues) && !d.resetValues && d.clusterAccessAllowed() {
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
// A release that is not installed yet has no values to reuse. Like `helm upgrade --install`,
// ignore --reuse-values and --reset-then-reuse-values in that case.
if isUpgrade && (d.reuseValues || d.resetThenReuseValues || shouldDefaultReusingValues) && !d.resetValues && d.clusterAccessAllowed() {
tmpfile, err := os.CreateTemp("", "existing-values")
if err != nil {
return nil, err
Expand Down
8 changes: 7 additions & 1 deletion cmd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestMain(m *testing.M) {
} else {
fmt.Print(os.Getenv("HELM_DIFF_FAKE_OUTPUT_2"))
}
case "capture_args":
case "capture_args", "unreleased":
argsFile := os.Getenv("HELM_DIFF_FAKE_ARGS_FILE")
if argsFile != "" {
f, err := os.OpenFile(argsFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
Expand All @@ -82,6 +82,12 @@ func TestMain(m *testing.M) {
_ = f.Close()
}
}
// "unreleased" captures the args like "capture_args", but every
// `helm get` fails the way helm does when the release does not exist.
if mode == "unreleased" && len(os.Args) > 1 && os.Args[1] == "get" {
fmt.Fprintln(os.Stderr, "Error: release: not found")
os.Exit(1)
}
printFakeHelmOutput()
default:
printFakeHelmOutput()
Expand Down
123 changes: 123 additions & 0 deletions cmd/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,129 @@ data:
})
}

// A release that is not installed yet has no values to reuse, so the flags that
// reuse them must not make the diff of a new install fail.
// See https://github.com/databus23/helm-diff/issues/481
func TestUpgradeCommand_Execution_ReuseValues(t *testing.T) {
manifestYAML := `---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
namespace: prod-apps
data:
key: value
`

cases := []struct {
name string
// fakeHelmMode is "capture_args" for an existing release and
// "unreleased" for a release that does not exist.
fakeHelmMode string
args []string
useUpgradeDryRun bool
// wantGetValues is the expected `helm get values` call.
// Empty means that the values must not be fetched at all.
wantGetValues string
}{
{
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",
},
{
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",
},
{
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",
},
{
// 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.
name: "existing release without value flags",
fakeHelmMode: "capture_args",
wantGetValues: "get values my-release --output yaml --all --namespace prod-apps\n",
},
{
name: "unreleased with --install",
fakeHelmMode: "unreleased",
args: []string{"--install"},
},
{
name: "unreleased with --allow-unreleased --reuse-values",
fakeHelmMode: "unreleased",
args: []string{"--allow-unreleased", "--reuse-values"},
},
{
name: "unreleased with --install --reuse-values",
fakeHelmMode: "unreleased",
args: []string{"--install", "--reuse-values"},
},
{
name: "unreleased with --allow-unreleased --reset-then-reuse-values",
fakeHelmMode: "unreleased",
args: []string{"--allow-unreleased", "--reset-then-reuse-values"},
},
{
name: "unreleased with --install --reset-then-reuse-values",
fakeHelmMode: "unreleased",
args: []string{"--install", "--reset-then-reuse-values"},
},
{
name: "unreleased with --install --reuse-values and HELM_DIFF_USE_UPGRADE_DRY_RUN",
fakeHelmMode: "unreleased",
args: []string{"--install", "--reuse-values"},
useUpgradeDryRun: true,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
argsFile := t.TempDir() + "/args"
setupFakeHelm(t, tc.fakeHelmMode, manifestYAML, argsFile, "")
renderSubcmd := "template"
if tc.useUpgradeDryRun {
t.Setenv("HELM_DIFF_USE_UPGRADE_DRY_RUN", "true")
renderSubcmd = "upgrade"
}

chartDir := t.TempDir()
cmd := newChartCommand()
cmd.SetArgs(append([]string{"my-release", chartDir, "-n", "prod-apps"}, tc.args...))

err := cmd.Execute()
if err != nil {
t.Fatalf("unexpected error executing upgrade command: %v", err)
}

data, err := os.ReadFile(argsFile)
if err != nil {
t.Fatalf("failed to read fake helm args: %v", err)
}
argsContent := string(data)

if !strings.Contains(argsContent, renderSubcmd+" my-release "+chartDir) {
t.Errorf("expected the chart to be rendered with 'helm %s', got:\n%s", renderSubcmd, argsContent)
}
if tc.wantGetValues == "" {
if strings.Contains(argsContent, "get values") {
t.Errorf("expected no 'helm get values' call, got:\n%s", argsContent)
}
} else if !strings.Contains(argsContent, tc.wantGetValues) {
t.Errorf("expected 'helm %s', got:\n%s", strings.TrimSpace(tc.wantGetValues), argsContent)
}
})
}
}

func TestThreeWayMergeModeFlag(t *testing.T) {
if f := newChartCommand().Flags().Lookup("three-way-merge-mode"); f == nil {
t.Fatal("expected flag --three-way-merge-mode to be registered")
Expand Down
Loading