Skip to content

Commit 5b3fdee

Browse files
yxxheroCopilot
andauthored
fix: support multi-file KUBECONFIG with three-way merge (#970)
* fix: support multi-file KUBECONFIG with three-way merge (#969) The three-way merge code path used a shallow copy of EnvSettings which caused ConfigFlags.KubeConfig to point to the wrong struct. When KUBECONFIG contained multiple colon-separated paths, the entire string was passed as ExplicitPath to the k8s client loader, causing os.Stat() to fail with 'no such file or directory'. Fix by creating a fresh EnvSettings and clearing KubeConfig when it contains multiple paths, allowing the default loading rules to handle multi-file KUBECONFIG via the Precedence field. Signed-off-by: yxxhero <[email protected]> * fix: remove unused envSettings global variable causing lint failure Agent-Logs-Url: https://github.com/databus23/helm-diff/sessions/6dac559a-e362-4546-a4d7-669c57b560ef Co-authored-by: yxxhero <[email protected]> --------- Signed-off-by: yxxhero <[email protected]> Co-authored-by: copilot-swe-agent[bot] <[email protected]>
1 parent 35ac1e0 commit 5b3fdee

2 files changed

Lines changed: 131 additions & 7 deletions

File tree

cmd/upgrade.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"log"
88
"os"
9+
"path/filepath"
910
"slices"
1011
"strconv"
1112
"strings"
@@ -112,8 +113,6 @@ This can be used to visualize what changes a helm upgrade will
112113
perform.
113114
`
114115

115-
var envSettings = cli.New()
116-
117116
func newChartCommand() *cobra.Command {
118117
diff := diffCmd{
119118
namespace: os.Getenv("HELM_NAMESPACE"),
@@ -297,11 +296,7 @@ func (d *diffCmd) runHelm3() error {
297296
var actionConfig *action.Configuration
298297
if d.threeWayMerge || d.takeOwnership {
299298
actionConfig = new(action.Configuration)
300-
localEnv := cli.New()
301-
*localEnv = *envSettings
302-
if d.kubeContext != "" {
303-
localEnv.KubeContext = d.kubeContext
304-
}
299+
localEnv := prepareEnvSettings(d.kubeContext)
305300
if err := actionConfig.Init(localEnv.RESTClientGetter(), localEnv.Namespace(), os.Getenv("HELM_DRIVER")); err != nil {
306301
log.Fatalf("%+v", err)
307302
}
@@ -409,3 +404,14 @@ func checkOwnership(d *diffCmd, resources kube.ResourceList, currentSpecs map[st
409404
})
410405
return newOwnedReleases, err
411406
}
407+
408+
func prepareEnvSettings(kubeContext string) *cli.EnvSettings {
409+
localEnv := cli.New()
410+
if len(filepath.SplitList(localEnv.KubeConfig)) > 1 {
411+
localEnv.KubeConfig = ""
412+
}
413+
if kubeContext != "" {
414+
localEnv.KubeContext = kubeContext
415+
}
416+
return localEnv
417+
}

cmd/upgrade_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package cmd
22

33
import (
4+
"os"
5+
"path/filepath"
46
"testing"
57
)
68

@@ -63,3 +65,119 @@ func TestIsRemoteAccessAllowed(t *testing.T) {
6365
})
6466
}
6567
}
68+
69+
func TestPrepareEnvSettings_MultiFileKubeconfig(t *testing.T) {
70+
original := os.Getenv("KUBECONFIG")
71+
defer os.Setenv("KUBECONFIG", original)
72+
73+
cases := []struct {
74+
name string
75+
kubeconfig string
76+
kubeContext string
77+
wantKubeConfig string
78+
wantKubeContext string
79+
}{
80+
{
81+
name: "single file kubeconfig is preserved",
82+
kubeconfig: "/path/to/config",
83+
kubeContext: "",
84+
wantKubeConfig: "/path/to/config",
85+
wantKubeContext: "",
86+
},
87+
{
88+
name: "multi-file kubeconfig is cleared",
89+
kubeconfig: "/path/to/file1" + string(filepath.ListSeparator) + "/path/to/file2",
90+
kubeContext: "",
91+
wantKubeConfig: "",
92+
wantKubeContext: "",
93+
},
94+
{
95+
name: "multi-file kubeconfig with three files is cleared",
96+
kubeconfig: "/a" + string(filepath.ListSeparator) + "/b" + string(filepath.ListSeparator) + "/c",
97+
kubeContext: "",
98+
wantKubeConfig: "",
99+
wantKubeContext: "",
100+
},
101+
{
102+
name: "empty kubeconfig is preserved",
103+
kubeconfig: "",
104+
kubeContext: "",
105+
wantKubeConfig: "",
106+
wantKubeContext: "",
107+
},
108+
{
109+
name: "kube-context override is applied",
110+
kubeconfig: "/path/to/config",
111+
kubeContext: "my-context",
112+
wantKubeConfig: "/path/to/config",
113+
wantKubeContext: "my-context",
114+
},
115+
{
116+
name: "multi-file kubeconfig with kube-context override",
117+
kubeconfig: "/path/to/file1" + string(filepath.ListSeparator) + "/path/to/file2",
118+
kubeContext: "my-context",
119+
wantKubeConfig: "",
120+
wantKubeContext: "my-context",
121+
},
122+
}
123+
124+
for _, tc := range cases {
125+
t.Run(tc.name, func(t *testing.T) {
126+
os.Setenv("KUBECONFIG", tc.kubeconfig)
127+
128+
env := prepareEnvSettings(tc.kubeContext)
129+
130+
if env.KubeConfig != tc.wantKubeConfig {
131+
t.Errorf("KubeConfig: got %q, want %q", env.KubeConfig, tc.wantKubeConfig)
132+
}
133+
if env.KubeContext != tc.wantKubeContext {
134+
t.Errorf("KubeContext: got %q, want %q", env.KubeContext, tc.wantKubeContext)
135+
}
136+
})
137+
}
138+
}
139+
140+
func TestPrepareEnvSettings_ConfigFlagsPointToCorrectFields(t *testing.T) {
141+
original := os.Getenv("KUBECONFIG")
142+
defer os.Setenv("KUBECONFIG", original)
143+
144+
t.Run("config flags reflect kube-context override", func(t *testing.T) {
145+
os.Setenv("KUBECONFIG", "/some/config")
146+
env := prepareEnvSettings("my-override-context")
147+
148+
if env.KubeContext != "my-override-context" {
149+
t.Errorf("env.KubeContext = %q, want %q", env.KubeContext, "my-override-context")
150+
}
151+
})
152+
153+
t.Run("multi-file kubeconfig does not set ExplicitPath", func(t *testing.T) {
154+
multiPath := "/tmp/file1" + string(filepath.ListSeparator) + "/tmp/file2"
155+
os.Setenv("KUBECONFIG", multiPath)
156+
157+
env := prepareEnvSettings("")
158+
159+
if env.KubeConfig != "" {
160+
t.Errorf("env.KubeConfig = %q, want empty string for multi-file KUBECONFIG", env.KubeConfig)
161+
}
162+
163+
getter := env.RESTClientGetter()
164+
rawConfig := getter.ToRawKubeConfigLoader()
165+
loadingRules := rawConfig.ConfigAccess()
166+
167+
if loadingRules != nil {
168+
if explicitPath := loadingRules.GetExplicitFile(); explicitPath != "" {
169+
t.Errorf("ExplicitPath = %q, want empty string for multi-file KUBECONFIG", explicitPath)
170+
}
171+
}
172+
})
173+
174+
t.Run("single file kubeconfig preserves ExplicitPath", func(t *testing.T) {
175+
os.Setenv("KUBECONFIG", "/tmp/single-config")
176+
177+
env := prepareEnvSettings("")
178+
179+
if env.KubeConfig != "/tmp/single-config" {
180+
t.Errorf("env.KubeConfig = %q, want %q", env.KubeConfig, "/tmp/single-config")
181+
}
182+
})
183+
}

0 commit comments

Comments
 (0)