Commit f40fe717 authored by k8s-merge-robot's avatar k8s-merge-robot

Merge pull request #24304 from cjcullen/cacheauth

Automatic merge from submit-queue Support persisting config from kubecfg AuthProvider plugins Plumbs through an interface to the plugin that can persist a `map[string]string` config for just that plugin. Also adds `config` to the AuthProvider serialization type, and `Login()` to the AuthProvider plugin interface. Modified the gcp AuthProvider to cache short-term access tokens in the kubecfg file. Builds on #23066 @bobbyrullo @deads2k @jlowdermilk @erictune
parents 53b3ef21 13a7d92d
...@@ -70,6 +70,9 @@ type Config struct { ...@@ -70,6 +70,9 @@ type Config struct {
// Server requires plugin-specified authentication. // Server requires plugin-specified authentication.
AuthProvider *clientcmdapi.AuthProviderConfig AuthProvider *clientcmdapi.AuthProviderConfig
// Callback to persist config for AuthProvider.
AuthConfigPersister AuthProviderConfigPersister
// TLSClientConfig contains settings to enable transport layer security // TLSClientConfig contains settings to enable transport layer security
TLSClientConfig TLSClientConfig
......
...@@ -30,9 +30,22 @@ type AuthProvider interface { ...@@ -30,9 +30,22 @@ type AuthProvider interface {
// WrapTransport allows the plugin to create a modified RoundTripper that // WrapTransport allows the plugin to create a modified RoundTripper that
// attaches authorization headers (or other info) to requests. // attaches authorization headers (or other info) to requests.
WrapTransport(http.RoundTripper) http.RoundTripper WrapTransport(http.RoundTripper) http.RoundTripper
// Login allows the plugin to initialize its configuration. It must not
// require direct user interaction.
Login() error
} }
type Factory func() (AuthProvider, error) // Factory generates an AuthProvider plugin.
// clusterAddress is the address of the current cluster.
// config is the inital configuration for this plugin.
// persister allows the plugin to save updated configuration.
type Factory func(clusterAddress string, config map[string]string, persister AuthProviderConfigPersister) (AuthProvider, error)
// AuthProviderConfigPersister allows a plugin to persist configuration info
// for just itself.
type AuthProviderConfigPersister interface {
Persist(map[string]string) error
}
// All registered auth provider plugins. // All registered auth provider plugins.
var pluginsLock sync.Mutex var pluginsLock sync.Mutex
...@@ -49,12 +62,12 @@ func RegisterAuthProviderPlugin(name string, plugin Factory) error { ...@@ -49,12 +62,12 @@ func RegisterAuthProviderPlugin(name string, plugin Factory) error {
return nil return nil
} }
func GetAuthProvider(apc *clientcmdapi.AuthProviderConfig) (AuthProvider, error) { func GetAuthProvider(clusterAddress string, apc *clientcmdapi.AuthProviderConfig, persister AuthProviderConfigPersister) (AuthProvider, error) {
pluginsLock.Lock() pluginsLock.Lock()
defer pluginsLock.Unlock() defer pluginsLock.Unlock()
p, ok := plugins[apc.Name] p, ok := plugins[apc.Name]
if !ok { if !ok {
return nil, fmt.Errorf("No Auth Provider found for name %q", apc.Name) return nil, fmt.Errorf("No Auth Provider found for name %q", apc.Name)
} }
return p() return p(clusterAddress, apc.Config, persister)
} }
...@@ -19,12 +19,14 @@ package restclient ...@@ -19,12 +19,14 @@ package restclient
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"reflect"
"strconv"
"testing" "testing"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
) )
func TestTransportConfigAuthPlugins(t *testing.T) { func TestAuthPluginWrapTransport(t *testing.T) {
if err := RegisterAuthProviderPlugin("pluginA", pluginAProvider); err != nil { if err := RegisterAuthProviderPlugin("pluginA", pluginAProvider); err != nil {
t.Errorf("Unexpected error: failed to register pluginA: %v", err) t.Errorf("Unexpected error: failed to register pluginA: %v", err)
} }
...@@ -92,6 +94,83 @@ func TestTransportConfigAuthPlugins(t *testing.T) { ...@@ -92,6 +94,83 @@ func TestTransportConfigAuthPlugins(t *testing.T) {
} }
} }
func TestAuthPluginPersist(t *testing.T) {
// register pluginA by a different name so we don't collide across tests.
if err := RegisterAuthProviderPlugin("pluginA2", pluginAProvider); err != nil {
t.Errorf("Unexpected error: failed to register pluginA: %v", err)
}
if err := RegisterAuthProviderPlugin("pluginPersist", pluginPersistProvider); err != nil {
t.Errorf("Unexpected error: failed to register pluginPersist: %v", err)
}
fooBarConfig := map[string]string{"foo": "bar"}
testCases := []struct {
plugin string
startingConfig map[string]string
expectedConfigAfterLogin map[string]string
expectedConfigAfterRoundTrip map[string]string
}{
// non-persisting plugins should work fine without modifying config.
{"pluginA2", map[string]string{}, map[string]string{}, map[string]string{}},
{"pluginA2", fooBarConfig, fooBarConfig, fooBarConfig},
// plugins that persist config should be able to persist when they want.
{
"pluginPersist",
map[string]string{},
map[string]string{
"login": "Y",
},
map[string]string{
"login": "Y",
"roundTrips": "1",
},
},
{
"pluginPersist",
map[string]string{
"login": "Y",
"roundTrips": "123",
},
map[string]string{
"login": "Y",
"roundTrips": "123",
},
map[string]string{
"login": "Y",
"roundTrips": "124",
},
},
}
for i, tc := range testCases {
cfg := &clientcmdapi.AuthProviderConfig{
Name: tc.plugin,
Config: tc.startingConfig,
}
persister := &inMemoryPersister{make(map[string]string)}
persister.Persist(tc.startingConfig)
plugin, err := GetAuthProvider("127.0.0.1", cfg, persister)
if err != nil {
t.Errorf("%d. Unexpected error: failed to get plugin %q: %v", i, tc.plugin, err)
}
if err := plugin.Login(); err != nil {
t.Errorf("%d. Unexpected error calling Login() w/ plugin %q: %v", i, tc.plugin, err)
}
// Make sure the plugin persisted what we expect after Login().
if !reflect.DeepEqual(persister.savedConfig, tc.expectedConfigAfterLogin) {
t.Errorf("%d. Unexpected persisted config after calling %s.Login(): \nGot:\n%v\nExpected:\n%v",
i, tc.plugin, persister.savedConfig, tc.expectedConfigAfterLogin)
}
if _, err := plugin.WrapTransport(&emptyTransport{}).RoundTrip(&http.Request{}); err != nil {
t.Errorf("%d. Unexpected error round-tripping w/ plugin %q: %v", i, tc.plugin, err)
}
// Make sure the plugin persisted what we expect after RoundTrip().
if !reflect.DeepEqual(persister.savedConfig, tc.expectedConfigAfterRoundTrip) {
t.Errorf("%d. Unexpected persisted config after calling %s.WrapTransport.RoundTrip(): \nGot:\n%v\nExpected:\n%v",
i, tc.plugin, persister.savedConfig, tc.expectedConfigAfterLogin)
}
}
}
// emptyTransport provides an empty http.Response with an initialized header // emptyTransport provides an empty http.Response with an initialized header
// to allow wrapping RoundTrippers to set header values. // to allow wrapping RoundTrippers to set header values.
type emptyTransport struct{} type emptyTransport struct{}
...@@ -137,7 +216,9 @@ func (*pluginA) WrapTransport(rt http.RoundTripper) http.RoundTripper { ...@@ -137,7 +216,9 @@ func (*pluginA) WrapTransport(rt http.RoundTripper) http.RoundTripper {
return &wrapTransportA{rt} return &wrapTransportA{rt}
} }
func pluginAProvider() (AuthProvider, error) { func (*pluginA) Login() error { return nil }
func pluginAProvider(string, map[string]string, AuthProviderConfigPersister) (AuthProvider, error) {
return &pluginA{}, nil return &pluginA{}, nil
} }
...@@ -161,11 +242,70 @@ func (*pluginB) WrapTransport(rt http.RoundTripper) http.RoundTripper { ...@@ -161,11 +242,70 @@ func (*pluginB) WrapTransport(rt http.RoundTripper) http.RoundTripper {
return &wrapTransportB{rt} return &wrapTransportB{rt}
} }
func pluginBProvider() (AuthProvider, error) { func (*pluginB) Login() error { return nil }
func pluginBProvider(string, map[string]string, AuthProviderConfigPersister) (AuthProvider, error) {
return &pluginB{}, nil return &pluginB{}, nil
} }
// pluginFailProvider simulates a registered AuthPlugin that fails to load. // pluginFailProvider simulates a registered AuthPlugin that fails to load.
func pluginFailProvider() (AuthProvider, error) { func pluginFailProvider(string, map[string]string, AuthProviderConfigPersister) (AuthProvider, error) {
return nil, fmt.Errorf("Failed to load AuthProvider") return nil, fmt.Errorf("Failed to load AuthProvider")
} }
type inMemoryPersister struct {
savedConfig map[string]string
}
func (i *inMemoryPersister) Persist(config map[string]string) error {
i.savedConfig = make(map[string]string)
for k, v := range config {
i.savedConfig[k] = v
}
return nil
}
// wrapTransportPersist increments the "roundTrips" entry from the config when
// roundTrip is called.
type wrapTransportPersist struct {
rt http.RoundTripper
config map[string]string
persister AuthProviderConfigPersister
}
func (w *wrapTransportPersist) RoundTrip(req *http.Request) (*http.Response, error) {
roundTrips := 0
if rtVal, ok := w.config["roundTrips"]; ok {
var err error
roundTrips, err = strconv.Atoi(rtVal)
if err != nil {
return nil, err
}
}
roundTrips++
w.config["roundTrips"] = fmt.Sprintf("%d", roundTrips)
if err := w.persister.Persist(w.config); err != nil {
return nil, err
}
return w.rt.RoundTrip(req)
}
type pluginPersist struct {
config map[string]string
persister AuthProviderConfigPersister
}
func (p *pluginPersist) WrapTransport(rt http.RoundTripper) http.RoundTripper {
return &wrapTransportPersist{rt, p.config, p.persister}
}
// Login sets the config entry "login" to "Y".
func (p *pluginPersist) Login() error {
p.config["login"] = "Y"
p.persister.Persist(p.config)
return nil
}
func pluginPersistProvider(_ string, config map[string]string, persister AuthProviderConfigPersister) (AuthProvider, error) {
return &pluginPersist{config, persister}, nil
}
...@@ -60,7 +60,7 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip ...@@ -60,7 +60,7 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip
func (c *Config) transportConfig() (*transport.Config, error) { func (c *Config) transportConfig() (*transport.Config, error) {
wt := c.WrapTransport wt := c.WrapTransport
if c.AuthProvider != nil { if c.AuthProvider != nil {
provider, err := GetAuthProvider(c.AuthProvider) provider, err := GetAuthProvider(c.Host, c.AuthProvider, c.AuthConfigPersister)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -116,7 +116,8 @@ type Context struct { ...@@ -116,7 +116,8 @@ type Context struct {
// AuthProviderConfig holds the configuration for a specified auth provider. // AuthProviderConfig holds the configuration for a specified auth provider.
type AuthProviderConfig struct { type AuthProviderConfig struct {
Name string `json:"name"` Name string `json:"name"`
Config map[string]string `json:"config,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
......
...@@ -59,7 +59,13 @@ func Example_ofOptionsConfig() { ...@@ -59,7 +59,13 @@ func Example_ofOptionsConfig() {
Token: "my-secret-token", Token: "my-secret-token",
} }
defaultConfig.AuthInfos["black-mage-via-auth-provider"] = &AuthInfo{ defaultConfig.AuthInfos["black-mage-via-auth-provider"] = &AuthInfo{
AuthProvider: &AuthProviderConfig{Name: "gcp"}, AuthProvider: &AuthProviderConfig{
Name: "gcp",
Config: map[string]string{
"foo": "bar",
"token": "s3cr3t-t0k3n",
},
},
} }
defaultConfig.Contexts["bravo-as-black-mage"] = &Context{ defaultConfig.Contexts["bravo-as-black-mage"] = &Context{
Cluster: "bravo", Cluster: "bravo",
...@@ -115,6 +121,9 @@ func Example_ofOptionsConfig() { ...@@ -115,6 +121,9 @@ func Example_ofOptionsConfig() {
// black-mage-via-auth-provider: // black-mage-via-auth-provider:
// LocationOfOrigin: "" // LocationOfOrigin: ""
// auth-provider: // auth-provider:
// config:
// foo: bar
// token: s3cr3t-t0k3n
// name: gcp // name: gcp
// red-mage-via-token: // red-mage-via-token:
// LocationOfOrigin: "" // LocationOfOrigin: ""
......
...@@ -140,5 +140,6 @@ type NamedExtension struct { ...@@ -140,5 +140,6 @@ type NamedExtension struct {
// AuthProviderConfig holds the configuration for a specified auth provider. // AuthProviderConfig holds the configuration for a specified auth provider.
type AuthProviderConfig struct { type AuthProviderConfig struct {
Name string `json:"name"` Name string `json:"name"`
Config map[string]string `json:"config"`
} }
...@@ -41,7 +41,7 @@ var ( ...@@ -41,7 +41,7 @@ var (
// EnvVarCluster allows overriding the DefaultCluster using an envvar for the server name // EnvVarCluster allows overriding the DefaultCluster using an envvar for the server name
EnvVarCluster = clientcmdapi.Cluster{Server: os.Getenv("KUBERNETES_MASTER")} EnvVarCluster = clientcmdapi.Cluster{Server: os.Getenv("KUBERNETES_MASTER")}
DefaultClientConfig = DirectClientConfig{*clientcmdapi.NewConfig(), "", &ConfigOverrides{}, nil} DefaultClientConfig = DirectClientConfig{*clientcmdapi.NewConfig(), "", &ConfigOverrides{}, nil, NewDefaultClientConfigLoadingRules()}
) )
// ClientConfig is used to make it easy to get an api server client // ClientConfig is used to make it easy to get an api server client
...@@ -54,29 +54,34 @@ type ClientConfig interface { ...@@ -54,29 +54,34 @@ type ClientConfig interface {
// result of all overrides and a boolean indicating if it was // result of all overrides and a boolean indicating if it was
// overridden // overridden
Namespace() (string, bool, error) Namespace() (string, bool, error)
// ConfigAccess returns the rules for loading/persisting the config.
ConfigAccess() ConfigAccess
} }
type PersistAuthProviderConfigForUser func(user string) restclient.AuthProviderConfigPersister
// DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information // DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
type DirectClientConfig struct { type DirectClientConfig struct {
config clientcmdapi.Config config clientcmdapi.Config
contextName string contextName string
overrides *ConfigOverrides overrides *ConfigOverrides
fallbackReader io.Reader fallbackReader io.Reader
configAccess ConfigAccess
} }
// NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name // NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) ClientConfig { func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) ClientConfig {
return &DirectClientConfig{config, config.CurrentContext, overrides, nil} return &DirectClientConfig{config, config.CurrentContext, overrides, nil, NewDefaultClientConfigLoadingRules()}
} }
// NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information // NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information
func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides) ClientConfig { func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, configAccess ConfigAccess) ClientConfig {
return &DirectClientConfig{config, contextName, overrides, nil} return &DirectClientConfig{config, contextName, overrides, nil, configAccess}
} }
// NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags // NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags
func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader) ClientConfig { func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader, configAccess ConfigAccess) ClientConfig {
return &DirectClientConfig{config, contextName, overrides, fallbackReader} return &DirectClientConfig{config, contextName, overrides, fallbackReader, configAccess}
} }
func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) { func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) {
...@@ -110,7 +115,11 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) { ...@@ -110,7 +115,11 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
// mergo is a first write wins for map value and a last writing wins for interface values // mergo is a first write wins for map value and a last writing wins for interface values
// NOTE: This behavior changed with https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a. // NOTE: This behavior changed with https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a.
// Our mergo.Merge version is older than this change. // Our mergo.Merge version is older than this change.
userAuthPartialConfig, err := getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader) var persister restclient.AuthProviderConfigPersister
if config.configAccess != nil {
persister = PersisterForUser(config.configAccess, config.getAuthInfoName())
}
userAuthPartialConfig, err := getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -152,7 +161,7 @@ func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, ...@@ -152,7 +161,7 @@ func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo,
// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority) // 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
// 3. if there is not enough information to idenfity the user, load try the ~/.kubernetes_auth file // 3. if there is not enough information to idenfity the user, load try the ~/.kubernetes_auth file
// 4. if there is not enough information to identify the user, prompt if possible // 4. if there is not enough information to identify the user, prompt if possible
func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader) (*restclient.Config, error) { func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister) (*restclient.Config, error) {
mergedConfig := &restclient.Config{} mergedConfig := &restclient.Config{}
// blindly overwrite existing values based on precedence // blindly overwrite existing values based on precedence
...@@ -174,6 +183,7 @@ func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fa ...@@ -174,6 +183,7 @@ func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fa
} }
if configAuthInfo.AuthProvider != nil { if configAuthInfo.AuthProvider != nil {
mergedConfig.AuthProvider = configAuthInfo.AuthProvider mergedConfig.AuthProvider = configAuthInfo.AuthProvider
mergedConfig.AuthConfigPersister = persistAuthConfig
} }
// if there still isn't enough information to authenticate the user, try prompting // if there still isn't enough information to authenticate the user, try prompting
...@@ -219,7 +229,7 @@ func canIdentifyUser(config restclient.Config) bool { ...@@ -219,7 +229,7 @@ func canIdentifyUser(config restclient.Config) bool {
config.AuthProvider != nil config.AuthProvider != nil
} }
// Namespace implements KubeConfig // Namespace implements ClientConfig
func (config *DirectClientConfig) Namespace() (string, bool, error) { func (config *DirectClientConfig) Namespace() (string, bool, error) {
if err := config.ConfirmUsable(); err != nil { if err := config.ConfirmUsable(); err != nil {
return "", false, err return "", false, err
...@@ -238,6 +248,11 @@ func (config *DirectClientConfig) Namespace() (string, bool, error) { ...@@ -238,6 +248,11 @@ func (config *DirectClientConfig) Namespace() (string, bool, error) {
return configContext.Namespace, overridden, nil return configContext.Namespace, overridden, nil
} }
// ConfigAccess implements ClientConfig
func (config *DirectClientConfig) ConfigAccess() ConfigAccess {
return config.configAccess
}
// ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config, // ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
// but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible. // but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
func (config *DirectClientConfig) ConfirmUsable() error { func (config *DirectClientConfig) ConfirmUsable() error {
...@@ -354,6 +369,10 @@ func (inClusterClientConfig) Namespace() (string, error) { ...@@ -354,6 +369,10 @@ func (inClusterClientConfig) Namespace() (string, error) {
return "default", nil return "default", nil
} }
func (inClusterClientConfig) ConfigAccess() ConfigAccess {
return NewDefaultClientConfigLoadingRules()
}
// Possible returns true if loading an inside-kubernetes-cluster is possible. // Possible returns true if loading an inside-kubernetes-cluster is possible.
func (inClusterClientConfig) Possible() bool { func (inClusterClientConfig) Possible() bool {
fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token") fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token")
...@@ -376,7 +395,6 @@ func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, ...@@ -376,7 +395,6 @@ func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config,
} }
glog.Warning("error creating inClusterConfig, falling back to default config: ", err) glog.Warning("error creating inClusterConfig, falling back to default config: ", err)
} }
return NewNonInteractiveDeferredLoadingClientConfig( return NewNonInteractiveDeferredLoadingClientConfig(
&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath}, &ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig() &ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
......
...@@ -78,7 +78,7 @@ func TestInsecureOverridesCA(t *testing.T) { ...@@ -78,7 +78,7 @@ func TestInsecureOverridesCA(t *testing.T) {
ClusterInfo: clientcmdapi.Cluster{ ClusterInfo: clientcmdapi.Cluster{
InsecureSkipTLSVerify: true, InsecureSkipTLSVerify: true,
}, },
}) }, nil)
actualCfg, err := clientBuilder.ClientConfig() actualCfg, err := clientBuilder.ClientConfig()
if err != nil { if err != nil {
...@@ -94,7 +94,7 @@ func TestMergeContext(t *testing.T) { ...@@ -94,7 +94,7 @@ func TestMergeContext(t *testing.T) {
const namespace = "overriden-namespace" const namespace = "overriden-namespace"
config := createValidTestConfig() config := createValidTestConfig()
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}) clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
_, overridden, err := clientBuilder.Namespace() _, overridden, err := clientBuilder.Namespace()
if err != nil { if err != nil {
...@@ -109,7 +109,7 @@ func TestMergeContext(t *testing.T) { ...@@ -109,7 +109,7 @@ func TestMergeContext(t *testing.T) {
Context: clientcmdapi.Context{ Context: clientcmdapi.Context{
Namespace: namespace, Namespace: namespace,
}, },
}) }, nil)
actual, overridden, err := clientBuilder.Namespace() actual, overridden, err := clientBuilder.Namespace()
if err != nil { if err != nil {
...@@ -143,7 +143,7 @@ func TestCertificateData(t *testing.T) { ...@@ -143,7 +143,7 @@ func TestCertificateData(t *testing.T) {
} }
config.CurrentContext = "clean" config.CurrentContext = "clean"
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}) clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
clientConfig, err := clientBuilder.ClientConfig() clientConfig, err := clientBuilder.ClientConfig()
if err != nil { if err != nil {
...@@ -174,7 +174,7 @@ func TestBasicAuthData(t *testing.T) { ...@@ -174,7 +174,7 @@ func TestBasicAuthData(t *testing.T) {
} }
config.CurrentContext = "clean" config.CurrentContext = "clean"
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}) clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
clientConfig, err := clientBuilder.ClientConfig() clientConfig, err := clientBuilder.ClientConfig()
if err != nil { if err != nil {
...@@ -188,7 +188,7 @@ func TestBasicAuthData(t *testing.T) { ...@@ -188,7 +188,7 @@ func TestBasicAuthData(t *testing.T) {
func TestCreateClean(t *testing.T) { func TestCreateClean(t *testing.T) {
config := createValidTestConfig() config := createValidTestConfig()
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}) clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
clientConfig, err := clientBuilder.ClientConfig() clientConfig, err := clientBuilder.ClientConfig()
if err != nil { if err != nil {
...@@ -228,7 +228,7 @@ func TestCreateCleanWithPrefix(t *testing.T) { ...@@ -228,7 +228,7 @@ func TestCreateCleanWithPrefix(t *testing.T) {
cleanConfig.Server = tc.server cleanConfig.Server = tc.server
config.Clusters["clean"] = cleanConfig config.Clusters["clean"] = cleanConfig
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}) clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
clientConfig, err := clientBuilder.ClientConfig() clientConfig, err := clientBuilder.ClientConfig()
if err != nil { if err != nil {
...@@ -256,7 +256,7 @@ func TestCreateCleanDefault(t *testing.T) { ...@@ -256,7 +256,7 @@ func TestCreateCleanDefault(t *testing.T) {
func TestCreateMissingContext(t *testing.T) { func TestCreateMissingContext(t *testing.T) {
const expectedErrorContains = "Context was not found for specified context" const expectedErrorContains = "Context was not found for specified context"
config := createValidTestConfig() config := createValidTestConfig()
clientBuilder := NewNonInteractiveClientConfig(*config, "not-present", &ConfigOverrides{}) clientBuilder := NewNonInteractiveClientConfig(*config, "not-present", &ConfigOverrides{}, nil)
clientConfig, err := clientBuilder.ClientConfig() clientConfig, err := clientBuilder.ClientConfig()
if err != nil { if err != nil {
......
...@@ -229,6 +229,54 @@ func (rules *ClientConfigLoadingRules) Migrate() error { ...@@ -229,6 +229,54 @@ func (rules *ClientConfigLoadingRules) Migrate() error {
return nil return nil
} }
// GetLoadingPrecedence implements ConfigAccess
func (rules *ClientConfigLoadingRules) GetLoadingPrecedence() []string {
return rules.Precedence
}
// GetStartingConfig implements ConfigAccess
func (rules *ClientConfigLoadingRules) GetStartingConfig() (*clientcmdapi.Config, error) {
clientConfig := NewNonInteractiveDeferredLoadingClientConfig(rules, &ConfigOverrides{})
rawConfig, err := clientConfig.RawConfig()
if os.IsNotExist(err) {
return clientcmdapi.NewConfig(), nil
}
if err != nil {
return nil, err
}
return &rawConfig, nil
}
// GetDefaultFilename implements ConfigAccess
func (rules *ClientConfigLoadingRules) GetDefaultFilename() string {
// Explicit file if we have one.
if rules.IsExplicitFile() {
return rules.GetExplicitFile()
}
// Otherwise, first existing file from precedence.
for _, filename := range rules.GetLoadingPrecedence() {
if _, err := os.Stat(filename); err == nil {
return filename
}
}
// If none exists, use the first from precendence.
if len(rules.Precedence) > 0 {
return rules.Precedence[0]
}
return ""
}
// IsExplicitFile implements ConfigAccess
func (rules *ClientConfigLoadingRules) IsExplicitFile() bool {
return len(rules.ExplicitPath) > 0
}
// GetExplicitFile implements ConfigAccess
func (rules *ClientConfigLoadingRules) GetExplicitFile() string {
return rules.ExplicitPath
}
// 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)
......
...@@ -64,9 +64,9 @@ func (config *DeferredLoadingClientConfig) createClientConfig() (ClientConfig, e ...@@ -64,9 +64,9 @@ func (config *DeferredLoadingClientConfig) createClientConfig() (ClientConfig, e
var mergedClientConfig ClientConfig var mergedClientConfig ClientConfig
if config.fallbackReader != nil { if config.fallbackReader != nil {
mergedClientConfig = NewInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.fallbackReader) mergedClientConfig = NewInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.fallbackReader, config.loadingRules)
} else { } else {
mergedClientConfig = NewNonInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides) mergedClientConfig = NewNonInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.loadingRules)
} }
config.clientConfig = mergedClientConfig config.clientConfig = mergedClientConfig
...@@ -91,6 +91,7 @@ func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, e ...@@ -91,6 +91,7 @@ func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
mergedConfig, err := mergedClientConfig.ClientConfig() mergedConfig, err := mergedClientConfig.ClientConfig()
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -102,7 +103,6 @@ func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, e ...@@ -102,7 +103,6 @@ func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, e
glog.V(2).Info("No kubeconfig could be created, falling back to service account.") glog.V(2).Info("No kubeconfig could be created, falling back to service account.")
return icc.ClientConfig() return icc.ClientConfig()
} }
return mergedConfig, nil return mergedConfig, nil
} }
...@@ -115,3 +115,8 @@ func (config *DeferredLoadingClientConfig) Namespace() (string, bool, error) { ...@@ -115,3 +115,8 @@ func (config *DeferredLoadingClientConfig) Namespace() (string, bool, error) {
return mergedKubeConfig.Namespace() return mergedKubeConfig.Namespace()
} }
// ConfigAccess implements ClientConfig
func (config *DeferredLoadingClientConfig) ConfigAccess() ConfigAccess {
return config.loadingRules
}
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"io" "io"
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config" cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config"
"k8s.io/kubernetes/pkg/kubectl/cmd/rollout" "k8s.io/kubernetes/pkg/kubectl/cmd/rollout"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
...@@ -225,7 +226,7 @@ Find more information at https://github.com/kubernetes/kubernetes.`, ...@@ -225,7 +226,7 @@ Find more information at https://github.com/kubernetes/kubernetes.`,
cmds.AddCommand(NewCmdLabel(f, out)) cmds.AddCommand(NewCmdLabel(f, out))
cmds.AddCommand(NewCmdAnnotate(f, out)) cmds.AddCommand(NewCmdAnnotate(f, out))
cmds.AddCommand(cmdconfig.NewCmdConfig(cmdconfig.NewDefaultPathOptions(), out)) cmds.AddCommand(cmdconfig.NewCmdConfig(clientcmd.NewDefaultPathOptions(), out))
cmds.AddCommand(NewCmdClusterInfo(f, out)) cmds.AddCommand(NewCmdClusterInfo(f, out))
cmds.AddCommand(NewCmdApiVersions(f, out)) cmds.AddCommand(NewCmdApiVersions(f, out))
cmds.AddCommand(NewCmdVersion(f, out)) cmds.AddCommand(NewCmdVersion(f, out))
......
...@@ -770,12 +770,12 @@ func testConfigCommand(args []string, startingConfig clientcmdapi.Config, t *tes ...@@ -770,12 +770,12 @@ func testConfigCommand(args []string, startingConfig clientcmdapi.Config, t *tes
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfig(NewDefaultPathOptions(), buf) cmd := NewCmdConfig(clientcmd.NewDefaultPathOptions(), buf)
cmd.SetArgs(argsToUse) cmd.SetArgs(argsToUse)
cmd.Execute() cmd.Execute()
// outBytes, _ := ioutil.ReadFile(fakeKubeFile.Name()) // outBytes, _ := ioutil.ReadFile(fakeKubeFile.Name())
config := getConfigFromFileOrDie(fakeKubeFile.Name()) config := clientcmd.GetConfigFromFileOrDie(fakeKubeFile.Name())
return buf.String(), *config return buf.String(), *config
} }
......
...@@ -33,7 +33,7 @@ import ( ...@@ -33,7 +33,7 @@ import (
) )
type createAuthInfoOptions struct { type createAuthInfoOptions struct {
configAccess ConfigAccess configAccess clientcmd.ConfigAccess
name string name string
authPath util.StringFlag authPath util.StringFlag
clientCertificate util.StringFlag clientCertificate util.StringFlag
...@@ -69,7 +69,7 @@ kubectl config set-credentials cluster-admin --username=admin --password=uXFGweU ...@@ -69,7 +69,7 @@ kubectl config set-credentials cluster-admin --username=admin --password=uXFGweU
# Embed client certificate data in the "cluster-admin" entry # Embed client certificate data in the "cluster-admin" entry
kubectl config set-credentials cluster-admin --client-certificate=~/.kube/admin.crt --embed-certs=true` kubectl config set-credentials cluster-admin --client-certificate=~/.kube/admin.crt --embed-certs=true`
func NewCmdConfigSetAuthInfo(out io.Writer, configAccess ConfigAccess) *cobra.Command { func NewCmdConfigSetAuthInfo(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &createAuthInfoOptions{configAccess: configAccess} options := &createAuthInfoOptions{configAccess: configAccess}
cmd := &cobra.Command{ cmd := &cobra.Command{
...@@ -122,7 +122,7 @@ func (o createAuthInfoOptions) run() error { ...@@ -122,7 +122,7 @@ func (o createAuthInfoOptions) run() error {
authInfo := o.modifyAuthInfo(*startingStanza) authInfo := o.modifyAuthInfo(*startingStanza)
config.AuthInfos[o.name] = &authInfo config.AuthInfos[o.name] = &authInfo
if err := ModifyConfig(o.configAccess, *config, true); err != nil { if err := clientcmd.ModifyConfig(o.configAccess, *config, true); err != nil {
return err return err
} }
......
...@@ -32,7 +32,7 @@ import ( ...@@ -32,7 +32,7 @@ import (
) )
type createClusterOptions struct { type createClusterOptions struct {
configAccess ConfigAccess configAccess clientcmd.ConfigAccess
name string name string
server util.StringFlag server util.StringFlag
apiVersion util.StringFlag apiVersion util.StringFlag
...@@ -54,7 +54,7 @@ kubectl config set-cluster e2e --certificate-authority=~/.kube/e2e/kubernetes.ca ...@@ -54,7 +54,7 @@ kubectl config set-cluster e2e --certificate-authority=~/.kube/e2e/kubernetes.ca
kubectl config set-cluster e2e --insecure-skip-tls-verify=true` kubectl config set-cluster e2e --insecure-skip-tls-verify=true`
) )
func NewCmdConfigSetCluster(out io.Writer, configAccess ConfigAccess) *cobra.Command { func NewCmdConfigSetCluster(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &createClusterOptions{configAccess: configAccess} options := &createClusterOptions{configAccess: configAccess}
cmd := &cobra.Command{ cmd := &cobra.Command{
...@@ -108,7 +108,7 @@ func (o createClusterOptions) run() error { ...@@ -108,7 +108,7 @@ func (o createClusterOptions) run() error {
cluster := o.modifyCluster(*startingStanza) cluster := o.modifyCluster(*startingStanza)
config.Clusters[o.name] = &cluster config.Clusters[o.name] = &cluster
if err := ModifyConfig(o.configAccess, *config, true); err != nil { if err := clientcmd.ModifyConfig(o.configAccess, *config, true); err != nil {
return err return err
} }
......
...@@ -29,7 +29,7 @@ import ( ...@@ -29,7 +29,7 @@ import (
) )
type createContextOptions struct { type createContextOptions struct {
configAccess ConfigAccess configAccess clientcmd.ConfigAccess
name string name string
cluster util.StringFlag cluster util.StringFlag
authInfo util.StringFlag authInfo util.StringFlag
...@@ -43,7 +43,7 @@ Specifying a name that already exists will merge new fields on top of existing v ...@@ -43,7 +43,7 @@ Specifying a name that already exists will merge new fields on top of existing v
kubectl config set-context gce --user=cluster-admin` kubectl config set-context gce --user=cluster-admin`
) )
func NewCmdConfigSetContext(out io.Writer, configAccess ConfigAccess) *cobra.Command { func NewCmdConfigSetContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &createContextOptions{configAccess: configAccess} options := &createContextOptions{configAccess: configAccess}
cmd := &cobra.Command{ cmd := &cobra.Command{
...@@ -90,7 +90,7 @@ func (o createContextOptions) run() error { ...@@ -90,7 +90,7 @@ func (o createContextOptions) run() error {
context := o.modifyContext(*startingStanza) context := o.modifyContext(*startingStanza)
config.Contexts[o.name] = &context config.Contexts[o.name] = &context
if err := ModifyConfig(o.configAccess, *config, true); err != nil { if err := clientcmd.ModifyConfig(o.configAccess, *config, true); err != nil {
return err return err
} }
......
...@@ -19,13 +19,15 @@ package config ...@@ -19,13 +19,15 @@ package config
import ( import (
"fmt" "fmt"
"io" "io"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
) )
type CurrentContextOptions struct { type CurrentContextOptions struct {
ConfigAccess ConfigAccess ConfigAccess clientcmd.ConfigAccess
} }
const ( const (
...@@ -34,7 +36,7 @@ const ( ...@@ -34,7 +36,7 @@ const (
kubectl config current-context` kubectl config current-context`
) )
func NewCmdConfigCurrentContext(out io.Writer, configAccess ConfigAccess) *cobra.Command { func NewCmdConfigCurrentContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &CurrentContextOptions{ConfigAccess: configAccess} options := &CurrentContextOptions{ConfigAccess: configAccess}
cmd := &cobra.Command{ cmd := &cobra.Command{
......
...@@ -64,7 +64,7 @@ func (test currentContextTest) run(t *testing.T) { ...@@ -64,7 +64,7 @@ func (test currentContextTest) run(t *testing.T) {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
pathOptions := NewDefaultPathOptions() pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = fakeKubeFile.Name() pathOptions.GlobalFile = fakeKubeFile.Name()
pathOptions.EnvVar = "" pathOptions.EnvVar = ""
options := CurrentContextOptions{ options := CurrentContextOptions{
......
...@@ -26,6 +26,7 @@ import ( ...@@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/util/flag" "k8s.io/kubernetes/pkg/util/flag"
) )
...@@ -35,7 +36,7 @@ const ( ...@@ -35,7 +36,7 @@ const (
) )
type setOptions struct { type setOptions struct {
configAccess ConfigAccess configAccess clientcmd.ConfigAccess
propertyName string propertyName string
propertyValue string propertyValue string
setRawBytes flag.Tristate setRawBytes flag.Tristate
...@@ -45,7 +46,7 @@ const set_long = `Sets an individual value in a kubeconfig file ...@@ -45,7 +46,7 @@ const set_long = `Sets an individual value in a kubeconfig file
PROPERTY_NAME is a dot delimited name where each token represents either a attribute name or a map key. Map keys may not contain dots. PROPERTY_NAME is a dot delimited name where each token represents either a attribute name or a map key. Map keys may not contain dots.
PROPERTY_VALUE is the new value you wish to set. Binary fields such as 'certificate-authority-data' expect a base64 encoded string unless the --set-raw-bytes flag is used.` PROPERTY_VALUE is the new value you wish to set. Binary fields such as 'certificate-authority-data' expect a base64 encoded string unless the --set-raw-bytes flag is used.`
func NewCmdConfigSet(out io.Writer, configAccess ConfigAccess) *cobra.Command { func NewCmdConfigSet(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &setOptions{configAccess: configAccess} options := &setOptions{configAccess: configAccess}
cmd := &cobra.Command{ cmd := &cobra.Command{
...@@ -96,7 +97,7 @@ func (o setOptions) run() error { ...@@ -96,7 +97,7 @@ func (o setOptions) run() error {
return err return err
} }
if err := ModifyConfig(o.configAccess, *config, false); err != nil { if err := clientcmd.ModifyConfig(o.configAccess, *config, false); err != nil {
return err return err
} }
......
...@@ -23,17 +23,19 @@ import ( ...@@ -23,17 +23,19 @@ import (
"reflect" "reflect"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
) )
type unsetOptions struct { type unsetOptions struct {
configAccess ConfigAccess configAccess clientcmd.ConfigAccess
propertyName string propertyName string
} }
const unset_long = `Unsets an individual value in a kubeconfig file const unset_long = `Unsets an individual value in a kubeconfig file
PROPERTY_NAME is a dot delimited name where each token represents either a attribute name or a map key. Map keys may not contain dots.` PROPERTY_NAME is a dot delimited name where each token represents either a attribute name or a map key. Map keys may not contain dots.`
func NewCmdConfigUnset(out io.Writer, configAccess ConfigAccess) *cobra.Command { func NewCmdConfigUnset(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &unsetOptions{configAccess: configAccess} options := &unsetOptions{configAccess: configAccess}
cmd := &cobra.Command{ cmd := &cobra.Command{
...@@ -77,7 +79,7 @@ func (o unsetOptions) run() error { ...@@ -77,7 +79,7 @@ func (o unsetOptions) run() error {
return err return err
} }
if err := ModifyConfig(o.configAccess, *config, false); err != nil { if err := clientcmd.ModifyConfig(o.configAccess, *config, false); err != nil {
return err return err
} }
......
...@@ -23,15 +23,16 @@ import ( ...@@ -23,15 +23,16 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
) )
type useContextOptions struct { type useContextOptions struct {
configAccess ConfigAccess configAccess clientcmd.ConfigAccess
contextName string contextName string
} }
func NewCmdConfigUseContext(out io.Writer, configAccess ConfigAccess) *cobra.Command { func NewCmdConfigUseContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &useContextOptions{configAccess: configAccess} options := &useContextOptions{configAccess: configAccess}
cmd := &cobra.Command{ cmd := &cobra.Command{
...@@ -68,7 +69,7 @@ func (o useContextOptions) run() error { ...@@ -68,7 +69,7 @@ func (o useContextOptions) run() error {
config.CurrentContext = o.contextName config.CurrentContext = o.contextName
if err := ModifyConfig(o.configAccess, *config, true); err != nil { if err := clientcmd.ModifyConfig(o.configAccess, *config, true); err != nil {
return err return err
} }
......
...@@ -32,7 +32,7 @@ import ( ...@@ -32,7 +32,7 @@ import (
) )
type ViewOptions struct { type ViewOptions struct {
ConfigAccess ConfigAccess ConfigAccess clientcmd.ConfigAccess
Merge flag.Tristate Merge flag.Tristate
Flatten bool Flatten bool
Minify bool Minify bool
...@@ -50,7 +50,7 @@ kubectl config view ...@@ -50,7 +50,7 @@ kubectl config view
kubectl config view -o jsonpath='{.users[?(@.name == "e2e")].user.password}'` kubectl config view -o jsonpath='{.users[?(@.name == "e2e")].user.password}'`
) )
func NewCmdConfigView(out io.Writer, ConfigAccess ConfigAccess) *cobra.Command { func NewCmdConfigView(out io.Writer, ConfigAccess clientcmd.ConfigAccess) *cobra.Command {
options := &ViewOptions{ConfigAccess: ConfigAccess} options := &ViewOptions{ConfigAccess: ConfigAccess}
// Default to yaml // Default to yaml
defaultOutputFormat := "yaml" defaultOutputFormat := "yaml"
......
...@@ -18,6 +18,7 @@ package gcp ...@@ -18,6 +18,7 @@ package gcp
import ( import (
"net/http" "net/http"
"time"
"github.com/golang/glog" "github.com/golang/glog"
"golang.org/x/net/context" "golang.org/x/net/context"
...@@ -35,14 +36,15 @@ func init() { ...@@ -35,14 +36,15 @@ func init() {
type gcpAuthProvider struct { type gcpAuthProvider struct {
tokenSource oauth2.TokenSource tokenSource oauth2.TokenSource
persister restclient.AuthProviderConfigPersister
} }
func newGCPAuthProvider() (restclient.AuthProvider, error) { func newGCPAuthProvider(_ string, gcpConfig map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) {
ts, err := google.DefaultTokenSource(context.TODO(), "https://www.googleapis.com/auth/cloud-platform") ts, err := newCachedTokenSource(gcpConfig["access-token"], gcpConfig["expiry"], persister)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &gcpAuthProvider{ts}, nil return &gcpAuthProvider{ts, persister}, nil
} }
func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper { func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
...@@ -51,3 +53,54 @@ func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper ...@@ -51,3 +53,54 @@ func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper
Base: rt, Base: rt,
} }
} }
func (g *gcpAuthProvider) Login() error { return nil }
type cachedTokenSource struct {
source oauth2.TokenSource
accessToken string
expiry time.Time
persister restclient.AuthProviderConfigPersister
}
func newCachedTokenSource(accessToken, expiry string, persister restclient.AuthProviderConfigPersister) (*cachedTokenSource, error) {
var expiryTime time.Time
if parsedTime, err := time.Parse(time.RFC3339Nano, expiry); err == nil {
expiryTime = parsedTime
}
ts, err := google.DefaultTokenSource(context.Background(), "https://www.googleapis.com/auth/cloud-platform")
if err != nil {
return nil, err
}
return &cachedTokenSource{
source: ts,
accessToken: accessToken,
expiry: expiryTime,
persister: persister,
}, nil
}
func (t *cachedTokenSource) Token() (*oauth2.Token, error) {
tok := &oauth2.Token{
AccessToken: t.accessToken,
TokenType: "Bearer",
Expiry: t.expiry,
}
if tok.Valid() && !tok.Expiry.IsZero() {
return tok, nil
}
tok, err := t.source.Token()
if err != nil {
return nil, err
}
if t.persister != nil {
cached := map[string]string{
"access-token": tok.AccessToken,
"expiry": tok.Expiry.Format(time.RFC3339Nano),
}
if err := t.persister.Persist(cached); err != nil {
glog.V(4).Infof("Failed to persist token: %v", err)
}
}
return tok, nil
}
...@@ -56,7 +56,7 @@ func TestKubectlValidation(t *testing.T) { ...@@ -56,7 +56,7 @@ func TestKubectlValidation(t *testing.T) {
Context: *ctx, Context: *ctx,
CurrentContext: "test", CurrentContext: "test",
} }
cmdConfig := clientcmd.NewNonInteractiveClientConfig(*cfg, "test", &overrides) cmdConfig := clientcmd.NewNonInteractiveClientConfig(*cfg, "test", &overrides, nil)
factory := util.NewFactory(cmdConfig) factory := util.NewFactory(cmdConfig)
schema, err := factory.Validator(true, "") schema, err := factory.Validator(true, "")
if err != nil { if err != nil {
......
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