Repository: roboll/helmfile
Pull request #1390 (2020-08-01 03:02:53)

Fix false-positive duplicate release with kubeContext
Helmfile has been incorrectly showing releases with the same name but in different kubeContexts as duplicates. This fixes that.

mumoshu: @jwitko Thanks for the feedback and the review ☺️ 

Patch:
diff --git a/pkg/app/app.go b/pkg/app/app.go
index 837a861..98839f5 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -827,7 +827,7 @@ func processFilteredReleases(st *state.HelmState, helm helmexec.Interface, conve
 	}
 
 	type Key struct {
-		TillerNamespace, Name string
+		TillerNamespace, Name, KubeContext string
 	}
 
 	releaseNameCounts := map[Key]int{}
@@ -840,11 +840,21 @@ func processFilteredReleases(st *state.HelmState, helm helmexec.Interface, conve
 				namespace = st.HelmDefaults.TillerNamespace
 			}
 		}
-		releaseNameCounts[Key{namespace, r.Name}]++
+		releaseNameCounts[Key{namespace, r.Name, r.KubeContext}]++
 	}
 	for name, c := range releaseNameCounts {
 		if c > 1 {
-			return false, []error{fmt.Errorf("duplicate release \"%s\" found in \"%s\": there were %d releases named \"%s\" matching specified selector", name.Name, name.TillerNamespace, c, name.Name)}
+			var msg string
+
+			if name.TillerNamespace != "" {
+				msg += fmt.Sprintf(" in namespace %q", name.TillerNamespace)
+			}
+
+			if name.KubeContext != "" {
+				msg += fmt.Sprintf(" in kubecontext %q", name.KubeContext)
+			}
+
+			return false, []error{fmt.Errorf("duplicate release %q found%s: there were %d releases named \"%s\" matching specified selector", name.Name, msg, c, name.Name)}
 		}
 	}
 


Test patch:
diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go
index 859a583..9792cac 100644
--- a/pkg/app/app_test.go
+++ b/pkg/app/app_test.go
@@ -456,11 +456,23 @@ releases:
 - name: foo
   chart: charts/foo
   labels:
-    duplicated: yes
+    duplicatedNs: yes
 - name: foo
   chart: charts/foo
   labels:
-    duplicated: yes
+    duplicatedNs: yes
+- name: grafana
+  chart: stable/grafana
+- name: foo
+  chart: charts/foo
+  kubeContext: baz
+  labels:
+    duplicatedCtx: yes
+- name: foo
+  chart: charts/foo
+  kubeContext: baz
+  labels:
+    duplicatedCtx: yes
 - name: bar
   chart: charts/foo
   tillerNamespace:  bar1
@@ -485,7 +497,8 @@ releases:
 		{label: "name!=", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[0]: in /path/to/helmfile.d/a1.yaml: Malformed label: name!=. Expected label in form k=v or k!=v"},
 		{label: "name", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[0]: in /path/to/helmfile.d/a1.yaml: Malformed label: name. Expected label in form k=v or k!=v"},
 		// See https://github.com/roboll/helmfile/issues/193
-		{label: "duplicated=yes", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[2]: in /path/to/helmfile.d/b.yaml: duplicate release \"foo\" found in \"zoo\": there were 2 releases named \"foo\" matching specified selector"},
+		{label: "duplicatedNs=yes", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[2]: in /path/to/helmfile.d/b.yaml: duplicate release \"foo\" found in namespace \"zoo\": there were 2 releases named \"foo\" matching specified selector"},
+		{label: "duplicatedCtx=yes", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[2]: in /path/to/helmfile.d/b.yaml: duplicate release \"foo\" found in namespace \"zoo\" in kubecontext \"baz\": there were 2 releases named \"foo\" matching specified selector"},
 		{label: "duplicatedOK=yes", expectedCount: 2, expectErr: false},
 	}
 
@@ -1302,7 +1315,7 @@ releases:
 		SetFilter(true),
 	)
 
-	expected := "in ./helmfile.yaml: duplicate release \"foo\" found in \"\": there were 2 releases named \"foo\" matching specified selector"
+	expected := "in ./helmfile.yaml: duplicate release \"foo\" found: there were 2 releases named \"foo\" matching specified selector"
 	if err == nil {
 		t.Errorf("error expected but not happened")
 	} else if err.Error() != expected {
@@ -1436,7 +1449,54 @@ releases:
 		SetFilter(true),
 	)
 
-	expected := "in ./helmfile.yaml: duplicate release \"foo\" found in \"foo\": there were 2 releases named \"foo\" matching specified selector"
+	expected := "in ./helmfile.yaml: duplicate release \"foo\" found in namespace \"foo\": there were 2 releases named \"foo\" matching specified selector"
+	if err == nil {
+		t.Errorf("error expected but not happened")
+	} else if err.Error() != expected {
+		t.Errorf("unexpected error message: expected=\"%s\", actual=\"%s\"", expected, err.Error())
+	}
+}
+
+func TestVisitDesiredStatesWithReleases_DuplicateReleasesInNsKubeContextHelm3(t *testing.T) {
+	files := map[string]string{
+		"/path/to/helmfile.yaml": `
+releases:
+- name: foo
+  namespace: foo
+  chart: charts/foo
+  kubeContext: foo
+- name: foo
+  namespace: foo
+  chart: charts/foo
+  kubeContext: foo
+`,
+	}
+
+	actual := []state.ReleaseSpec{}
+
+	collectReleases := func(run *Run) (bool, []error) {
+		for _, r := range run.state.Releases {
+			actual = append(actual, r)
+		}
+		return false, []error{}
+	}
+	app := appWithFs(&App{
+		OverrideHelmBinary:  DefaultHelmBinary,
+		OverrideKubeContext: "default",
+		Logger:              helmexec.NewLogger(os.Stderr, "debug"),
+		Namespace:           "",
+		Env:                 "default",
+		FileOrDir:           "helmfile.yaml",
+	}, files)
+
+	expectNoCallsToHelmVersion(app, true)
+
+	err := app.ForEachState(
+		collectReleases,
+		SetFilter(true),
+	)
+
+	expected := "in ./helmfile.yaml: duplicate release \"foo\" found in namespace \"foo\" in kubecontext \"foo\": there were 2 releases named \"foo\" matching specified selector"
 	if err == nil {
 		t.Errorf("error expected but not happened")
 	} else if err.Error() != expected {