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
This commit is contained in:
k8s-merge-robot 2016-05-08 13:27:57 -07:00
commit f40fe7173b
26 changed files with 781 additions and 437 deletions

View File

@ -70,6 +70,9 @@ type Config struct {
// Server requires plugin-specified authentication.
AuthProvider *clientcmdapi.AuthProviderConfig
// Callback to persist config for AuthProvider.
AuthConfigPersister AuthProviderConfigPersister
// TLSClientConfig contains settings to enable transport layer security
TLSClientConfig

View File

@ -30,9 +30,22 @@ type AuthProvider interface {
// WrapTransport allows the plugin to create a modified RoundTripper that
// attaches authorization headers (or other info) to requests.
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.
var pluginsLock sync.Mutex
@ -49,12 +62,12 @@ func RegisterAuthProviderPlugin(name string, plugin Factory) error {
return nil
}
func GetAuthProvider(apc *clientcmdapi.AuthProviderConfig) (AuthProvider, error) {
func GetAuthProvider(clusterAddress string, apc *clientcmdapi.AuthProviderConfig, persister AuthProviderConfigPersister) (AuthProvider, error) {
pluginsLock.Lock()
defer pluginsLock.Unlock()
p, ok := plugins[apc.Name]
if !ok {
return nil, fmt.Errorf("No Auth Provider found for name %q", apc.Name)
}
return p()
return p(clusterAddress, apc.Config, persister)
}

View File

@ -19,12 +19,14 @@ package restclient
import (
"fmt"
"net/http"
"reflect"
"strconv"
"testing"
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 {
t.Errorf("Unexpected error: failed to register pluginA: %v", err)
}
@ -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
// to allow wrapping RoundTrippers to set header values.
type emptyTransport struct{}
@ -137,7 +216,9 @@ func (*pluginA) WrapTransport(rt http.RoundTripper) http.RoundTripper {
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
}
@ -161,11 +242,70 @@ func (*pluginB) WrapTransport(rt http.RoundTripper) http.RoundTripper {
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
}
// 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")
}
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
}

View File

@ -60,7 +60,7 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip
func (c *Config) transportConfig() (*transport.Config, error) {
wt := c.WrapTransport
if c.AuthProvider != nil {
provider, err := GetAuthProvider(c.AuthProvider)
provider, err := GetAuthProvider(c.Host, c.AuthProvider, c.AuthConfigPersister)
if err != nil {
return nil, err
}

View File

@ -116,7 +116,8 @@ type Context struct {
// AuthProviderConfig holds the configuration for a specified auth provider.
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

View File

@ -59,7 +59,13 @@ func Example_ofOptionsConfig() {
Token: "my-secret-token",
}
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{
Cluster: "bravo",
@ -115,6 +121,9 @@ func Example_ofOptionsConfig() {
// black-mage-via-auth-provider:
// LocationOfOrigin: ""
// auth-provider:
// config:
// foo: bar
// token: s3cr3t-t0k3n
// name: gcp
// red-mage-via-token:
// LocationOfOrigin: ""

View File

@ -140,5 +140,6 @@ type NamedExtension struct {
// AuthProviderConfig holds the configuration for a specified auth provider.
type AuthProviderConfig struct {
Name string `json:"name"`
Name string `json:"name"`
Config map[string]string `json:"config"`
}

View File

@ -41,7 +41,7 @@ var (
// EnvVarCluster allows overriding the DefaultCluster using an envvar for the server name
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
@ -54,29 +54,34 @@ type ClientConfig interface {
// result of all overrides and a boolean indicating if it was
// overridden
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
type DirectClientConfig struct {
config clientcmdapi.Config
contextName string
overrides *ConfigOverrides
fallbackReader io.Reader
configAccess ConfigAccess
}
// NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
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
func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides) ClientConfig {
return &DirectClientConfig{config, contextName, overrides, nil}
func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, configAccess ConfigAccess) ClientConfig {
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
func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader) ClientConfig {
return &DirectClientConfig{config, contextName, overrides, fallbackReader}
func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader, configAccess ConfigAccess) ClientConfig {
return &DirectClientConfig{config, contextName, overrides, fallbackReader, configAccess}
}
func (config *DirectClientConfig) RawConfig() (clientcmdapi.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
// NOTE: This behavior changed with https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a.
// 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 {
return nil, err
}
@ -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)
// 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
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{}
// blindly overwrite existing values based on precedence
@ -174,6 +183,7 @@ func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fa
}
if configAuthInfo.AuthProvider != nil {
mergedConfig.AuthProvider = configAuthInfo.AuthProvider
mergedConfig.AuthConfigPersister = persistAuthConfig
}
// if there still isn't enough information to authenticate the user, try prompting
@ -219,7 +229,7 @@ func canIdentifyUser(config restclient.Config) bool {
config.AuthProvider != nil
}
// Namespace implements KubeConfig
// Namespace implements ClientConfig
func (config *DirectClientConfig) Namespace() (string, bool, error) {
if err := config.ConfirmUsable(); err != nil {
return "", false, err
@ -238,6 +248,11 @@ func (config *DirectClientConfig) Namespace() (string, bool, error) {
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,
// 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 {
@ -354,6 +369,10 @@ func (inClusterClientConfig) Namespace() (string, error) {
return "default", nil
}
func (inClusterClientConfig) ConfigAccess() ConfigAccess {
return NewDefaultClientConfigLoadingRules()
}
// Possible returns true if loading an inside-kubernetes-cluster is possible.
func (inClusterClientConfig) Possible() bool {
fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token")
@ -376,7 +395,6 @@ func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config,
}
glog.Warning("error creating inClusterConfig, falling back to default config: ", err)
}
return NewNonInteractiveDeferredLoadingClientConfig(
&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()

View File

@ -78,7 +78,7 @@ func TestInsecureOverridesCA(t *testing.T) {
ClusterInfo: clientcmdapi.Cluster{
InsecureSkipTLSVerify: true,
},
})
}, nil)
actualCfg, err := clientBuilder.ClientConfig()
if err != nil {
@ -94,7 +94,7 @@ func TestMergeContext(t *testing.T) {
const namespace = "overriden-namespace"
config := createValidTestConfig()
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{})
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
_, overridden, err := clientBuilder.Namespace()
if err != nil {
@ -109,7 +109,7 @@ func TestMergeContext(t *testing.T) {
Context: clientcmdapi.Context{
Namespace: namespace,
},
})
}, nil)
actual, overridden, err := clientBuilder.Namespace()
if err != nil {
@ -143,7 +143,7 @@ func TestCertificateData(t *testing.T) {
}
config.CurrentContext = "clean"
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{})
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
clientConfig, err := clientBuilder.ClientConfig()
if err != nil {
@ -174,7 +174,7 @@ func TestBasicAuthData(t *testing.T) {
}
config.CurrentContext = "clean"
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{})
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
clientConfig, err := clientBuilder.ClientConfig()
if err != nil {
@ -188,7 +188,7 @@ func TestBasicAuthData(t *testing.T) {
func TestCreateClean(t *testing.T) {
config := createValidTestConfig()
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{})
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
clientConfig, err := clientBuilder.ClientConfig()
if err != nil {
@ -228,7 +228,7 @@ func TestCreateCleanWithPrefix(t *testing.T) {
cleanConfig.Server = tc.server
config.Clusters["clean"] = cleanConfig
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{})
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
clientConfig, err := clientBuilder.ClientConfig()
if err != nil {
@ -256,7 +256,7 @@ func TestCreateCleanDefault(t *testing.T) {
func TestCreateMissingContext(t *testing.T) {
const expectedErrorContains = "Context was not found for specified context"
config := createValidTestConfig()
clientBuilder := NewNonInteractiveClientConfig(*config, "not-present", &ConfigOverrides{})
clientBuilder := NewNonInteractiveClientConfig(*config, "not-present", &ConfigOverrides{}, nil)
clientConfig, err := clientBuilder.ClientConfig()
if err != nil {

View File

@ -0,0 +1,419 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package clientcmd
import (
"errors"
"os"
"path"
"path/filepath"
"reflect"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/client/restclient"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
// ConfigAccess is used by subcommands and methods in this package to load and modify the appropriate config files
type ConfigAccess interface {
// GetLoadingPrecedence returns the slice of files that should be used for loading and inspecting the config
GetLoadingPrecedence() []string
// GetStartingConfig returns the config that subcommands should being operating against. It may or may not be merged depending on loading rules
GetStartingConfig() (*clientcmdapi.Config, error)
// GetDefaultFilename returns the name of the file you should write into (create if necessary), if you're trying to create a new stanza as opposed to updating an existing one.
GetDefaultFilename() string
// IsExplicitFile indicates whether or not this command is interested in exactly one file. This implementation only ever does that via a flag, but implementations that handle local, global, and flags may have more
IsExplicitFile() bool
// GetExplicitFile returns the particular file this command is operating against. This implementation only ever has one, but implementations that handle local, global, and flags may have more
GetExplicitFile() string
}
type PathOptions struct {
// GlobalFile is the full path to the file to load as the global (final) option
GlobalFile string
// EnvVar is the env var name that points to the list of kubeconfig files to load
EnvVar string
// ExplicitFileFlag is the name of the flag to use for prompting for the kubeconfig file
ExplicitFileFlag string
// GlobalFileSubpath is an optional value used for displaying help
GlobalFileSubpath string
LoadingRules *ClientConfigLoadingRules
}
func (o *PathOptions) GetEnvVarFiles() []string {
if len(o.EnvVar) == 0 {
return []string{}
}
envVarValue := os.Getenv(o.EnvVar)
if len(envVarValue) == 0 {
return []string{}
}
return filepath.SplitList(envVarValue)
}
func (o *PathOptions) GetLoadingPrecedence() []string {
if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
return envVarFiles
}
return []string{o.GlobalFile}
}
func (o *PathOptions) GetStartingConfig() (*clientcmdapi.Config, error) {
// don't mutate the original
loadingRules := *o.LoadingRules
loadingRules.Precedence = o.GetLoadingPrecedence()
clientConfig := NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, &ConfigOverrides{})
rawConfig, err := clientConfig.RawConfig()
if os.IsNotExist(err) {
return clientcmdapi.NewConfig(), nil
}
if err != nil {
return nil, err
}
return &rawConfig, nil
}
func (o *PathOptions) GetDefaultFilename() string {
if o.IsExplicitFile() {
return o.GetExplicitFile()
}
if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
if len(envVarFiles) == 1 {
return envVarFiles[0]
}
// if any of the envvar files already exists, return it
for _, envVarFile := range envVarFiles {
if _, err := os.Stat(envVarFile); err == nil {
return envVarFile
}
}
// otherwise, return the last one in the list
return envVarFiles[len(envVarFiles)-1]
}
return o.GlobalFile
}
func (o *PathOptions) IsExplicitFile() bool {
if len(o.LoadingRules.ExplicitPath) > 0 {
return true
}
return false
}
func (o *PathOptions) GetExplicitFile() string {
return o.LoadingRules.ExplicitPath
}
func NewDefaultPathOptions() *PathOptions {
ret := &PathOptions{
GlobalFile: RecommendedHomeFile,
EnvVar: RecommendedConfigPathEnvVar,
ExplicitFileFlag: RecommendedConfigPathFlag,
GlobalFileSubpath: path.Join(RecommendedHomeDir, RecommendedFileName),
LoadingRules: NewDefaultClientConfigLoadingRules(),
}
ret.LoadingRules.DoNotResolvePaths = true
return ret
}
// ModifyConfig takes a Config object, iterates through Clusters, AuthInfos, and Contexts, uses the LocationOfOrigin if specified or
// uses the default destination file to write the results into. This results in multiple file reads, but it's very easy to follow.
// Preferences and CurrentContext should always be set in the default destination file. Since we can't distinguish between empty and missing values
// (no nil strings), we're forced have separate handling for them. In the kubeconfig cases, newConfig should have at most one difference,
// that means that this code will only write into a single file. If you want to relativizePaths, you must provide a fully qualified path in any
// modified element.
func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, relativizePaths bool) error {
startingConfig, err := configAccess.GetStartingConfig()
if err != nil {
return err
}
// We need to find all differences, locate their original files, read a partial config to modify only that stanza and write out the file.
// Special case the test for current context and preferences since those always write to the default file.
if reflect.DeepEqual(*startingConfig, newConfig) {
// nothing to do
return nil
}
if startingConfig.CurrentContext != newConfig.CurrentContext {
if err := writeCurrentContext(configAccess, newConfig.CurrentContext); err != nil {
return err
}
}
if !reflect.DeepEqual(startingConfig.Preferences, newConfig.Preferences) {
if err := writePreferences(configAccess, newConfig.Preferences); err != nil {
return err
}
}
// Search every cluster, authInfo, and context. First from new to old for differences, then from old to new for deletions
for key, cluster := range newConfig.Clusters {
startingCluster, exists := startingConfig.Clusters[key]
if !reflect.DeepEqual(cluster, startingCluster) || !exists {
destinationFile := cluster.LocationOfOrigin
if len(destinationFile) == 0 {
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite := GetConfigFromFileOrDie(destinationFile)
t := *cluster
configToWrite.Clusters[key] = &t
configToWrite.Clusters[key].LocationOfOrigin = destinationFile
if relativizePaths {
if err := RelativizeClusterLocalPaths(configToWrite.Clusters[key]); err != nil {
return err
}
}
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
}
for key, context := range newConfig.Contexts {
startingContext, exists := startingConfig.Contexts[key]
if !reflect.DeepEqual(context, startingContext) || !exists {
destinationFile := context.LocationOfOrigin
if len(destinationFile) == 0 {
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite := GetConfigFromFileOrDie(destinationFile)
configToWrite.Contexts[key] = context
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
}
for key, authInfo := range newConfig.AuthInfos {
startingAuthInfo, exists := startingConfig.AuthInfos[key]
if !reflect.DeepEqual(authInfo, startingAuthInfo) || !exists {
destinationFile := authInfo.LocationOfOrigin
if len(destinationFile) == 0 {
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite := GetConfigFromFileOrDie(destinationFile)
t := *authInfo
configToWrite.AuthInfos[key] = &t
configToWrite.AuthInfos[key].LocationOfOrigin = destinationFile
if relativizePaths {
if err := RelativizeAuthInfoLocalPaths(configToWrite.AuthInfos[key]); err != nil {
return err
}
}
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
}
for key, cluster := range startingConfig.Clusters {
if _, exists := newConfig.Clusters[key]; !exists {
destinationFile := cluster.LocationOfOrigin
if len(destinationFile) == 0 {
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite := GetConfigFromFileOrDie(destinationFile)
delete(configToWrite.Clusters, key)
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
}
for key, context := range startingConfig.Contexts {
if _, exists := newConfig.Contexts[key]; !exists {
destinationFile := context.LocationOfOrigin
if len(destinationFile) == 0 {
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite := GetConfigFromFileOrDie(destinationFile)
delete(configToWrite.Contexts, key)
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
}
for key, authInfo := range startingConfig.AuthInfos {
if _, exists := newConfig.AuthInfos[key]; !exists {
destinationFile := authInfo.LocationOfOrigin
if len(destinationFile) == 0 {
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite := GetConfigFromFileOrDie(destinationFile)
delete(configToWrite.AuthInfos, key)
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
}
return nil
}
func PersisterForUser(configAccess ConfigAccess, user string) restclient.AuthProviderConfigPersister {
return &persister{configAccess, user}
}
type persister struct {
configAccess ConfigAccess
user string
}
func (p *persister) Persist(config map[string]string) error {
newConfig, err := p.configAccess.GetStartingConfig()
if err != nil {
return err
}
authInfo, ok := newConfig.AuthInfos[p.user]
if ok && authInfo.AuthProvider != nil {
authInfo.AuthProvider.Config = config
ModifyConfig(p.configAccess, *newConfig, false)
}
return nil
}
// writeCurrentContext takes three possible paths.
// If newCurrentContext is the same as the startingConfig's current context, then we exit.
// If newCurrentContext has a value, then that value is written into the default destination file.
// If newCurrentContext is empty, then we find the config file that is setting the CurrentContext and clear the value from that file
func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) error {
if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
return err
} else if startingConfig.CurrentContext == newCurrentContext {
return nil
}
if configAccess.IsExplicitFile() {
file := configAccess.GetExplicitFile()
currConfig := GetConfigFromFileOrDie(file)
currConfig.CurrentContext = newCurrentContext
if err := WriteToFile(*currConfig, file); err != nil {
return err
}
return nil
}
if len(newCurrentContext) > 0 {
destinationFile := configAccess.GetDefaultFilename()
config := GetConfigFromFileOrDie(destinationFile)
config.CurrentContext = newCurrentContext
if err := WriteToFile(*config, destinationFile); err != nil {
return err
}
return nil
}
// we're supposed to be clearing the current context. We need to find the first spot in the chain that is setting it and clear it
for _, file := range configAccess.GetLoadingPrecedence() {
if _, err := os.Stat(file); err == nil {
currConfig := GetConfigFromFileOrDie(file)
if len(currConfig.CurrentContext) > 0 {
currConfig.CurrentContext = newCurrentContext
if err := WriteToFile(*currConfig, file); err != nil {
return err
}
return nil
}
}
}
return errors.New("no config found to write context")
}
func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferences) error {
if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
return err
} else if reflect.DeepEqual(startingConfig.Preferences, newPrefs) {
return nil
}
if configAccess.IsExplicitFile() {
file := configAccess.GetExplicitFile()
currConfig := GetConfigFromFileOrDie(file)
currConfig.Preferences = newPrefs
if err := WriteToFile(*currConfig, file); err != nil {
return err
}
return nil
}
for _, file := range configAccess.GetLoadingPrecedence() {
currConfig := GetConfigFromFileOrDie(file)
if !reflect.DeepEqual(currConfig.Preferences, newPrefs) {
currConfig.Preferences = newPrefs
if err := WriteToFile(*currConfig, file); err != nil {
return err
}
return nil
}
}
return errors.New("no config found to write preferences")
}
// GetConfigFromFileOrDie tries to read a kubeconfig file and if it can't, it calls exit. One exception, missing files result in empty configs, not an exit
func GetConfigFromFileOrDie(filename string) *clientcmdapi.Config {
config, err := LoadFromFile(filename)
if err != nil && !os.IsNotExist(err) {
glog.FatalDepth(1, err)
}
if config == nil {
return clientcmdapi.NewConfig()
}
return config
}

View File

@ -229,6 +229,54 @@ func (rules *ClientConfigLoadingRules) Migrate() error {
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
func LoadFromFile(filename string) (*clientcmdapi.Config, error) {
kubeconfigBytes, err := ioutil.ReadFile(filename)

View File

@ -64,9 +64,9 @@ func (config *DeferredLoadingClientConfig) createClientConfig() (ClientConfig, e
var mergedClientConfig ClientConfig
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 {
mergedClientConfig = NewNonInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides)
mergedClientConfig = NewNonInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.loadingRules)
}
config.clientConfig = mergedClientConfig
@ -91,6 +91,7 @@ func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, e
if err != nil {
return nil, err
}
mergedConfig, err := mergedClientConfig.ClientConfig()
if err != nil {
return nil, err
@ -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.")
return icc.ClientConfig()
}
return mergedConfig, nil
}
@ -115,3 +115,8 @@ func (config *DeferredLoadingClientConfig) Namespace() (string, bool, error) {
return mergedKubeConfig.Namespace()
}
// ConfigAccess implements ClientConfig
func (config *DeferredLoadingClientConfig) ConfigAccess() ConfigAccess {
return config.loadingRules
}

View File

@ -20,6 +20,7 @@ import (
"io"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config"
"k8s.io/kubernetes/pkg/kubectl/cmd/rollout"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
@ -225,7 +226,7 @@ Find more information at https://github.com/kubernetes/kubernetes.`,
cmds.AddCommand(NewCmdLabel(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(NewCmdApiVersions(f, out))
cmds.AddCommand(NewCmdVersion(f, out))

View File

@ -17,50 +17,16 @@ limitations under the License.
package config
import (
"errors"
"io"
"os"
"path"
"path/filepath"
"reflect"
"strconv"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
type PathOptions struct {
// GlobalFile is the full path to the file to load as the global (final) option
GlobalFile string
// EnvVar is the env var name that points to the list of kubeconfig files to load
EnvVar string
// ExplicitFileFlag is the name of the flag to use for prompting for the kubeconfig file
ExplicitFileFlag string
// GlobalFileSubpath is an optional value used for displaying help
GlobalFileSubpath string
LoadingRules *clientcmd.ClientConfigLoadingRules
}
// ConfigAccess is used by subcommands and methods in this package to load and modify the appropriate config files
type ConfigAccess interface {
// GetLoadingPrecedence returns the slice of files that should be used for loading and inspecting the config
GetLoadingPrecedence() []string
// GetStartingConfig returns the config that subcommands should being operating against. It may or may not be merged depending on loading rules
GetStartingConfig() (*clientcmdapi.Config, error)
// GetDefaultFilename returns the name of the file you should write into (create if necessary), if you're trying to create a new stanza as opposed to updating an existing one.
GetDefaultFilename() string
// IsExplicitFile indicates whether or not this command is interested in exactly one file. This implementation only ever does that via a flag, but implementations that handle local, global, and flags may have more
IsExplicitFile() bool
// GetExplicitFile returns the particular file this command is operating against. This implementation only ever has one, but implementations that handle local, global, and flags may have more
GetExplicitFile() string
}
func NewCmdConfig(pathOptions *PathOptions, out io.Writer) *cobra.Command {
func NewCmdConfig(pathOptions *clientcmd.PathOptions, out io.Writer) *cobra.Command {
if len(pathOptions.ExplicitFileFlag) == 0 {
pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag
}
@ -95,345 +61,6 @@ The loading order follows these rules:
return cmd
}
func NewDefaultPathOptions() *PathOptions {
ret := &PathOptions{
GlobalFile: clientcmd.RecommendedHomeFile,
EnvVar: clientcmd.RecommendedConfigPathEnvVar,
ExplicitFileFlag: clientcmd.RecommendedConfigPathFlag,
GlobalFileSubpath: path.Join(clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName),
LoadingRules: clientcmd.NewDefaultClientConfigLoadingRules(),
}
ret.LoadingRules.DoNotResolvePaths = true
return ret
}
func (o *PathOptions) GetEnvVarFiles() []string {
if len(o.EnvVar) == 0 {
return []string{}
}
envVarValue := os.Getenv(o.EnvVar)
if len(envVarValue) == 0 {
return []string{}
}
return filepath.SplitList(envVarValue)
}
func (o *PathOptions) GetLoadingPrecedence() []string {
if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
return envVarFiles
}
return []string{o.GlobalFile}
}
func (o *PathOptions) GetStartingConfig() (*clientcmdapi.Config, error) {
// don't mutate the original
loadingRules := *o.LoadingRules
loadingRules.Precedence = o.GetLoadingPrecedence()
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, &clientcmd.ConfigOverrides{})
rawConfig, err := clientConfig.RawConfig()
if os.IsNotExist(err) {
return clientcmdapi.NewConfig(), nil
}
if err != nil {
return nil, err
}
return &rawConfig, nil
}
func (o *PathOptions) GetDefaultFilename() string {
if o.IsExplicitFile() {
return o.GetExplicitFile()
}
if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
if len(envVarFiles) == 1 {
return envVarFiles[0]
}
// if any of the envvar files already exists, return it
for _, envVarFile := range envVarFiles {
if _, err := os.Stat(envVarFile); err == nil {
return envVarFile
}
}
// otherwise, return the last one in the list
return envVarFiles[len(envVarFiles)-1]
}
return o.GlobalFile
}
func (o *PathOptions) IsExplicitFile() bool {
if len(o.LoadingRules.ExplicitPath) > 0 {
return true
}
return false
}
func (o *PathOptions) GetExplicitFile() string {
return o.LoadingRules.ExplicitPath
}
// ModifyConfig takes a Config object, iterates through Clusters, AuthInfos, and Contexts, uses the LocationOfOrigin if specified or
// uses the default destination file to write the results into. This results in multiple file reads, but it's very easy to follow.
// Preferences and CurrentContext should always be set in the default destination file. Since we can't distinguish between empty and missing values
// (no nil strings), we're forced have separate handling for them. In the kubeconfig cases, newConfig should have at most one difference,
// that means that this code will only write into a single file. If you want to relativizePaths, you must provide a fully qualified path in any
// modified element.
func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, relativizePaths bool) error {
startingConfig, err := configAccess.GetStartingConfig()
if err != nil {
return err
}
// We need to find all differences, locate their original files, read a partial config to modify only that stanza and write out the file.
// Special case the test for current context and preferences since those always write to the default file.
if reflect.DeepEqual(*startingConfig, newConfig) {
// nothing to do
return nil
}
if startingConfig.CurrentContext != newConfig.CurrentContext {
if err := writeCurrentContext(configAccess, newConfig.CurrentContext); err != nil {
return err
}
}
if !reflect.DeepEqual(startingConfig.Preferences, newConfig.Preferences) {
if err := writePreferences(configAccess, newConfig.Preferences); err != nil {
return err
}
}
// Search every cluster, authInfo, and context. First from new to old for differences, then from old to new for deletions
for key, cluster := range newConfig.Clusters {
startingCluster, exists := startingConfig.Clusters[key]
if !reflect.DeepEqual(cluster, startingCluster) || !exists {
destinationFile := cluster.LocationOfOrigin
if len(destinationFile) == 0 {
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite := getConfigFromFileOrDie(destinationFile)
t := *cluster
configToWrite.Clusters[key] = &t
configToWrite.Clusters[key].LocationOfOrigin = destinationFile
if relativizePaths {
if err := clientcmd.RelativizeClusterLocalPaths(configToWrite.Clusters[key]); err != nil {
return err
}
}
if err := clientcmd.WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
}
for key, context := range newConfig.Contexts {
startingContext, exists := startingConfig.Contexts[key]
if !reflect.DeepEqual(context, startingContext) || !exists {
destinationFile := context.LocationOfOrigin
if len(destinationFile) == 0 {
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite := getConfigFromFileOrDie(destinationFile)
configToWrite.Contexts[key] = context
if err := clientcmd.WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
}
for key, authInfo := range newConfig.AuthInfos {
startingAuthInfo, exists := startingConfig.AuthInfos[key]
if !reflect.DeepEqual(authInfo, startingAuthInfo) || !exists {
destinationFile := authInfo.LocationOfOrigin
if len(destinationFile) == 0 {
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite := getConfigFromFileOrDie(destinationFile)
t := *authInfo
configToWrite.AuthInfos[key] = &t
configToWrite.AuthInfos[key].LocationOfOrigin = destinationFile
if relativizePaths {
if err := clientcmd.RelativizeAuthInfoLocalPaths(configToWrite.AuthInfos[key]); err != nil {
return err
}
}
if err := clientcmd.WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
}
for key, cluster := range startingConfig.Clusters {
if _, exists := newConfig.Clusters[key]; !exists {
destinationFile := cluster.LocationOfOrigin
if len(destinationFile) == 0 {
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite := getConfigFromFileOrDie(destinationFile)
delete(configToWrite.Clusters, key)
if err := clientcmd.WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
}
for key, context := range startingConfig.Contexts {
if _, exists := newConfig.Contexts[key]; !exists {
destinationFile := context.LocationOfOrigin
if len(destinationFile) == 0 {
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite := getConfigFromFileOrDie(destinationFile)
delete(configToWrite.Contexts, key)
if err := clientcmd.WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
}
for key, authInfo := range startingConfig.AuthInfos {
if _, exists := newConfig.AuthInfos[key]; !exists {
destinationFile := authInfo.LocationOfOrigin
if len(destinationFile) == 0 {
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite := getConfigFromFileOrDie(destinationFile)
delete(configToWrite.AuthInfos, key)
if err := clientcmd.WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
}
return nil
}
// writeCurrentContext takes three possible paths.
// If newCurrentContext is the same as the startingConfig's current context, then we exit.
// If newCurrentContext has a value, then that value is written into the default destination file.
// If newCurrentContext is empty, then we find the config file that is setting the CurrentContext and clear the value from that file
func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) error {
if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
return err
} else if startingConfig.CurrentContext == newCurrentContext {
return nil
}
if configAccess.IsExplicitFile() {
file := configAccess.GetExplicitFile()
currConfig := getConfigFromFileOrDie(file)
currConfig.CurrentContext = newCurrentContext
if err := clientcmd.WriteToFile(*currConfig, file); err != nil {
return err
}
return nil
}
if len(newCurrentContext) > 0 {
destinationFile := configAccess.GetDefaultFilename()
config := getConfigFromFileOrDie(destinationFile)
config.CurrentContext = newCurrentContext
if err := clientcmd.WriteToFile(*config, destinationFile); err != nil {
return err
}
return nil
}
// we're supposed to be clearing the current context. We need to find the first spot in the chain that is setting it and clear it
for _, file := range configAccess.GetLoadingPrecedence() {
if _, err := os.Stat(file); err == nil {
currConfig := getConfigFromFileOrDie(file)
if len(currConfig.CurrentContext) > 0 {
currConfig.CurrentContext = newCurrentContext
if err := clientcmd.WriteToFile(*currConfig, file); err != nil {
return err
}
return nil
}
}
}
return errors.New("no config found to write context")
}
func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferences) error {
if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
return err
} else if reflect.DeepEqual(startingConfig.Preferences, newPrefs) {
return nil
}
if configAccess.IsExplicitFile() {
file := configAccess.GetExplicitFile()
currConfig := getConfigFromFileOrDie(file)
currConfig.Preferences = newPrefs
if err := clientcmd.WriteToFile(*currConfig, file); err != nil {
return err
}
return nil
}
for _, file := range configAccess.GetLoadingPrecedence() {
currConfig := getConfigFromFileOrDie(file)
if !reflect.DeepEqual(currConfig.Preferences, newPrefs) {
currConfig.Preferences = newPrefs
if err := clientcmd.WriteToFile(*currConfig, file); err != nil {
return err
}
return nil
}
}
return errors.New("no config found to write preferences")
}
// getConfigFromFileOrDie tries to read a kubeconfig file and if it can't, it calls exit. One exception, missing files result in empty configs, not an exit
func getConfigFromFileOrDie(filename string) *clientcmdapi.Config {
config, err := clientcmd.LoadFromFile(filename)
if err != nil && !os.IsNotExist(err) {
glog.FatalDepth(1, err)
}
if config == nil {
return clientcmdapi.NewConfig()
}
return config
}
func toBool(propertyValue string) (bool, error) {
boolValue := false
if len(propertyValue) != 0 {

View File

@ -770,12 +770,12 @@ func testConfigCommand(args []string, startingConfig clientcmdapi.Config, t *tes
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfig(NewDefaultPathOptions(), buf)
cmd := NewCmdConfig(clientcmd.NewDefaultPathOptions(), buf)
cmd.SetArgs(argsToUse)
cmd.Execute()
// outBytes, _ := ioutil.ReadFile(fakeKubeFile.Name())
config := getConfigFromFileOrDie(fakeKubeFile.Name())
config := clientcmd.GetConfigFromFileOrDie(fakeKubeFile.Name())
return buf.String(), *config
}

View File

@ -33,7 +33,7 @@ import (
)
type createAuthInfoOptions struct {
configAccess ConfigAccess
configAccess clientcmd.ConfigAccess
name string
authPath util.StringFlag
clientCertificate util.StringFlag
@ -69,7 +69,7 @@ kubectl config set-credentials cluster-admin --username=admin --password=uXFGweU
# Embed client certificate data in the "cluster-admin" entry
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}
cmd := &cobra.Command{
@ -122,7 +122,7 @@ func (o createAuthInfoOptions) run() error {
authInfo := o.modifyAuthInfo(*startingStanza)
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
}

View File

@ -32,7 +32,7 @@ import (
)
type createClusterOptions struct {
configAccess ConfigAccess
configAccess clientcmd.ConfigAccess
name string
server util.StringFlag
apiVersion util.StringFlag
@ -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`
)
func NewCmdConfigSetCluster(out io.Writer, configAccess ConfigAccess) *cobra.Command {
func NewCmdConfigSetCluster(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &createClusterOptions{configAccess: configAccess}
cmd := &cobra.Command{
@ -108,7 +108,7 @@ func (o createClusterOptions) run() error {
cluster := o.modifyCluster(*startingStanza)
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
}

View File

@ -29,7 +29,7 @@ import (
)
type createContextOptions struct {
configAccess ConfigAccess
configAccess clientcmd.ConfigAccess
name string
cluster util.StringFlag
authInfo util.StringFlag
@ -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`
)
func NewCmdConfigSetContext(out io.Writer, configAccess ConfigAccess) *cobra.Command {
func NewCmdConfigSetContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &createContextOptions{configAccess: configAccess}
cmd := &cobra.Command{
@ -90,7 +90,7 @@ func (o createContextOptions) run() error {
context := o.modifyContext(*startingStanza)
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
}

View File

@ -19,13 +19,15 @@ package config
import (
"fmt"
"io"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
type CurrentContextOptions struct {
ConfigAccess ConfigAccess
ConfigAccess clientcmd.ConfigAccess
}
const (
@ -34,7 +36,7 @@ const (
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}
cmd := &cobra.Command{

View File

@ -64,7 +64,7 @@ func (test currentContextTest) run(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
pathOptions := NewDefaultPathOptions()
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = fakeKubeFile.Name()
pathOptions.EnvVar = ""
options := CurrentContextOptions{

View File

@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/util/flag"
)
@ -35,7 +36,7 @@ const (
)
type setOptions struct {
configAccess ConfigAccess
configAccess clientcmd.ConfigAccess
propertyName string
propertyValue string
setRawBytes flag.Tristate
@ -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_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}
cmd := &cobra.Command{
@ -96,7 +97,7 @@ func (o setOptions) run() error {
return err
}
if err := ModifyConfig(o.configAccess, *config, false); err != nil {
if err := clientcmd.ModifyConfig(o.configAccess, *config, false); err != nil {
return err
}

View File

@ -23,17 +23,19 @@ import (
"reflect"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
)
type unsetOptions struct {
configAccess ConfigAccess
configAccess clientcmd.ConfigAccess
propertyName string
}
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.`
func NewCmdConfigUnset(out io.Writer, configAccess ConfigAccess) *cobra.Command {
func NewCmdConfigUnset(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &unsetOptions{configAccess: configAccess}
cmd := &cobra.Command{
@ -77,7 +79,7 @@ func (o unsetOptions) run() error {
return err
}
if err := ModifyConfig(o.configAccess, *config, false); err != nil {
if err := clientcmd.ModifyConfig(o.configAccess, *config, false); err != nil {
return err
}

View File

@ -23,15 +23,16 @@ import (
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
type useContextOptions struct {
configAccess ConfigAccess
configAccess clientcmd.ConfigAccess
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}
cmd := &cobra.Command{
@ -68,7 +69,7 @@ func (o useContextOptions) run() error {
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
}

View File

@ -32,7 +32,7 @@ import (
)
type ViewOptions struct {
ConfigAccess ConfigAccess
ConfigAccess clientcmd.ConfigAccess
Merge flag.Tristate
Flatten bool
Minify bool
@ -50,7 +50,7 @@ kubectl config view
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}
// Default to yaml
defaultOutputFormat := "yaml"

View File

@ -18,6 +18,7 @@ package gcp
import (
"net/http"
"time"
"github.com/golang/glog"
"golang.org/x/net/context"
@ -35,14 +36,15 @@ func init() {
type gcpAuthProvider struct {
tokenSource oauth2.TokenSource
persister restclient.AuthProviderConfigPersister
}
func newGCPAuthProvider() (restclient.AuthProvider, error) {
ts, err := google.DefaultTokenSource(context.TODO(), "https://www.googleapis.com/auth/cloud-platform")
func newGCPAuthProvider(_ string, gcpConfig map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) {
ts, err := newCachedTokenSource(gcpConfig["access-token"], gcpConfig["expiry"], persister)
if err != nil {
return nil, err
}
return &gcpAuthProvider{ts}, nil
return &gcpAuthProvider{ts, persister}, nil
}
func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
@ -51,3 +53,54 @@ func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper
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
}

View File

@ -56,7 +56,7 @@ func TestKubectlValidation(t *testing.T) {
Context: *ctx,
CurrentContext: "test",
}
cmdConfig := clientcmd.NewNonInteractiveClientConfig(*cfg, "test", &overrides)
cmdConfig := clientcmd.NewNonInteractiveClientConfig(*cfg, "test", &overrides, nil)
factory := util.NewFactory(cmdConfig)
schema, err := factory.Validator(true, "")
if err != nil {