From c5ef83b29bc18e7c911a8a9b355e774df99d322a Mon Sep 17 00:00:00 2001 From: deads2k Date: Mon, 29 Jun 2015 16:27:31 -0400 Subject: [PATCH] relativize paths in kubeconfig files --- pkg/client/clientcmd/loader.go | 214 ++++++++++++++++------ pkg/kubectl/cmd/config/config.go | 23 ++- pkg/kubectl/cmd/config/config_test.go | 40 ++-- pkg/kubectl/cmd/config/create_authinfo.go | 5 +- pkg/kubectl/cmd/config/create_cluster.go | 4 +- pkg/kubectl/cmd/config/create_context.go | 2 +- pkg/kubectl/cmd/config/set.go | 2 +- pkg/kubectl/cmd/config/unset.go | 2 +- pkg/kubectl/cmd/config/use_context.go | 2 +- 9 files changed, 215 insertions(+), 79 deletions(-) diff --git a/pkg/client/clientcmd/loader.go b/pkg/client/clientcmd/loader.go index 4422b69deed..596433f6d30 100644 --- a/pkg/client/clientcmd/loader.go +++ b/pkg/client/clientcmd/loader.go @@ -23,6 +23,7 @@ import ( "os" "path" "path/filepath" + "strings" "github.com/ghodss/yaml" "github.com/imdario/mergo" @@ -120,11 +121,6 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) { if err := mergeConfigWithFile(mapConfig, file); err != nil { errlist = append(errlist, err) } - if rules.ResolvePaths() { - if err := ResolveLocalPaths(file, mapConfig); err != nil { - errlist = append(errlist, err) - } - } } // merge all of the struct values in the reverse order so that priority is given correctly @@ -133,9 +129,6 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) { for i := len(kubeConfigFiles) - 1; i >= 0; i-- { file := kubeConfigFiles[i] mergeConfigWithFile(nonMapConfig, file) - if rules.ResolvePaths() { - ResolveLocalPaths(file, nonMapConfig) - } } // since values are overwritten, but maps values are not, we can merge the non-map config on top of the map config and @@ -144,6 +137,12 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) { mergo.Merge(config, mapConfig) mergo.Merge(config, nonMapConfig) + if rules.ResolvePaths() { + if err := ResolveLocalPaths(config); err != nil { + errlist = append(errlist, err) + } + } + return config, errors.NewAggregate(errlist) } @@ -213,49 +212,6 @@ func mergeConfigWithFile(startingConfig *clientcmdapi.Config, filename string) e return nil } -// ResolveLocalPaths resolves all relative paths in the config object with respect to the parent directory of the filename -// this cannot be done directly inside of LoadFromFile because doing so there would make it impossible to load a file without -// modification of its contents. -func ResolveLocalPaths(filename string, config *clientcmdapi.Config) error { - if len(filename) == 0 { - return nil - } - - configDir, err := filepath.Abs(filepath.Dir(filename)) - if err != nil { - return fmt.Errorf("Could not determine the absolute path of config file %s: %v", filename, err) - } - - resolvedClusters := make(map[string]*clientcmdapi.Cluster) - for key, cluster := range config.Clusters { - cluster.CertificateAuthority = resolveLocalPath(configDir, cluster.CertificateAuthority) - resolvedClusters[key] = cluster - } - config.Clusters = resolvedClusters - - resolvedAuthInfos := make(map[string]*clientcmdapi.AuthInfo) - for key, authInfo := range config.AuthInfos { - authInfo.ClientCertificate = resolveLocalPath(configDir, authInfo.ClientCertificate) - authInfo.ClientKey = resolveLocalPath(configDir, authInfo.ClientKey) - resolvedAuthInfos[key] = authInfo - } - config.AuthInfos = resolvedAuthInfos - - return nil -} - -// resolveLocalPath makes the path absolute with respect to the startingDir -func resolveLocalPath(startingDir, path string) string { - if len(path) == 0 { - return path - } - if filepath.IsAbs(path) { - return path - } - - return filepath.Join(startingDir, path) -} - // LoadFromFile takes a filename and deserializes the contents into Config object func LoadFromFile(filename string) (*clientcmdapi.Config, error) { kubeconfigBytes, err := ioutil.ReadFile(filename) @@ -335,3 +291,159 @@ func Write(config clientcmdapi.Config) ([]byte, error) { func (rules ClientConfigLoadingRules) ResolvePaths() bool { return !rules.DoNotResolvePaths } + +// ResolveLocalPaths resolves all relative paths in the config object with respect to the stanza's LocationOfOrigin +// this cannot be done directly inside of LoadFromFile because doing so there would make it impossible to load a file without +// modification of its contents. +func ResolveLocalPaths(config *clientcmdapi.Config) error { + for _, cluster := range config.Clusters { + if len(cluster.LocationOfOrigin) == 0 { + continue + } + base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin)) + if err != nil { + return fmt.Errorf("Could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err) + } + + if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil { + return err + } + } + for _, authInfo := range config.AuthInfos { + if len(authInfo.LocationOfOrigin) == 0 { + continue + } + base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin)) + if err != nil { + return fmt.Errorf("Could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err) + } + + if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil { + return err + } + } + + return nil +} + +// RelativizeClusterLocalPaths first absolutizes the paths by calling ResolveLocalPaths. This assumes that any NEW path is already +// absolute, but any existing path will be resolved relative to LocationOfOrigin +func RelativizeClusterLocalPaths(cluster *clientcmdapi.Cluster) error { + if len(cluster.LocationOfOrigin) == 0 { + return fmt.Errorf("no location of origin for %s", cluster.Server) + } + base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin)) + if err != nil { + return fmt.Errorf("could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err) + } + + if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil { + return err + } + if err := RelativizePathWithNoBacksteps(GetClusterFileReferences(cluster), base); err != nil { + return err + } + + return nil +} + +// RelativizeAuthInfoLocalPaths first absolutizes the paths by calling ResolveLocalPaths. This assumes that any NEW path is already +// absolute, but any existing path will be resolved relative to LocationOfOrigin +func RelativizeAuthInfoLocalPaths(authInfo *clientcmdapi.AuthInfo) error { + if len(authInfo.LocationOfOrigin) == 0 { + return fmt.Errorf("no location of origin for %v", authInfo) + } + base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin)) + if err != nil { + return fmt.Errorf("could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err) + } + + if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil { + return err + } + if err := RelativizePathWithNoBacksteps(GetAuthInfoFileReferences(authInfo), base); err != nil { + return err + } + + return nil +} + +func RelativizeConfigPaths(config *clientcmdapi.Config, base string) error { + return RelativizePathWithNoBacksteps(GetConfigFileReferences(config), base) +} + +func ResolveConfigPaths(config *clientcmdapi.Config, base string) error { + return ResolvePaths(GetConfigFileReferences(config), base) +} + +func GetConfigFileReferences(config *clientcmdapi.Config) []*string { + refs := []*string{} + + for _, cluster := range config.Clusters { + refs = append(refs, GetClusterFileReferences(cluster)...) + } + for _, authInfo := range config.AuthInfos { + refs = append(refs, GetAuthInfoFileReferences(authInfo)...) + } + + return refs +} + +func GetClusterFileReferences(cluster *clientcmdapi.Cluster) []*string { + return []*string{&cluster.CertificateAuthority} +} + +func GetAuthInfoFileReferences(authInfo *clientcmdapi.AuthInfo) []*string { + return []*string{&authInfo.ClientCertificate, &authInfo.ClientKey} +} + +// ResolvePaths updates the given refs to be absolute paths, relative to the given base directory +func ResolvePaths(refs []*string, base string) error { + for _, ref := range refs { + // Don't resolve empty paths + if len(*ref) > 0 { + // Don't resolve absolute paths + if !filepath.IsAbs(*ref) { + *ref = filepath.Join(base, *ref) + } + } + } + return nil +} + +// RelativizePathWithNoBacksteps updates the given refs to be relative paths, relative to the given base directory as long as they do not require backsteps. +// Any path requiring a backstep is left as-is as long it is absolute. Any non-absolute path that can't be relativized produces an error +func RelativizePathWithNoBacksteps(refs []*string, base string) error { + for _, ref := range refs { + // Don't relativize empty paths + if len(*ref) > 0 { + rel, err := MakeRelative(*ref, base) + if err != nil { + return err + } + + // if we have a backstep, don't mess with the path + if strings.HasPrefix(rel, "../") { + if filepath.IsAbs(*ref) { + continue + } + + return fmt.Errorf("%v requires backsteps and is not absolute", *ref) + } + + *ref = rel + } + } + return nil +} + +func MakeRelative(path, base string) (string, error) { + if len(path) > 0 { + rel, err := filepath.Rel(base, path) + if err != nil { + return path, err + } + return rel, nil + } + return path, nil +} diff --git a/pkg/kubectl/cmd/config/config.go b/pkg/kubectl/cmd/config/config.go index fbf9d5d3e30..9876f48dade 100644 --- a/pkg/kubectl/cmd/config/config.go +++ b/pkg/kubectl/cmd/config/config.go @@ -187,8 +187,9 @@ func (o *PathOptions) GetExplicitFile() string { // uses the default destination file to write the results into. This results in multiple file reads, but it's very easy to follow. // Preferences and CurrentContext should always be set in the default destination file. Since we can't distinguish between empty and missing values // (no nil strings), we're forced have separate handling for them. In the kubeconfig cases, newConfig should have at most one difference, -// that means that this code will only write into a single file. -func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) error { +// that means that this code will only write into a single file. If you want to relativizePaths, you must provide a fully qualified path in any +// modified element. +func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, relativizePaths bool) error { startingConfig, err := configAccess.GetStartingConfig() if err != nil { return err @@ -223,7 +224,14 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) erro } configToWrite := getConfigFromFileOrDie(destinationFile) - configToWrite.Clusters[key] = cluster + t := *cluster + configToWrite.Clusters[key] = &t + configToWrite.Clusters[key].LocationOfOrigin = destinationFile + if relativizePaths { + if err := clientcmd.RelativizeClusterLocalPaths(configToWrite.Clusters[key]); err != nil { + return err + } + } if err := clientcmd.WriteToFile(*configToWrite, destinationFile); err != nil { return err @@ -257,7 +265,14 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) erro } configToWrite := getConfigFromFileOrDie(destinationFile) - configToWrite.AuthInfos[key] = authInfo + t := *authInfo + configToWrite.AuthInfos[key] = &t + configToWrite.AuthInfos[key].LocationOfOrigin = destinationFile + if relativizePaths { + if err := clientcmd.RelativizeAuthInfoLocalPaths(configToWrite.AuthInfos[key]); err != nil { + return err + } + } if err := clientcmd.WriteToFile(*configToWrite, destinationFile); err != nil { return err diff --git a/pkg/kubectl/cmd/config/config_test.go b/pkg/kubectl/cmd/config/config_test.go index 1bafbf0e4a3..5e25b3e53c6 100644 --- a/pkg/kubectl/cmd/config/config_test.go +++ b/pkg/kubectl/cmd/config/config_test.go @@ -21,6 +21,7 @@ import ( "fmt" "io/ioutil" "os" + "path" "reflect" "strings" "testing" @@ -79,10 +80,9 @@ func TestSetCurrentContext(t *testing.T) { startingConfig := newRedFederalCowHammerConfig() newContextName := "the-new-context" - newContext := clientcmdapi.NewContext() - startingConfig.Contexts[newContextName] = *newContext - expectedConfig.Contexts[newContextName] = *newContext + startingConfig.Contexts[newContextName] = clientcmdapi.NewContext() + expectedConfig.Contexts[newContextName] = clientcmdapi.NewContext() expectedConfig.CurrentContext = newContextName @@ -287,13 +287,15 @@ func TestEmbedNoKeyOrCertDisallowed(t *testing.T) { } func TestEmptyTokenAndCertAllowed(t *testing.T) { + fakeCertFile, _ := ioutil.TempFile("", "cert-file") + expectedConfig := newRedFederalCowHammerConfig() authInfo := clientcmdapi.NewAuthInfo() - authInfo.ClientCertificate = "cert-file" + authInfo.ClientCertificate = path.Base(fakeCertFile.Name()) expectedConfig.AuthInfos["another-user"] = authInfo test := configCommandTest{ - args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=cert-file", "--" + clientcmd.FlagBearerToken + "="}, + args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=" + fakeCertFile.Name(), "--" + clientcmd.FlagBearerToken + "="}, startingConfig: newRedFederalCowHammerConfig(), expectedConfig: expectedConfig, } @@ -305,10 +307,10 @@ func TestTokenAndCertAllowed(t *testing.T) { expectedConfig := newRedFederalCowHammerConfig() authInfo := clientcmdapi.NewAuthInfo() authInfo.Token = "token" - authInfo.ClientCertificate = "cert-file" + authInfo.ClientCertificate = "/cert-file" expectedConfig.AuthInfos["another-user"] = authInfo test := configCommandTest{ - args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=cert-file", "--" + clientcmd.FlagBearerToken + "=token"}, + args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=/cert-file", "--" + clientcmd.FlagBearerToken + "=token"}, startingConfig: newRedFederalCowHammerConfig(), expectedConfig: expectedConfig, } @@ -409,8 +411,8 @@ func TestCertLeavesToken(t *testing.T) { authInfoWithTokenAndCerts := clientcmdapi.NewAuthInfo() authInfoWithTokenAndCerts.Token = "token" - authInfoWithTokenAndCerts.ClientCertificate = "cert" - authInfoWithTokenAndCerts.ClientKey = "key" + authInfoWithTokenAndCerts.ClientCertificate = "/cert" + authInfoWithTokenAndCerts.ClientKey = "/key" startingConfig := newRedFederalCowHammerConfig() startingConfig.AuthInfos["another-user"] = authInfoWithToken @@ -419,7 +421,7 @@ func TestCertLeavesToken(t *testing.T) { expectedConfig.AuthInfos["another-user"] = authInfoWithTokenAndCerts test := configCommandTest{ - args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=cert", "--" + clientcmd.FlagKeyFile + "=key"}, + args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=/cert", "--" + clientcmd.FlagKeyFile + "=/key"}, startingConfig: startingConfig, expectedConfig: expectedConfig, } @@ -428,11 +430,13 @@ func TestCertLeavesToken(t *testing.T) { } func TestCAClearsInsecure(t *testing.T) { + fakeCAFile, _ := ioutil.TempFile("", "ca-file") + clusterInfoWithInsecure := clientcmdapi.NewCluster() clusterInfoWithInsecure.InsecureSkipTLSVerify = true clusterInfoWithCA := clientcmdapi.NewCluster() - clusterInfoWithCA.CertificateAuthority = "cafile" + clusterInfoWithCA.CertificateAuthority = path.Base(fakeCAFile.Name()) startingConfig := newRedFederalCowHammerConfig() startingConfig.Clusters["another-cluster"] = clusterInfoWithInsecure @@ -441,7 +445,7 @@ func TestCAClearsInsecure(t *testing.T) { expectedConfig.Clusters["another-cluster"] = clusterInfoWithCA test := configCommandTest{ - args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=cafile"}, + args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=" + fakeCAFile.Name()}, startingConfig: startingConfig, expectedConfig: expectedConfig, } @@ -454,7 +458,7 @@ func TestCAClearsCAData(t *testing.T) { clusterInfoWithCAData.CertificateAuthorityData = []byte("cadata") clusterInfoWithCA := clientcmdapi.NewCluster() - clusterInfoWithCA.CertificateAuthority = "cafile" + clusterInfoWithCA.CertificateAuthority = "/cafile" startingConfig := newRedFederalCowHammerConfig() startingConfig.Clusters["another-cluster"] = clusterInfoWithCAData @@ -463,7 +467,7 @@ func TestCAClearsCAData(t *testing.T) { expectedConfig.Clusters["another-cluster"] = clusterInfoWithCA test := configCommandTest{ - args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=cafile", "--" + clientcmd.FlagInsecure + "=false"}, + args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=/cafile", "--" + clientcmd.FlagInsecure + "=false"}, startingConfig: startingConfig, expectedConfig: expectedConfig, } @@ -547,10 +551,10 @@ func TestCAAndInsecureDisallowed(t *testing.T) { func TestMergeExistingAuth(t *testing.T) { expectedConfig := newRedFederalCowHammerConfig() authInfo := expectedConfig.AuthInfos["red-user"] - authInfo.ClientKey = "key" + authInfo.ClientKey = "/key" expectedConfig.AuthInfos["red-user"] = authInfo test := configCommandTest{ - args: []string{"set-credentials", "red-user", "--" + clientcmd.FlagKeyFile + "=key"}, + args: []string{"set-credentials", "red-user", "--" + clientcmd.FlagKeyFile + "=/key"}, startingConfig: newRedFederalCowHammerConfig(), expectedConfig: expectedConfig, } @@ -574,12 +578,12 @@ func TestAdditionalCluster(t *testing.T) { expectedConfig := newRedFederalCowHammerConfig() cluster := clientcmdapi.NewCluster() cluster.APIVersion = testapi.Version() - cluster.CertificateAuthority = "ca-location" + cluster.CertificateAuthority = "/ca-location" cluster.InsecureSkipTLSVerify = false cluster.Server = "serverlocation" expectedConfig.Clusters["different-cluster"] = cluster test := configCommandTest{ - args: []string{"set-cluster", "different-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation", "--" + clientcmd.FlagInsecure + "=false", "--" + clientcmd.FlagCAFile + "=ca-location", "--" + clientcmd.FlagAPIVersion + "=" + testapi.Version()}, + args: []string{"set-cluster", "different-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation", "--" + clientcmd.FlagInsecure + "=false", "--" + clientcmd.FlagCAFile + "=/ca-location", "--" + clientcmd.FlagAPIVersion + "=" + testapi.Version()}, startingConfig: newRedFederalCowHammerConfig(), expectedConfig: expectedConfig, } diff --git a/pkg/kubectl/cmd/config/create_authinfo.go b/pkg/kubectl/cmd/config/create_authinfo.go index e5276c29fc0..8d4811a9ff1 100644 --- a/pkg/kubectl/cmd/config/create_authinfo.go +++ b/pkg/kubectl/cmd/config/create_authinfo.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "io/ioutil" + "path/filepath" "strings" "github.com/spf13/cobra" @@ -115,7 +116,7 @@ func (o createAuthInfoOptions) run() error { authInfo := o.modifyAuthInfo(*startingStanza) config.AuthInfos[o.name] = &authInfo - if err := ModifyConfig(o.configAccess, *config); err != nil { + if err := ModifyConfig(o.configAccess, *config, true); err != nil { return err } @@ -134,6 +135,7 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut modifiedAuthInfo.ClientCertificateData, _ = ioutil.ReadFile(certPath) modifiedAuthInfo.ClientCertificate = "" } else { + certPath, _ = filepath.Abs(certPath) modifiedAuthInfo.ClientCertificate = certPath if len(modifiedAuthInfo.ClientCertificate) > 0 { modifiedAuthInfo.ClientCertificateData = nil @@ -146,6 +148,7 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut modifiedAuthInfo.ClientKeyData, _ = ioutil.ReadFile(keyPath) modifiedAuthInfo.ClientKey = "" } else { + keyPath, _ = filepath.Abs(keyPath) modifiedAuthInfo.ClientKey = keyPath if len(modifiedAuthInfo.ClientKey) > 0 { modifiedAuthInfo.ClientKeyData = nil diff --git a/pkg/kubectl/cmd/config/create_cluster.go b/pkg/kubectl/cmd/config/create_cluster.go index 2a2750e18db..063bb2c98b2 100644 --- a/pkg/kubectl/cmd/config/create_cluster.go +++ b/pkg/kubectl/cmd/config/create_cluster.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "io/ioutil" + "path/filepath" "github.com/spf13/cobra" @@ -101,7 +102,7 @@ func (o createClusterOptions) run() error { cluster := o.modifyCluster(*startingStanza) config.Clusters[o.name] = &cluster - if err := ModifyConfig(o.configAccess, *config); err != nil { + if err := ModifyConfig(o.configAccess, *config, true); err != nil { return err } @@ -133,6 +134,7 @@ func (o *createClusterOptions) modifyCluster(existingCluster clientcmdapi.Cluste modifiedCluster.InsecureSkipTLSVerify = false modifiedCluster.CertificateAuthority = "" } else { + caPath, _ = filepath.Abs(caPath) modifiedCluster.CertificateAuthority = caPath // Specifying a certificate authority file clears certificate authority data and insecure mode if caPath != "" { diff --git a/pkg/kubectl/cmd/config/create_context.go b/pkg/kubectl/cmd/config/create_context.go index d4b93dd9c25..fe7874f1200 100644 --- a/pkg/kubectl/cmd/config/create_context.go +++ b/pkg/kubectl/cmd/config/create_context.go @@ -88,7 +88,7 @@ func (o createContextOptions) run() error { context := o.modifyContext(*startingStanza) config.Contexts[o.name] = &context - if err := ModifyConfig(o.configAccess, *config); err != nil { + if err := ModifyConfig(o.configAccess, *config, true); err != nil { return err } diff --git a/pkg/kubectl/cmd/config/set.go b/pkg/kubectl/cmd/config/set.go index 8f68b9cfde8..585e87c599e 100644 --- a/pkg/kubectl/cmd/config/set.go +++ b/pkg/kubectl/cmd/config/set.go @@ -82,7 +82,7 @@ func (o setOptions) run() error { return err } - if err := ModifyConfig(o.configAccess, *config); err != nil { + if err := ModifyConfig(o.configAccess, *config, false); err != nil { return err } diff --git a/pkg/kubectl/cmd/config/unset.go b/pkg/kubectl/cmd/config/unset.go index 3e607dc1021..ef418060b62 100644 --- a/pkg/kubectl/cmd/config/unset.go +++ b/pkg/kubectl/cmd/config/unset.go @@ -75,7 +75,7 @@ func (o unsetOptions) run() error { return err } - if err := ModifyConfig(o.configAccess, *config); err != nil { + if err := ModifyConfig(o.configAccess, *config, false); err != nil { return err } diff --git a/pkg/kubectl/cmd/config/use_context.go b/pkg/kubectl/cmd/config/use_context.go index 341c48b6a9e..409f64daf13 100644 --- a/pkg/kubectl/cmd/config/use_context.go +++ b/pkg/kubectl/cmd/config/use_context.go @@ -66,7 +66,7 @@ func (o useContextOptions) run() error { config.CurrentContext = o.contextName - if err := ModifyConfig(o.configAccess, *config); err != nil { + if err := ModifyConfig(o.configAccess, *config, true); err != nil { return err }