diff --git a/cmd/clusterctl/client/cluster/proxy.go b/cmd/clusterctl/client/cluster/proxy.go index 92ef0879e781..28107e0d3deb 100644 --- a/cmd/clusterctl/client/cluster/proxy.go +++ b/cmd/clusterctl/client/cluster/proxy.go @@ -39,16 +39,19 @@ var ( ) type proxy struct { - kubeconfig Kubeconfig - timeout time.Duration + kubeconfig Kubeconfig + timeout time.Duration + configLoadingRules *clientcmd.ClientConfigLoadingRules } var _ Proxy = &proxy{} +// CurrentNamespace returns the namespace for the specified context or the +// first valid context as determined by the default config loading rules. func (k *proxy) CurrentNamespace() (string, error) { - config, err := clientcmd.LoadFromFile(k.kubeconfig.Path) + config, err := k.configLoadingRules.Load() if err != nil { - return "", errors.Wrapf(err, "failed to load Kubeconfig file from %q", k.kubeconfig.Path) + return "", errors.Wrap(err, "failed to load Kubeconfig") } context := config.CurrentContext @@ -59,7 +62,10 @@ func (k *proxy) CurrentNamespace() (string, error) { v, ok := config.Contexts[context] if !ok { - return "", errors.Errorf("failed to get context %q from %q", context, k.kubeconfig.Path) + if k.kubeconfig.Path != "" { + return "", errors.Errorf("failed to get context %q from %q", context, k.configLoadingRules.GetExplicitFile()) + } + return "", errors.Errorf("failed to get context %q from %q", context, k.configLoadingRules.GetLoadingPrecedence()) } if v.Namespace != "" { @@ -93,10 +99,11 @@ func (k *proxy) ValidateKubernetesVersion() error { return nil } +// GetConfig returns the config for a kubernetes client. func (k *proxy) GetConfig() (*rest.Config, error) { - config, err := clientcmd.LoadFromFile(k.kubeconfig.Path) + config, err := k.configLoadingRules.Load() if err != nil { - return nil, errors.Wrapf(err, "failed to load Kubeconfig file from %q", k.kubeconfig.Path) + return nil, errors.Wrap(err, "failed to load Kubeconfig") } configOverrides := &clientcmd.ConfigOverrides{ @@ -213,14 +220,22 @@ func InjectProxyTimeout(t time.Duration) ProxyOption { } } +func InjectKubeconfigPaths(paths []string) ProxyOption { + return func(p *proxy) { + p.configLoadingRules.Precedence = paths + } +} + func newProxy(kubeconfig Kubeconfig, opts ...ProxyOption) Proxy { // If a kubeconfig file isn't provided, find one in the standard locations. - if kubeconfig.Path == "" { - kubeconfig.Path = clientcmd.NewDefaultClientConfigLoadingRules().GetDefaultFilename() + rules := clientcmd.NewDefaultClientConfigLoadingRules() + if kubeconfig.Path != "" { + rules.ExplicitPath = kubeconfig.Path } p := &proxy{ - kubeconfig: kubeconfig, - timeout: 30 * time.Second, + kubeconfig: kubeconfig, + timeout: 30 * time.Second, + configLoadingRules: rules, } for _, o := range opts { diff --git a/cmd/clusterctl/client/cluster/proxy_test.go b/cmd/clusterctl/client/cluster/proxy_test.go index 160d58f6a90c..55f0b09c0356 100644 --- a/cmd/clusterctl/client/cluster/proxy_test.go +++ b/cmd/clusterctl/client/cluster/proxy_test.go @@ -103,6 +103,69 @@ func TestProxyGetConfig(t *testing.T) { }) } +// These tests are emulating the files passed in via KUBECONFIG env var by +// injecting the file paths into the ClientConfigLoadingRules.Precedence +// chain. +func TestKUBECONFIGEnvVar(t *testing.T) { + t.Run("CurrentNamespace", func(t *testing.T) { + // KUBECONFIG can specify multiple config files. We should be able to + // get the correct namespace for the context by parsing through all + // kubeconfig files + var ( + context = "workload" + kubeconfigContents = kubeconfig("does-not-exist", "some-ns") + ) + + g := NewWithT(t) + dir, err := ioutil.TempDir("", "clusterctl") + g.Expect(err).NotTo(HaveOccurred()) + defer os.RemoveAll(dir) + configFile := filepath.Join(dir, ".test-kubeconfig.yaml") + g.Expect(ioutil.WriteFile(configFile, []byte(kubeconfigContents), 0640)).To(Succeed()) + + proxy := newProxy( + // dont't give an explicit path but rather define the file in the + // configLoadingRules precedence chain. + Kubeconfig{Path: "", Context: context}, + InjectKubeconfigPaths([]string{configFile}), + ) + actualNS, err := proxy.CurrentNamespace() + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(actualNS).To(Equal("some-ns")) + }) + + t.Run("GetConfig", func(t *testing.T) { + // KUBECONFIG can specify multiple config files. We should be able to + // get the valid cluster context by parsing all the kubeconfig files. + var ( + context = "workload" + // TODO: If we change current context to "do-not-exist", we get an + // error. See https://github.com/kubernetes/client-go/issues/797 + kubeconfigContents = kubeconfig("management", "default") + expectedHost = "https://kind-server:38790" + ) + g := NewWithT(t) + dir, err := ioutil.TempDir("", "clusterctl") + g.Expect(err).NotTo(HaveOccurred()) + defer os.RemoveAll(dir) + configFile := filepath.Join(dir, ".test-kubeconfig.yaml") + g.Expect(ioutil.WriteFile(configFile, []byte(kubeconfigContents), 0640)).To(Succeed()) + + proxy := newProxy( + // dont't give an explicit path but rather define the file in the + // configLoadingRules precedence chain. + Kubeconfig{Path: "", Context: context}, + InjectKubeconfigPaths([]string{configFile}), + ) + conf, err := proxy.GetConfig() + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(conf).ToNot(BeNil()) + // asserting on the host of the cluster associated with the + // context + g.Expect(conf.Host).To(Equal(expectedHost)) + }) +} + func TestProxyCurrentNamespace(t *testing.T) { tests := []struct { name string