mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
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:
commit
f40fe7173b
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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: ""
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
419
pkg/client/unversioned/clientcmd/config.go
Normal file
419
pkg/client/unversioned/clientcmd/config.go
Normal 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
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user