Commit 553b6e67 authored by Mike Danese's avatar Mike Danese

Merge pull request #10540 from deads2k/prevent-backsteps-in-kubeconfig

Prevent backsteps in kubeconfig
parents 33278e79 c5ef83b2
...@@ -43,10 +43,10 @@ func MinifyConfig(config *Config) error { ...@@ -43,10 +43,10 @@ func MinifyConfig(config *Config) error {
return fmt.Errorf("cannot locate context %v", config.CurrentContext) return fmt.Errorf("cannot locate context %v", config.CurrentContext)
} }
newContexts := map[string]Context{} newContexts := map[string]*Context{}
newContexts[config.CurrentContext] = currContext newContexts[config.CurrentContext] = currContext
newClusters := map[string]Cluster{} newClusters := map[string]*Cluster{}
if len(currContext.Cluster) > 0 { if len(currContext.Cluster) > 0 {
if _, exists := config.Clusters[currContext.Cluster]; !exists { if _, exists := config.Clusters[currContext.Cluster]; !exists {
return fmt.Errorf("cannot locate cluster %v", currContext.Cluster) return fmt.Errorf("cannot locate cluster %v", currContext.Cluster)
...@@ -55,7 +55,7 @@ func MinifyConfig(config *Config) error { ...@@ -55,7 +55,7 @@ func MinifyConfig(config *Config) error {
newClusters[currContext.Cluster] = config.Clusters[currContext.Cluster] newClusters[currContext.Cluster] = config.Clusters[currContext.Cluster]
} }
newAuthInfos := map[string]AuthInfo{} newAuthInfos := map[string]*AuthInfo{}
if len(currContext.AuthInfo) > 0 { if len(currContext.AuthInfo) > 0 {
if _, exists := config.AuthInfos[currContext.AuthInfo]; !exists { if _, exists := config.AuthInfos[currContext.AuthInfo]; !exists {
return fmt.Errorf("cannot locate user %v", currContext.AuthInfo) return fmt.Errorf("cannot locate user %v", currContext.AuthInfo)
......
...@@ -38,13 +38,13 @@ func newMergedConfig(certFile, certContent, keyFile, keyContent, caFile, caConte ...@@ -38,13 +38,13 @@ func newMergedConfig(certFile, certContent, keyFile, keyContent, caFile, caConte
} }
return Config{ return Config{
AuthInfos: map[string]AuthInfo{ AuthInfos: map[string]*AuthInfo{
"red-user": {Token: "red-token", ClientCertificateData: []byte(certContent), ClientKeyData: []byte(keyContent)}, "red-user": {Token: "red-token", ClientCertificateData: []byte(certContent), ClientKeyData: []byte(keyContent)},
"blue-user": {Token: "blue-token", ClientCertificate: certFile, ClientKey: keyFile}}, "blue-user": {Token: "blue-token", ClientCertificate: certFile, ClientKey: keyFile}},
Clusters: map[string]Cluster{ Clusters: map[string]*Cluster{
"cow-cluster": {Server: "http://cow.org:8080", CertificateAuthorityData: []byte(caContent)}, "cow-cluster": {Server: "http://cow.org:8080", CertificateAuthorityData: []byte(caContent)},
"chicken-cluster": {Server: "http://chicken.org:8080", CertificateAuthority: caFile}}, "chicken-cluster": {Server: "http://chicken.org:8080", CertificateAuthority: caFile}},
Contexts: map[string]Context{ Contexts: map[string]*Context{
"federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster"}, "federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster"},
"shaker-context": {AuthInfo: "blue-user", Cluster: "chicken-cluster"}}, "shaker-context": {AuthInfo: "blue-user", Cluster: "chicken-cluster"}},
CurrentContext: "federal-context", CurrentContext: "federal-context",
......
...@@ -33,21 +33,21 @@ type Config struct { ...@@ -33,21 +33,21 @@ type Config struct {
// Preferences holds general information to be use for cli interactions // Preferences holds general information to be use for cli interactions
Preferences Preferences `json:"preferences"` Preferences Preferences `json:"preferences"`
// Clusters is a map of referencable names to cluster configs // Clusters is a map of referencable names to cluster configs
Clusters map[string]Cluster `json:"clusters"` Clusters map[string]*Cluster `json:"clusters"`
// AuthInfos is a map of referencable names to user configs // AuthInfos is a map of referencable names to user configs
AuthInfos map[string]AuthInfo `json:"users"` AuthInfos map[string]*AuthInfo `json:"users"`
// Contexts is a map of referencable names to context configs // Contexts is a map of referencable names to context configs
Contexts map[string]Context `json:"contexts"` Contexts map[string]*Context `json:"contexts"`
// CurrentContext is the name of the context that you would like to use by default // CurrentContext is the name of the context that you would like to use by default
CurrentContext string `json:"current-context"` CurrentContext string `json:"current-context"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"` Extensions map[string]*runtime.EmbeddedObject `json:"extensions,omitempty"`
} }
type Preferences struct { type Preferences struct {
Colors bool `json:"colors,omitempty"` Colors bool `json:"colors,omitempty"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"` Extensions map[string]*runtime.EmbeddedObject `json:"extensions,omitempty"`
} }
// Cluster contains information about how to communicate with a kubernetes cluster // Cluster contains information about how to communicate with a kubernetes cluster
...@@ -65,7 +65,7 @@ type Cluster struct { ...@@ -65,7 +65,7 @@ type Cluster struct {
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority // CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"` CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"` Extensions map[string]*runtime.EmbeddedObject `json:"extensions,omitempty"`
} }
// AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are. // AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
...@@ -87,7 +87,7 @@ type AuthInfo struct { ...@@ -87,7 +87,7 @@ type AuthInfo struct {
// Password is the password for basic authentication to the kubernetes cluster. // Password is the password for basic authentication to the kubernetes cluster.
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"` Extensions map[string]*runtime.EmbeddedObject `json:"extensions,omitempty"`
} }
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with) // Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
...@@ -101,36 +101,36 @@ type Context struct { ...@@ -101,36 +101,36 @@ type Context struct {
// Namespace is the default namespace to use on unspecified requests // Namespace is the default namespace to use on unspecified requests
Namespace string `json:"namespace,omitempty"` Namespace string `json:"namespace,omitempty"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"` Extensions map[string]*runtime.EmbeddedObject `json:"extensions,omitempty"`
} }
// NewConfig is a convenience function that returns a new Config object with non-nil maps // NewConfig is a convenience function that returns a new Config object with non-nil maps
func NewConfig() *Config { func NewConfig() *Config {
return &Config{ return &Config{
Preferences: *NewPreferences(), Preferences: *NewPreferences(),
Clusters: make(map[string]Cluster), Clusters: make(map[string]*Cluster),
AuthInfos: make(map[string]AuthInfo), AuthInfos: make(map[string]*AuthInfo),
Contexts: make(map[string]Context), Contexts: make(map[string]*Context),
Extensions: make(map[string]runtime.EmbeddedObject), Extensions: make(map[string]*runtime.EmbeddedObject),
} }
} }
// NewConfig is a convenience function that returns a new Config object with non-nil maps // NewConfig is a convenience function that returns a new Config object with non-nil maps
func NewContext() *Context { func NewContext() *Context {
return &Context{Extensions: make(map[string]runtime.EmbeddedObject)} return &Context{Extensions: make(map[string]*runtime.EmbeddedObject)}
} }
// NewConfig is a convenience function that returns a new Config object with non-nil maps // NewConfig is a convenience function that returns a new Config object with non-nil maps
func NewCluster() *Cluster { func NewCluster() *Cluster {
return &Cluster{Extensions: make(map[string]runtime.EmbeddedObject)} return &Cluster{Extensions: make(map[string]*runtime.EmbeddedObject)}
} }
// NewConfig is a convenience function that returns a new Config object with non-nil maps // NewConfig is a convenience function that returns a new Config object with non-nil maps
func NewAuthInfo() *AuthInfo { func NewAuthInfo() *AuthInfo {
return &AuthInfo{Extensions: make(map[string]runtime.EmbeddedObject)} return &AuthInfo{Extensions: make(map[string]*runtime.EmbeddedObject)}
} }
// NewConfig is a convenience function that returns a new Config object with non-nil maps // NewConfig is a convenience function that returns a new Config object with non-nil maps
func NewPreferences() *Preferences { func NewPreferences() *Preferences {
return &Preferences{Extensions: make(map[string]runtime.EmbeddedObject)} return &Preferences{Extensions: make(map[string]*runtime.EmbeddedObject)}
} }
...@@ -42,35 +42,35 @@ func ExampleEmptyConfig() { ...@@ -42,35 +42,35 @@ func ExampleEmptyConfig() {
func ExampleOfOptionsConfig() { func ExampleOfOptionsConfig() {
defaultConfig := NewConfig() defaultConfig := NewConfig()
defaultConfig.Preferences.Colors = true defaultConfig.Preferences.Colors = true
defaultConfig.Clusters["alfa"] = Cluster{ defaultConfig.Clusters["alfa"] = &Cluster{
Server: "https://alfa.org:8080", Server: "https://alfa.org:8080",
APIVersion: "v1beta2", APIVersion: "v1beta2",
InsecureSkipTLSVerify: true, InsecureSkipTLSVerify: true,
CertificateAuthority: "path/to/my/cert-ca-filename", CertificateAuthority: "path/to/my/cert-ca-filename",
} }
defaultConfig.Clusters["bravo"] = Cluster{ defaultConfig.Clusters["bravo"] = &Cluster{
Server: "https://bravo.org:8080", Server: "https://bravo.org:8080",
APIVersion: "v1beta1", APIVersion: "v1beta1",
InsecureSkipTLSVerify: false, InsecureSkipTLSVerify: false,
} }
defaultConfig.AuthInfos["white-mage-via-cert"] = AuthInfo{ defaultConfig.AuthInfos["white-mage-via-cert"] = &AuthInfo{
ClientCertificate: "path/to/my/client-cert-filename", ClientCertificate: "path/to/my/client-cert-filename",
ClientKey: "path/to/my/client-key-filename", ClientKey: "path/to/my/client-key-filename",
} }
defaultConfig.AuthInfos["red-mage-via-token"] = AuthInfo{ defaultConfig.AuthInfos["red-mage-via-token"] = &AuthInfo{
Token: "my-secret-token", Token: "my-secret-token",
} }
defaultConfig.Contexts["bravo-as-black-mage"] = Context{ defaultConfig.Contexts["bravo-as-black-mage"] = &Context{
Cluster: "bravo", Cluster: "bravo",
AuthInfo: "black-mage-via-file", AuthInfo: "black-mage-via-file",
Namespace: "yankee", Namespace: "yankee",
} }
defaultConfig.Contexts["alfa-as-black-mage"] = Context{ defaultConfig.Contexts["alfa-as-black-mage"] = &Context{
Cluster: "alfa", Cluster: "alfa",
AuthInfo: "black-mage-via-file", AuthInfo: "black-mage-via-file",
Namespace: "zulu", Namespace: "zulu",
} }
defaultConfig.Contexts["alfa-as-white-mage"] = Context{ defaultConfig.Contexts["alfa-as-white-mage"] = &Context{
Cluster: "alfa", Cluster: "alfa",
AuthInfo: "white-mage-via-cert", AuthInfo: "white-mage-via-cert",
} }
......
...@@ -57,19 +57,19 @@ func init() { ...@@ -57,19 +57,19 @@ func init() {
return err return err
} }
out.Clusters = make(map[string]api.Cluster) out.Clusters = make(map[string]*api.Cluster)
if err := s.Convert(&in.Clusters, &out.Clusters, 0); err != nil { if err := s.Convert(&in.Clusters, &out.Clusters, 0); err != nil {
return err return err
} }
out.AuthInfos = make(map[string]api.AuthInfo) out.AuthInfos = make(map[string]*api.AuthInfo)
if err := s.Convert(&in.AuthInfos, &out.AuthInfos, 0); err != nil { if err := s.Convert(&in.AuthInfos, &out.AuthInfos, 0); err != nil {
return err return err
} }
out.Contexts = make(map[string]api.Context) out.Contexts = make(map[string]*api.Context)
if err := s.Convert(&in.Contexts, &out.Contexts, 0); err != nil { if err := s.Convert(&in.Contexts, &out.Contexts, 0); err != nil {
return err return err
} }
out.Extensions = make(map[string]runtime.EmbeddedObject) out.Extensions = make(map[string]*runtime.EmbeddedObject)
if err := s.Convert(&in.Extensions, &out.Extensions, 0); err != nil { if err := s.Convert(&in.Extensions, &out.Extensions, 0); err != nil {
return err return err
} }
...@@ -99,18 +99,18 @@ func init() { ...@@ -99,18 +99,18 @@ func init() {
} }
return nil return nil
}, },
func(in *[]NamedCluster, out *map[string]api.Cluster, s conversion.Scope) error { func(in *[]NamedCluster, out *map[string]*api.Cluster, s conversion.Scope) error {
for _, curr := range *in { for _, curr := range *in {
newCluster := api.NewCluster() newCluster := api.NewCluster()
if err := s.Convert(&curr.Cluster, newCluster, 0); err != nil { if err := s.Convert(&curr.Cluster, newCluster, 0); err != nil {
return err return err
} }
(*out)[curr.Name] = *newCluster (*out)[curr.Name] = newCluster
} }
return nil return nil
}, },
func(in *map[string]api.Cluster, out *[]NamedCluster, s conversion.Scope) error { func(in *map[string]*api.Cluster, out *[]NamedCluster, s conversion.Scope) error {
allKeys := make([]string, 0, len(*in)) allKeys := make([]string, 0, len(*in))
for key := range *in { for key := range *in {
allKeys = append(allKeys, key) allKeys = append(allKeys, key)
...@@ -120,7 +120,7 @@ func init() { ...@@ -120,7 +120,7 @@ func init() {
for _, key := range allKeys { for _, key := range allKeys {
newCluster := (*in)[key] newCluster := (*in)[key]
oldCluster := &Cluster{} oldCluster := &Cluster{}
if err := s.Convert(&newCluster, oldCluster, 0); err != nil { if err := s.Convert(newCluster, oldCluster, 0); err != nil {
return err return err
} }
...@@ -130,18 +130,18 @@ func init() { ...@@ -130,18 +130,18 @@ func init() {
return nil return nil
}, },
func(in *[]NamedAuthInfo, out *map[string]api.AuthInfo, s conversion.Scope) error { func(in *[]NamedAuthInfo, out *map[string]*api.AuthInfo, s conversion.Scope) error {
for _, curr := range *in { for _, curr := range *in {
newAuthInfo := api.NewAuthInfo() newAuthInfo := api.NewAuthInfo()
if err := s.Convert(&curr.AuthInfo, newAuthInfo, 0); err != nil { if err := s.Convert(&curr.AuthInfo, newAuthInfo, 0); err != nil {
return err return err
} }
(*out)[curr.Name] = *newAuthInfo (*out)[curr.Name] = newAuthInfo
} }
return nil return nil
}, },
func(in *map[string]api.AuthInfo, out *[]NamedAuthInfo, s conversion.Scope) error { func(in *map[string]*api.AuthInfo, out *[]NamedAuthInfo, s conversion.Scope) error {
allKeys := make([]string, 0, len(*in)) allKeys := make([]string, 0, len(*in))
for key := range *in { for key := range *in {
allKeys = append(allKeys, key) allKeys = append(allKeys, key)
...@@ -151,7 +151,7 @@ func init() { ...@@ -151,7 +151,7 @@ func init() {
for _, key := range allKeys { for _, key := range allKeys {
newAuthInfo := (*in)[key] newAuthInfo := (*in)[key]
oldAuthInfo := &AuthInfo{} oldAuthInfo := &AuthInfo{}
if err := s.Convert(&newAuthInfo, oldAuthInfo, 0); err != nil { if err := s.Convert(newAuthInfo, oldAuthInfo, 0); err != nil {
return err return err
} }
...@@ -161,18 +161,18 @@ func init() { ...@@ -161,18 +161,18 @@ func init() {
return nil return nil
}, },
func(in *[]NamedContext, out *map[string]api.Context, s conversion.Scope) error { func(in *[]NamedContext, out *map[string]*api.Context, s conversion.Scope) error {
for _, curr := range *in { for _, curr := range *in {
newContext := api.NewContext() newContext := api.NewContext()
if err := s.Convert(&curr.Context, newContext, 0); err != nil { if err := s.Convert(&curr.Context, newContext, 0); err != nil {
return err return err
} }
(*out)[curr.Name] = *newContext (*out)[curr.Name] = newContext
} }
return nil return nil
}, },
func(in *map[string]api.Context, out *[]NamedContext, s conversion.Scope) error { func(in *map[string]*api.Context, out *[]NamedContext, s conversion.Scope) error {
allKeys := make([]string, 0, len(*in)) allKeys := make([]string, 0, len(*in))
for key := range *in { for key := range *in {
allKeys = append(allKeys, key) allKeys = append(allKeys, key)
...@@ -182,7 +182,7 @@ func init() { ...@@ -182,7 +182,7 @@ func init() {
for _, key := range allKeys { for _, key := range allKeys {
newContext := (*in)[key] newContext := (*in)[key]
oldContext := &Context{} oldContext := &Context{}
if err := s.Convert(&newContext, oldContext, 0); err != nil { if err := s.Convert(newContext, oldContext, 0); err != nil {
return err return err
} }
...@@ -192,18 +192,18 @@ func init() { ...@@ -192,18 +192,18 @@ func init() {
return nil return nil
}, },
func(in *[]NamedExtension, out *map[string]runtime.EmbeddedObject, s conversion.Scope) error { func(in *[]NamedExtension, out *map[string]*runtime.EmbeddedObject, s conversion.Scope) error {
for _, curr := range *in { for _, curr := range *in {
newExtension := &runtime.EmbeddedObject{} newExtension := &runtime.EmbeddedObject{}
if err := s.Convert(&curr.Extension, newExtension, 0); err != nil { if err := s.Convert(&curr.Extension, newExtension, 0); err != nil {
return err return err
} }
(*out)[curr.Name] = *newExtension (*out)[curr.Name] = newExtension
} }
return nil return nil
}, },
func(in *map[string]runtime.EmbeddedObject, out *[]NamedExtension, s conversion.Scope) error { func(in *map[string]*runtime.EmbeddedObject, out *[]NamedExtension, s conversion.Scope) error {
allKeys := make([]string, 0, len(*in)) allKeys := make([]string, 0, len(*in))
for key := range *in { for key := range *in {
allKeys = append(allKeys, key) allKeys = append(allKeys, key)
...@@ -213,7 +213,7 @@ func init() { ...@@ -213,7 +213,7 @@ func init() {
for _, key := range allKeys { for _, key := range allKeys {
newExtension := (*in)[key] newExtension := (*in)[key]
oldExtension := &runtime.RawExtension{} oldExtension := &runtime.RawExtension{}
if err := s.Convert(&newExtension, oldExtension, 0); err != nil { if err := s.Convert(newExtension, oldExtension, 0); err != nil {
return err return err
} }
......
...@@ -32,14 +32,14 @@ func createValidTestConfig() *clientcmdapi.Config { ...@@ -32,14 +32,14 @@ func createValidTestConfig() *clientcmdapi.Config {
) )
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.Clusters["clean"] = clientcmdapi.Cluster{ config.Clusters["clean"] = &clientcmdapi.Cluster{
Server: server, Server: server,
APIVersion: latest.Version, APIVersion: latest.Version,
} }
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{ config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
Token: token, Token: token,
} }
config.Contexts["clean"] = clientcmdapi.Context{ config.Contexts["clean"] = &clientcmdapi.Context{
Cluster: "clean", Cluster: "clean",
AuthInfo: "clean", AuthInfo: "clean",
} }
...@@ -87,16 +87,16 @@ func TestCertificateData(t *testing.T) { ...@@ -87,16 +87,16 @@ func TestCertificateData(t *testing.T) {
keyData := []byte("key-data") keyData := []byte("key-data")
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.Clusters["clean"] = clientcmdapi.Cluster{ config.Clusters["clean"] = &clientcmdapi.Cluster{
Server: "https://localhost:8443", Server: "https://localhost:8443",
APIVersion: latest.Version, APIVersion: latest.Version,
CertificateAuthorityData: caData, CertificateAuthorityData: caData,
} }
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{ config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
ClientCertificateData: certData, ClientCertificateData: certData,
ClientKeyData: keyData, ClientKeyData: keyData,
} }
config.Contexts["clean"] = clientcmdapi.Context{ config.Contexts["clean"] = &clientcmdapi.Context{
Cluster: "clean", Cluster: "clean",
AuthInfo: "clean", AuthInfo: "clean",
} }
...@@ -120,15 +120,15 @@ func TestBasicAuthData(t *testing.T) { ...@@ -120,15 +120,15 @@ func TestBasicAuthData(t *testing.T) {
password := "mypass" password := "mypass"
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.Clusters["clean"] = clientcmdapi.Cluster{ config.Clusters["clean"] = &clientcmdapi.Cluster{
Server: "https://localhost:8443", Server: "https://localhost:8443",
APIVersion: latest.Version, APIVersion: latest.Version,
} }
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{ config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
Username: username, Username: username,
Password: password, Password: password,
} }
config.Contexts["clean"] = clientcmdapi.Context{ config.Contexts["clean"] = &clientcmdapi.Context{
Cluster: "clean", Cluster: "clean",
AuthInfo: "clean", AuthInfo: "clean",
} }
......
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/imdario/mergo" "github.com/imdario/mergo"
...@@ -120,11 +121,6 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) { ...@@ -120,11 +121,6 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
if err := mergeConfigWithFile(mapConfig, file); err != nil { if err := mergeConfigWithFile(mapConfig, file); err != nil {
errlist = append(errlist, err) 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 // 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) { ...@@ -133,9 +129,6 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
for i := len(kubeConfigFiles) - 1; i >= 0; i-- { for i := len(kubeConfigFiles) - 1; i >= 0; i-- {
file := kubeConfigFiles[i] file := kubeConfigFiles[i]
mergeConfigWithFile(nonMapConfig, file) 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 // 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) { ...@@ -144,6 +137,12 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
mergo.Merge(config, mapConfig) mergo.Merge(config, mapConfig)
mergo.Merge(config, nonMapConfig) mergo.Merge(config, nonMapConfig)
if rules.ResolvePaths() {
if err := ResolveLocalPaths(config); err != nil {
errlist = append(errlist, err)
}
}
return config, errors.NewAggregate(errlist) return config, errors.NewAggregate(errlist)
} }
...@@ -213,49 +212,6 @@ func mergeConfigWithFile(startingConfig *clientcmdapi.Config, filename string) e ...@@ -213,49 +212,6 @@ func mergeConfigWithFile(startingConfig *clientcmdapi.Config, filename string) e
return nil 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 // LoadFromFile takes a filename and deserializes the contents into Config object
func LoadFromFile(filename string) (*clientcmdapi.Config, error) { func LoadFromFile(filename string) (*clientcmdapi.Config, error) {
kubeconfigBytes, err := ioutil.ReadFile(filename) kubeconfigBytes, err := ioutil.ReadFile(filename)
...@@ -335,3 +291,159 @@ func Write(config clientcmdapi.Config) ([]byte, error) { ...@@ -335,3 +291,159 @@ func Write(config clientcmdapi.Config) ([]byte, error) {
func (rules ClientConfigLoadingRules) ResolvePaths() bool { func (rules ClientConfigLoadingRules) ResolvePaths() bool {
return !rules.DoNotResolvePaths 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
}
...@@ -34,43 +34,43 @@ import ( ...@@ -34,43 +34,43 @@ import (
var ( var (
testConfigAlfa = clientcmdapi.Config{ testConfigAlfa = clientcmdapi.Config{
AuthInfos: map[string]clientcmdapi.AuthInfo{ AuthInfos: map[string]*clientcmdapi.AuthInfo{
"red-user": {Token: "red-token"}}, "red-user": {Token: "red-token"}},
Clusters: map[string]clientcmdapi.Cluster{ Clusters: map[string]*clientcmdapi.Cluster{
"cow-cluster": {Server: "http://cow.org:8080"}}, "cow-cluster": {Server: "http://cow.org:8080"}},
Contexts: map[string]clientcmdapi.Context{ Contexts: map[string]*clientcmdapi.Context{
"federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster", Namespace: "hammer-ns"}}, "federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster", Namespace: "hammer-ns"}},
} }
testConfigBravo = clientcmdapi.Config{ testConfigBravo = clientcmdapi.Config{
AuthInfos: map[string]clientcmdapi.AuthInfo{ AuthInfos: map[string]*clientcmdapi.AuthInfo{
"black-user": {Token: "black-token"}}, "black-user": {Token: "black-token"}},
Clusters: map[string]clientcmdapi.Cluster{ Clusters: map[string]*clientcmdapi.Cluster{
"pig-cluster": {Server: "http://pig.org:8080"}}, "pig-cluster": {Server: "http://pig.org:8080"}},
Contexts: map[string]clientcmdapi.Context{ Contexts: map[string]*clientcmdapi.Context{
"queen-anne-context": {AuthInfo: "black-user", Cluster: "pig-cluster", Namespace: "saw-ns"}}, "queen-anne-context": {AuthInfo: "black-user", Cluster: "pig-cluster", Namespace: "saw-ns"}},
} }
testConfigCharlie = clientcmdapi.Config{ testConfigCharlie = clientcmdapi.Config{
AuthInfos: map[string]clientcmdapi.AuthInfo{ AuthInfos: map[string]*clientcmdapi.AuthInfo{
"green-user": {Token: "green-token"}}, "green-user": {Token: "green-token"}},
Clusters: map[string]clientcmdapi.Cluster{ Clusters: map[string]*clientcmdapi.Cluster{
"horse-cluster": {Server: "http://horse.org:8080"}}, "horse-cluster": {Server: "http://horse.org:8080"}},
Contexts: map[string]clientcmdapi.Context{ Contexts: map[string]*clientcmdapi.Context{
"shaker-context": {AuthInfo: "green-user", Cluster: "horse-cluster", Namespace: "chisel-ns"}}, "shaker-context": {AuthInfo: "green-user", Cluster: "horse-cluster", Namespace: "chisel-ns"}},
} }
testConfigDelta = clientcmdapi.Config{ testConfigDelta = clientcmdapi.Config{
AuthInfos: map[string]clientcmdapi.AuthInfo{ AuthInfos: map[string]*clientcmdapi.AuthInfo{
"blue-user": {Token: "blue-token"}}, "blue-user": {Token: "blue-token"}},
Clusters: map[string]clientcmdapi.Cluster{ Clusters: map[string]*clientcmdapi.Cluster{
"chicken-cluster": {Server: "http://chicken.org:8080"}}, "chicken-cluster": {Server: "http://chicken.org:8080"}},
Contexts: map[string]clientcmdapi.Context{ Contexts: map[string]*clientcmdapi.Context{
"gothic-context": {AuthInfo: "blue-user", Cluster: "chicken-cluster", Namespace: "plane-ns"}}, "gothic-context": {AuthInfo: "blue-user", Cluster: "chicken-cluster", Namespace: "plane-ns"}},
} }
testConfigConflictAlfa = clientcmdapi.Config{ testConfigConflictAlfa = clientcmdapi.Config{
AuthInfos: map[string]clientcmdapi.AuthInfo{ AuthInfos: map[string]*clientcmdapi.AuthInfo{
"red-user": {Token: "a-different-red-token"}, "red-user": {Token: "a-different-red-token"},
"yellow-user": {Token: "yellow-token"}}, "yellow-user": {Token: "yellow-token"}},
Clusters: map[string]clientcmdapi.Cluster{ Clusters: map[string]*clientcmdapi.Cluster{
"cow-cluster": {Server: "http://a-different-cow.org:8080", InsecureSkipTLSVerify: true}, "cow-cluster": {Server: "http://a-different-cow.org:8080", InsecureSkipTLSVerify: true},
"donkey-cluster": {Server: "http://donkey.org:8080", InsecureSkipTLSVerify: true}}, "donkey-cluster": {Server: "http://donkey.org:8080", InsecureSkipTLSVerify: true}},
CurrentContext: "federal-context", CurrentContext: "federal-context",
...@@ -176,21 +176,21 @@ func TestConflictingCurrentContext(t *testing.T) { ...@@ -176,21 +176,21 @@ func TestConflictingCurrentContext(t *testing.T) {
func TestResolveRelativePaths(t *testing.T) { func TestResolveRelativePaths(t *testing.T) {
pathResolutionConfig1 := clientcmdapi.Config{ pathResolutionConfig1 := clientcmdapi.Config{
AuthInfos: map[string]clientcmdapi.AuthInfo{ AuthInfos: map[string]*clientcmdapi.AuthInfo{
"relative-user-1": {ClientCertificate: "relative/client/cert", ClientKey: "../relative/client/key"}, "relative-user-1": {ClientCertificate: "relative/client/cert", ClientKey: "../relative/client/key"},
"absolute-user-1": {ClientCertificate: "/absolute/client/cert", ClientKey: "/absolute/client/key"}, "absolute-user-1": {ClientCertificate: "/absolute/client/cert", ClientKey: "/absolute/client/key"},
}, },
Clusters: map[string]clientcmdapi.Cluster{ Clusters: map[string]*clientcmdapi.Cluster{
"relative-server-1": {CertificateAuthority: "../relative/ca"}, "relative-server-1": {CertificateAuthority: "../relative/ca"},
"absolute-server-1": {CertificateAuthority: "/absolute/ca"}, "absolute-server-1": {CertificateAuthority: "/absolute/ca"},
}, },
} }
pathResolutionConfig2 := clientcmdapi.Config{ pathResolutionConfig2 := clientcmdapi.Config{
AuthInfos: map[string]clientcmdapi.AuthInfo{ AuthInfos: map[string]*clientcmdapi.AuthInfo{
"relative-user-2": {ClientCertificate: "relative/client/cert2", ClientKey: "../relative/client/key2"}, "relative-user-2": {ClientCertificate: "relative/client/cert2", ClientKey: "../relative/client/key2"},
"absolute-user-2": {ClientCertificate: "/absolute/client/cert2", ClientKey: "/absolute/client/key2"}, "absolute-user-2": {ClientCertificate: "/absolute/client/cert2", ClientKey: "/absolute/client/key2"},
}, },
Clusters: map[string]clientcmdapi.Cluster{ Clusters: map[string]*clientcmdapi.Cluster{
"relative-server-2": {CertificateAuthority: "../relative/ca2"}, "relative-server-2": {CertificateAuthority: "../relative/ca2"},
"absolute-server-2": {CertificateAuthority: "/absolute/ca2"}, "absolute-server-2": {CertificateAuthority: "/absolute/ca2"},
}, },
......
...@@ -95,15 +95,15 @@ func Validate(config clientcmdapi.Config) error { ...@@ -95,15 +95,15 @@ func Validate(config clientcmdapi.Config) error {
} }
for contextName, context := range config.Contexts { for contextName, context := range config.Contexts {
validationErrors = append(validationErrors, validateContext(contextName, context, config)...) validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
} }
for authInfoName, authInfo := range config.AuthInfos { for authInfoName, authInfo := range config.AuthInfos {
validationErrors = append(validationErrors, validateAuthInfo(authInfoName, authInfo)...) validationErrors = append(validationErrors, validateAuthInfo(authInfoName, *authInfo)...)
} }
for clusterName, clusterInfo := range config.Clusters { for clusterName, clusterInfo := range config.Clusters {
validationErrors = append(validationErrors, validateClusterInfo(clusterName, clusterInfo)...) validationErrors = append(validationErrors, validateClusterInfo(clusterName, *clusterInfo)...)
} }
return newErrConfigurationInvalid(validationErrors) return newErrConfigurationInvalid(validationErrors)
...@@ -131,9 +131,9 @@ func ConfirmUsable(config clientcmdapi.Config, passedContextName string) error { ...@@ -131,9 +131,9 @@ func ConfirmUsable(config clientcmdapi.Config, passedContextName string) error {
} }
if exists { if exists {
validationErrors = append(validationErrors, validateContext(contextName, context, config)...) validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
validationErrors = append(validationErrors, validateAuthInfo(context.AuthInfo, config.AuthInfos[context.AuthInfo])...) validationErrors = append(validationErrors, validateAuthInfo(context.AuthInfo, *config.AuthInfos[context.AuthInfo])...)
validationErrors = append(validationErrors, validateClusterInfo(context.Cluster, config.Clusters[context.Cluster])...) validationErrors = append(validationErrors, validateClusterInfo(context.Cluster, *config.Clusters[context.Cluster])...)
} }
return newErrConfigurationInvalid(validationErrors) return newErrConfigurationInvalid(validationErrors)
......
...@@ -28,25 +28,25 @@ import ( ...@@ -28,25 +28,25 @@ import (
func TestConfirmUsableBadInfoButOkConfig(t *testing.T) { func TestConfirmUsableBadInfoButOkConfig(t *testing.T) {
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.Clusters["missing ca"] = clientcmdapi.Cluster{ config.Clusters["missing ca"] = &clientcmdapi.Cluster{
Server: "anything", Server: "anything",
CertificateAuthority: "missing", CertificateAuthority: "missing",
} }
config.AuthInfos["error"] = clientcmdapi.AuthInfo{ config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
Username: "anything", Username: "anything",
Token: "here", Token: "here",
} }
config.Contexts["dirty"] = clientcmdapi.Context{ config.Contexts["dirty"] = &clientcmdapi.Context{
Cluster: "missing ca", Cluster: "missing ca",
AuthInfo: "error", AuthInfo: "error",
} }
config.Clusters["clean"] = clientcmdapi.Cluster{ config.Clusters["clean"] = &clientcmdapi.Cluster{
Server: "anything", Server: "anything",
} }
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{ config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
Token: "here", Token: "here",
} }
config.Contexts["clean"] = clientcmdapi.Context{ config.Contexts["clean"] = &clientcmdapi.Context{
Cluster: "clean", Cluster: "clean",
AuthInfo: "clean", AuthInfo: "clean",
} }
...@@ -64,15 +64,15 @@ func TestConfirmUsableBadInfoButOkConfig(t *testing.T) { ...@@ -64,15 +64,15 @@ func TestConfirmUsableBadInfoButOkConfig(t *testing.T) {
} }
func TestConfirmUsableBadInfoConfig(t *testing.T) { func TestConfirmUsableBadInfoConfig(t *testing.T) {
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.Clusters["missing ca"] = clientcmdapi.Cluster{ config.Clusters["missing ca"] = &clientcmdapi.Cluster{
Server: "anything", Server: "anything",
CertificateAuthority: "missing", CertificateAuthority: "missing",
} }
config.AuthInfos["error"] = clientcmdapi.AuthInfo{ config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
Username: "anything", Username: "anything",
Token: "here", Token: "here",
} }
config.Contexts["first"] = clientcmdapi.Context{ config.Contexts["first"] = &clientcmdapi.Context{
Cluster: "missing ca", Cluster: "missing ca",
AuthInfo: "error", AuthInfo: "error",
} }
...@@ -150,7 +150,7 @@ func TestIsConfigurationInvalid(t *testing.T) { ...@@ -150,7 +150,7 @@ func TestIsConfigurationInvalid(t *testing.T) {
func TestValidateMissingReferencesConfig(t *testing.T) { func TestValidateMissingReferencesConfig(t *testing.T) {
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.CurrentContext = "anything" config.CurrentContext = "anything"
config.Contexts["anything"] = clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"} config.Contexts["anything"] = &clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"}
test := configValidationTest{ test := configValidationTest{
config: config, config: config,
expectedErrorSubstring: []string{"user \"missing\" was not found for context \"anything\"", "cluster \"missing\" was not found for context \"anything\""}, expectedErrorSubstring: []string{"user \"missing\" was not found for context \"anything\"", "cluster \"missing\" was not found for context \"anything\""},
...@@ -162,7 +162,7 @@ func TestValidateMissingReferencesConfig(t *testing.T) { ...@@ -162,7 +162,7 @@ func TestValidateMissingReferencesConfig(t *testing.T) {
func TestValidateEmptyContext(t *testing.T) { func TestValidateEmptyContext(t *testing.T) {
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.CurrentContext = "anything" config.CurrentContext = "anything"
config.Contexts["anything"] = clientcmdapi.Context{} config.Contexts["anything"] = &clientcmdapi.Context{}
test := configValidationTest{ test := configValidationTest{
config: config, config: config,
expectedErrorSubstring: []string{"user was not specified for context \"anything\"", "cluster was not specified for context \"anything\""}, expectedErrorSubstring: []string{"user was not specified for context \"anything\"", "cluster was not specified for context \"anything\""},
...@@ -174,7 +174,7 @@ func TestValidateEmptyContext(t *testing.T) { ...@@ -174,7 +174,7 @@ func TestValidateEmptyContext(t *testing.T) {
func TestValidateEmptyClusterInfo(t *testing.T) { func TestValidateEmptyClusterInfo(t *testing.T) {
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.Clusters["empty"] = clientcmdapi.Cluster{} config.Clusters["empty"] = &clientcmdapi.Cluster{}
test := configValidationTest{ test := configValidationTest{
config: config, config: config,
expectedErrorSubstring: []string{"no server found for"}, expectedErrorSubstring: []string{"no server found for"},
...@@ -185,7 +185,7 @@ func TestValidateEmptyClusterInfo(t *testing.T) { ...@@ -185,7 +185,7 @@ func TestValidateEmptyClusterInfo(t *testing.T) {
} }
func TestValidateMissingCAFileClusterInfo(t *testing.T) { func TestValidateMissingCAFileClusterInfo(t *testing.T) {
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.Clusters["missing ca"] = clientcmdapi.Cluster{ config.Clusters["missing ca"] = &clientcmdapi.Cluster{
Server: "anything", Server: "anything",
CertificateAuthority: "missing", CertificateAuthority: "missing",
} }
...@@ -199,7 +199,7 @@ func TestValidateMissingCAFileClusterInfo(t *testing.T) { ...@@ -199,7 +199,7 @@ func TestValidateMissingCAFileClusterInfo(t *testing.T) {
} }
func TestValidateCleanClusterInfo(t *testing.T) { func TestValidateCleanClusterInfo(t *testing.T) {
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.Clusters["clean"] = clientcmdapi.Cluster{ config.Clusters["clean"] = &clientcmdapi.Cluster{
Server: "anything", Server: "anything",
} }
test := configValidationTest{ test := configValidationTest{
...@@ -214,7 +214,7 @@ func TestValidateCleanWithCAClusterInfo(t *testing.T) { ...@@ -214,7 +214,7 @@ func TestValidateCleanWithCAClusterInfo(t *testing.T) {
defer os.Remove(tempFile.Name()) defer os.Remove(tempFile.Name())
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.Clusters["clean"] = clientcmdapi.Cluster{ config.Clusters["clean"] = &clientcmdapi.Cluster{
Server: "anything", Server: "anything",
CertificateAuthority: tempFile.Name(), CertificateAuthority: tempFile.Name(),
} }
...@@ -228,7 +228,7 @@ func TestValidateCleanWithCAClusterInfo(t *testing.T) { ...@@ -228,7 +228,7 @@ func TestValidateCleanWithCAClusterInfo(t *testing.T) {
func TestValidateEmptyAuthInfo(t *testing.T) { func TestValidateEmptyAuthInfo(t *testing.T) {
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.AuthInfos["error"] = clientcmdapi.AuthInfo{} config.AuthInfos["error"] = &clientcmdapi.AuthInfo{}
test := configValidationTest{ test := configValidationTest{
config: config, config: config,
} }
...@@ -238,7 +238,7 @@ func TestValidateEmptyAuthInfo(t *testing.T) { ...@@ -238,7 +238,7 @@ func TestValidateEmptyAuthInfo(t *testing.T) {
} }
func TestValidateCertFilesNotFoundAuthInfo(t *testing.T) { func TestValidateCertFilesNotFoundAuthInfo(t *testing.T) {
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.AuthInfos["error"] = clientcmdapi.AuthInfo{ config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
ClientCertificate: "missing", ClientCertificate: "missing",
ClientKey: "missing", ClientKey: "missing",
} }
...@@ -255,7 +255,7 @@ func TestValidateCertDataOverridesFiles(t *testing.T) { ...@@ -255,7 +255,7 @@ func TestValidateCertDataOverridesFiles(t *testing.T) {
defer os.Remove(tempFile.Name()) defer os.Remove(tempFile.Name())
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{ config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
ClientCertificate: tempFile.Name(), ClientCertificate: tempFile.Name(),
ClientCertificateData: []byte("certdata"), ClientCertificateData: []byte("certdata"),
ClientKey: tempFile.Name(), ClientKey: tempFile.Name(),
...@@ -274,7 +274,7 @@ func TestValidateCleanCertFilesAuthInfo(t *testing.T) { ...@@ -274,7 +274,7 @@ func TestValidateCleanCertFilesAuthInfo(t *testing.T) {
defer os.Remove(tempFile.Name()) defer os.Remove(tempFile.Name())
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{ config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
ClientCertificate: tempFile.Name(), ClientCertificate: tempFile.Name(),
ClientKey: tempFile.Name(), ClientKey: tempFile.Name(),
} }
...@@ -287,7 +287,7 @@ func TestValidateCleanCertFilesAuthInfo(t *testing.T) { ...@@ -287,7 +287,7 @@ func TestValidateCleanCertFilesAuthInfo(t *testing.T) {
} }
func TestValidateCleanTokenAuthInfo(t *testing.T) { func TestValidateCleanTokenAuthInfo(t *testing.T) {
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{ config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
Token: "any-value", Token: "any-value",
} }
test := configValidationTest{ test := configValidationTest{
...@@ -300,7 +300,7 @@ func TestValidateCleanTokenAuthInfo(t *testing.T) { ...@@ -300,7 +300,7 @@ func TestValidateCleanTokenAuthInfo(t *testing.T) {
func TestValidateMultipleMethodsAuthInfo(t *testing.T) { func TestValidateMultipleMethodsAuthInfo(t *testing.T) {
config := clientcmdapi.NewConfig() config := clientcmdapi.NewConfig()
config.AuthInfos["error"] = clientcmdapi.AuthInfo{ config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
Token: "token", Token: "token",
Username: "username", Username: "username",
} }
...@@ -319,7 +319,7 @@ type configValidationTest struct { ...@@ -319,7 +319,7 @@ type configValidationTest struct {
} }
func (c configValidationTest) testContext(contextName string, t *testing.T) { func (c configValidationTest) testContext(contextName string, t *testing.T) {
errs := validateContext(contextName, c.config.Contexts[contextName], *c.config) errs := validateContext(contextName, *c.config.Contexts[contextName], *c.config)
if len(c.expectedErrorSubstring) != 0 { if len(c.expectedErrorSubstring) != 0 {
if len(errs) == 0 { if len(errs) == 0 {
...@@ -379,7 +379,7 @@ func (c configValidationTest) testConfig(t *testing.T) { ...@@ -379,7 +379,7 @@ func (c configValidationTest) testConfig(t *testing.T) {
} }
} }
func (c configValidationTest) testCluster(clusterName string, t *testing.T) { func (c configValidationTest) testCluster(clusterName string, t *testing.T) {
errs := validateClusterInfo(clusterName, c.config.Clusters[clusterName]) errs := validateClusterInfo(clusterName, *c.config.Clusters[clusterName])
if len(c.expectedErrorSubstring) != 0 { if len(c.expectedErrorSubstring) != 0 {
if len(errs) == 0 { if len(errs) == 0 {
...@@ -399,7 +399,7 @@ func (c configValidationTest) testCluster(clusterName string, t *testing.T) { ...@@ -399,7 +399,7 @@ func (c configValidationTest) testCluster(clusterName string, t *testing.T) {
} }
func (c configValidationTest) testAuthInfo(authInfoName string, t *testing.T) { func (c configValidationTest) testAuthInfo(authInfoName string, t *testing.T) {
errs := validateAuthInfo(authInfoName, c.config.AuthInfos[authInfoName]) errs := validateAuthInfo(authInfoName, *c.config.AuthInfos[authInfoName])
if len(c.expectedErrorSubstring) != 0 { if len(c.expectedErrorSubstring) != 0 {
if len(errs) == 0 { if len(errs) == 0 {
......
...@@ -187,8 +187,9 @@ func (o *PathOptions) GetExplicitFile() string { ...@@ -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. // 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 // 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, // (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. // 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
func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) error { // modified element.
func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, relativizePaths bool) error {
startingConfig, err := configAccess.GetStartingConfig() startingConfig, err := configAccess.GetStartingConfig()
if err != nil { if err != nil {
return err return err
...@@ -223,7 +224,14 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) erro ...@@ -223,7 +224,14 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) erro
} }
configToWrite := getConfigFromFileOrDie(destinationFile) 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 { if err := clientcmd.WriteToFile(*configToWrite, destinationFile); err != nil {
return err return err
...@@ -257,7 +265,14 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) erro ...@@ -257,7 +265,14 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) erro
} }
configToWrite := getConfigFromFileOrDie(destinationFile) 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 { if err := clientcmd.WriteToFile(*configToWrite, destinationFile); err != nil {
return err return err
......
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"path/filepath"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
...@@ -108,10 +109,14 @@ func (o createAuthInfoOptions) run() error { ...@@ -108,10 +109,14 @@ func (o createAuthInfoOptions) run() error {
return err return err
} }
authInfo := o.modifyAuthInfo(config.AuthInfos[o.name]) startingStanza, exists := config.AuthInfos[o.name]
config.AuthInfos[o.name] = authInfo if !exists {
startingStanza = clientcmdapi.NewAuthInfo()
}
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 return err
} }
...@@ -130,6 +135,7 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut ...@@ -130,6 +135,7 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut
modifiedAuthInfo.ClientCertificateData, _ = ioutil.ReadFile(certPath) modifiedAuthInfo.ClientCertificateData, _ = ioutil.ReadFile(certPath)
modifiedAuthInfo.ClientCertificate = "" modifiedAuthInfo.ClientCertificate = ""
} else { } else {
certPath, _ = filepath.Abs(certPath)
modifiedAuthInfo.ClientCertificate = certPath modifiedAuthInfo.ClientCertificate = certPath
if len(modifiedAuthInfo.ClientCertificate) > 0 { if len(modifiedAuthInfo.ClientCertificate) > 0 {
modifiedAuthInfo.ClientCertificateData = nil modifiedAuthInfo.ClientCertificateData = nil
...@@ -142,6 +148,7 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut ...@@ -142,6 +148,7 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut
modifiedAuthInfo.ClientKeyData, _ = ioutil.ReadFile(keyPath) modifiedAuthInfo.ClientKeyData, _ = ioutil.ReadFile(keyPath)
modifiedAuthInfo.ClientKey = "" modifiedAuthInfo.ClientKey = ""
} else { } else {
keyPath, _ = filepath.Abs(keyPath)
modifiedAuthInfo.ClientKey = keyPath modifiedAuthInfo.ClientKey = keyPath
if len(modifiedAuthInfo.ClientKey) > 0 { if len(modifiedAuthInfo.ClientKey) > 0 {
modifiedAuthInfo.ClientKeyData = nil modifiedAuthInfo.ClientKeyData = nil
......
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"path/filepath"
"github.com/spf13/cobra" "github.com/spf13/cobra"
...@@ -94,10 +95,14 @@ func (o createClusterOptions) run() error { ...@@ -94,10 +95,14 @@ func (o createClusterOptions) run() error {
return err return err
} }
cluster := o.modifyCluster(config.Clusters[o.name]) startingStanza, exists := config.Clusters[o.name]
config.Clusters[o.name] = cluster if !exists {
startingStanza = clientcmdapi.NewCluster()
}
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 return err
} }
...@@ -129,6 +134,7 @@ func (o *createClusterOptions) modifyCluster(existingCluster clientcmdapi.Cluste ...@@ -129,6 +134,7 @@ func (o *createClusterOptions) modifyCluster(existingCluster clientcmdapi.Cluste
modifiedCluster.InsecureSkipTLSVerify = false modifiedCluster.InsecureSkipTLSVerify = false
modifiedCluster.CertificateAuthority = "" modifiedCluster.CertificateAuthority = ""
} else { } else {
caPath, _ = filepath.Abs(caPath)
modifiedCluster.CertificateAuthority = caPath modifiedCluster.CertificateAuthority = caPath
// Specifying a certificate authority file clears certificate authority data and insecure mode // Specifying a certificate authority file clears certificate authority data and insecure mode
if caPath != "" { if caPath != "" {
......
...@@ -81,10 +81,14 @@ func (o createContextOptions) run() error { ...@@ -81,10 +81,14 @@ func (o createContextOptions) run() error {
return err return err
} }
context := o.modifyContext(config.Contexts[o.name]) startingStanza, exists := config.Contexts[o.name]
config.Contexts[o.name] = context if !exists {
startingStanza = clientcmdapi.NewContext()
}
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 return err
} }
......
...@@ -50,7 +50,7 @@ func newNavigationSteps(path string) (*navigationSteps, error) { ...@@ -50,7 +50,7 @@ func newNavigationSteps(path string) (*navigationSteps, error) {
// store them as a single step. In order to do that, we need to determine what set of tokens is a legal step AFTER the name of the map key // store them as a single step. In order to do that, we need to determine what set of tokens is a legal step AFTER the name of the map key
// This set of reflective code pulls the type of the map values, uses that type to look up the set of legal tags. Those legal tags are used to // This set of reflective code pulls the type of the map values, uses that type to look up the set of legal tags. Those legal tags are used to
// walk the list of remaining parts until we find a match to a legal tag or the end of the string. That name is used to burn all the used parts. // walk the list of remaining parts until we find a match to a legal tag or the end of the string. That name is used to burn all the used parts.
mapValueType := currType.Elem() mapValueType := currType.Elem().Elem()
mapValueOptions, err := getPotentialTypeValues(mapValueType) mapValueOptions, err := getPotentialTypeValues(mapValueType)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -120,6 +120,10 @@ func findNameStep(parts []string, typeOptions util.StringSet) string { ...@@ -120,6 +120,10 @@ func findNameStep(parts []string, typeOptions util.StringSet) string {
// getPotentialTypeValues takes a type and looks up the tags used to represent its fields when serialized. // getPotentialTypeValues takes a type and looks up the tags used to represent its fields when serialized.
func getPotentialTypeValues(typeValue reflect.Type) (map[string]reflect.Type, error) { func getPotentialTypeValues(typeValue reflect.Type) (map[string]reflect.Type, error) {
if typeValue.Kind() == reflect.Ptr {
typeValue = typeValue.Elem()
}
if typeValue.Kind() != reflect.Struct { if typeValue.Kind() != reflect.Struct {
return nil, fmt.Errorf("%v is not of type struct", typeValue) return nil, fmt.Errorf("%v is not of type struct", typeValue)
} }
......
...@@ -36,7 +36,7 @@ func TestParseWithDots(t *testing.T) { ...@@ -36,7 +36,7 @@ func TestParseWithDots(t *testing.T) {
path: "clusters.my.dot.delimited.name.server", path: "clusters.my.dot.delimited.name.server",
expectedNavigationSteps: navigationSteps{ expectedNavigationSteps: navigationSteps{
steps: []navigationStep{ steps: []navigationStep{
{"clusters", reflect.TypeOf(make(map[string]clientcmdapi.Cluster))}, {"clusters", reflect.TypeOf(make(map[string]*clientcmdapi.Cluster))},
{"my.dot.delimited.name", reflect.TypeOf(clientcmdapi.Cluster{})}, {"my.dot.delimited.name", reflect.TypeOf(clientcmdapi.Cluster{})},
{"server", reflect.TypeOf("")}, {"server", reflect.TypeOf("")},
}, },
...@@ -51,7 +51,7 @@ func TestParseWithDotsEndingWithName(t *testing.T) { ...@@ -51,7 +51,7 @@ func TestParseWithDotsEndingWithName(t *testing.T) {
path: "contexts.10.12.12.12", path: "contexts.10.12.12.12",
expectedNavigationSteps: navigationSteps{ expectedNavigationSteps: navigationSteps{
steps: []navigationStep{ steps: []navigationStep{
{"contexts", reflect.TypeOf(make(map[string]clientcmdapi.Context))}, {"contexts", reflect.TypeOf(make(map[string]*clientcmdapi.Context))},
{"10.12.12.12", reflect.TypeOf(clientcmdapi.Context{})}, {"10.12.12.12", reflect.TypeOf(clientcmdapi.Context{})},
}, },
}, },
...@@ -91,5 +91,6 @@ func (test stepParserTest) run(t *testing.T) { ...@@ -91,5 +91,6 @@ func (test stepParserTest) run(t *testing.T) {
if !reflect.DeepEqual(test.expectedNavigationSteps, *actualSteps) { if !reflect.DeepEqual(test.expectedNavigationSteps, *actualSteps) {
t.Errorf("diff: %v", util.ObjectDiff(test.expectedNavigationSteps, *actualSteps)) t.Errorf("diff: %v", util.ObjectDiff(test.expectedNavigationSteps, *actualSteps))
t.Errorf("expected: %#v\n actual: %#v", test.expectedNavigationSteps, *actualSteps)
} }
} }
...@@ -82,7 +82,7 @@ func (o setOptions) run() error { ...@@ -82,7 +82,7 @@ func (o setOptions) run() error {
return err return err
} }
if err := ModifyConfig(o.configAccess, *config); err != nil { if err := ModifyConfig(o.configAccess, *config, false); err != nil {
return err return err
} }
...@@ -139,26 +139,15 @@ func modifyConfig(curr reflect.Value, steps *navigationSteps, propertyValue stri ...@@ -139,26 +139,15 @@ func modifyConfig(curr reflect.Value, steps *navigationSteps, propertyValue stri
needToSetNewMapValue := currMapValue.Kind() == reflect.Invalid needToSetNewMapValue := currMapValue.Kind() == reflect.Invalid
if needToSetNewMapValue { if needToSetNewMapValue {
currMapValue = reflect.New(mapValueType).Elem() currMapValue = reflect.New(mapValueType.Elem()).Elem().Addr()
actualCurrValue.SetMapIndex(mapKey, currMapValue) actualCurrValue.SetMapIndex(mapKey, currMapValue)
} }
// our maps do not hold pointers to structs, they hold the structs themselves. This means that MapIndex returns the struct itself err := modifyConfig(currMapValue, steps, propertyValue, unset)
// That in turn means that they have kinds of type.Struct, which is not a settable type. Because of this, we need to make new struct of that type
// copy all the data from the old value into the new value, then take the .addr of the new value to modify it in the next recursion.
// clear as mud
modifiableMapValue := reflect.New(currMapValue.Type()).Elem()
modifiableMapValue.Set(currMapValue)
if modifiableMapValue.Kind() == reflect.Struct {
modifiableMapValue = modifiableMapValue.Addr()
}
err := modifyConfig(modifiableMapValue, steps, propertyValue, unset)
if err != nil { if err != nil {
return err return err
} }
actualCurrValue.SetMapIndex(mapKey, reflect.Indirect(modifiableMapValue))
return nil return nil
case reflect.String: case reflect.String:
...@@ -213,5 +202,6 @@ func modifyConfig(curr reflect.Value, steps *navigationSteps, propertyValue stri ...@@ -213,5 +202,6 @@ func modifyConfig(curr reflect.Value, steps *navigationSteps, propertyValue stri
} }
return fmt.Errorf("Unrecognized type: %v", actualCurrValue) panic(fmt.Errorf("Unrecognized type: %v", actualCurrValue))
return nil
} }
...@@ -75,7 +75,7 @@ func (o unsetOptions) run() error { ...@@ -75,7 +75,7 @@ func (o unsetOptions) run() error {
return err return err
} }
if err := ModifyConfig(o.configAccess, *config); err != nil { if err := ModifyConfig(o.configAccess, *config, false); err != nil {
return err return err
} }
......
...@@ -66,7 +66,7 @@ func (o useContextOptions) run() error { ...@@ -66,7 +66,7 @@ func (o useContextOptions) run() error {
config.CurrentContext = o.contextName config.CurrentContext = o.contextName
if err := ModifyConfig(o.configAccess, *config); err != nil { if err := ModifyConfig(o.configAccess, *config, true); err != nil {
return err return err
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment