404 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			404 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
/*
 | 
						|
   Copyright 2018 The Kubernetes Authors.
 | 
						|
   Copy From: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/kubectl/pkg/cmd/get/get_flags.go
 | 
						|
   Change:  ConfigFlags struct add CaBundle fields, toRawKubeConfigLoader method modify new loadRules and overrides.
 | 
						|
*/
 | 
						|
 | 
						|
package karmadactl
 | 
						|
 | 
						|
import (
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"regexp"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/spf13/pflag"
 | 
						|
	"k8s.io/apimachinery/pkg/api/meta"
 | 
						|
	"k8s.io/client-go/discovery"
 | 
						|
	diskcached "k8s.io/client-go/discovery/cached/disk"
 | 
						|
	"k8s.io/client-go/rest"
 | 
						|
	"k8s.io/client-go/restmapper"
 | 
						|
	"k8s.io/client-go/tools/clientcmd"
 | 
						|
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
 | 
						|
	"k8s.io/client-go/util/homedir"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	flagClusterName   = "cluster"
 | 
						|
	flagAuthInfoName  = "user"
 | 
						|
	flagContext       = "context"
 | 
						|
	flagNamespace     = "namespace"
 | 
						|
	flagAPIServer     = "server"
 | 
						|
	flagTLSServerName = "tls-server-name"
 | 
						|
	flagInsecure      = "insecure-skip-tls-verify"
 | 
						|
	flagCertFile      = "client-certificate"
 | 
						|
	flagKeyFile       = "client-key"
 | 
						|
	flagCAFile        = "certificate-authority"
 | 
						|
	flagBearerToken   = "token"
 | 
						|
	flagTimeout       = "request-timeout"
 | 
						|
	flagCacheDir      = "cache-dir"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	defaultKubeConfig = filepath.Join(homedir.HomeDir(), ".kube", "config")
 | 
						|
	defaultCacheDir   = filepath.Join(homedir.HomeDir(), ".kube", "cache")
 | 
						|
	// ErrEmptyConfig is the error message to be displayed if the configuration info is missing or incomplete
 | 
						|
	ErrEmptyConfig = clientcmd.NewEmptyConfigError(
 | 
						|
		`Missing or incomplete configuration info.  Please point to an existing, complete config file:
 | 
						|
  1. Via the command-line flag --kubeconfig
 | 
						|
  2. Via the KUBECONFIG environment variable
 | 
						|
  3. In your home directory as ~/.kube/config
 | 
						|
 | 
						|
To view or setup config directly use the 'config' command.`)
 | 
						|
)
 | 
						|
 | 
						|
// RESTClientGetter is an interface that the ConfigFlags describe to provide an easier way to mock for commands
 | 
						|
// and eliminate the direct coupling to a struct type.  Users may wish to duplicate this type in their own packages
 | 
						|
// as per the golang type overlapping.
 | 
						|
type RESTClientGetter interface {
 | 
						|
	// ToRESTConfig returns restconfig
 | 
						|
	ToRESTConfig() (*rest.Config, error)
 | 
						|
	// ToDiscoveryClient returns discovery client
 | 
						|
	ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error)
 | 
						|
	// ToRESTMapper returns a restmapper
 | 
						|
	ToRESTMapper() (meta.RESTMapper, error)
 | 
						|
	// ToRawKubeConfigLoader return kubeconfig loader as-is
 | 
						|
	ToRawKubeConfigLoader() clientcmd.ClientConfig
 | 
						|
}
 | 
						|
 | 
						|
type clientConfig struct {
 | 
						|
	defaultClientConfig clientcmd.ClientConfig
 | 
						|
}
 | 
						|
 | 
						|
func (c *clientConfig) RawConfig() (clientcmdapi.Config, error) {
 | 
						|
	config, err := c.defaultClientConfig.RawConfig()
 | 
						|
	// replace client-go's ErrEmptyConfig error with our custom, more verbose version
 | 
						|
	if clientcmd.IsEmptyConfig(err) {
 | 
						|
		return config, ErrEmptyConfig
 | 
						|
	}
 | 
						|
	return config, err
 | 
						|
}
 | 
						|
 | 
						|
func (c *clientConfig) ClientConfig() (*rest.Config, error) {
 | 
						|
	config, err := c.defaultClientConfig.ClientConfig()
 | 
						|
	// replace client-go's ErrEmptyConfig error with our custom, more verbose version
 | 
						|
	if clientcmd.IsEmptyConfig(err) {
 | 
						|
		return config, ErrEmptyConfig
 | 
						|
	}
 | 
						|
	return config, err
 | 
						|
}
 | 
						|
 | 
						|
func (c *clientConfig) Namespace() (string, bool, error) {
 | 
						|
	namespace, ok, err := c.defaultClientConfig.Namespace()
 | 
						|
	// replace client-go's ErrEmptyConfig error with our custom, more verbose version
 | 
						|
	if clientcmd.IsEmptyConfig(err) {
 | 
						|
		return namespace, ok, ErrEmptyConfig
 | 
						|
	}
 | 
						|
	return namespace, ok, err
 | 
						|
}
 | 
						|
 | 
						|
func (c *clientConfig) ConfigAccess() clientcmd.ConfigAccess {
 | 
						|
	return c.defaultClientConfig.ConfigAccess()
 | 
						|
}
 | 
						|
 | 
						|
var _ RESTClientGetter = &ConfigFlags{}
 | 
						|
 | 
						|
// ConfigFlags composes the set of values necessary
 | 
						|
// for obtaining a REST client config
 | 
						|
type ConfigFlags struct {
 | 
						|
	CaBundle *string
 | 
						|
 | 
						|
	CacheDir   *string
 | 
						|
	KubeConfig *string
 | 
						|
 | 
						|
	// config flags
 | 
						|
	ClusterName      *string
 | 
						|
	AuthInfoName     *string
 | 
						|
	Context          *string
 | 
						|
	Namespace        *string
 | 
						|
	APIServer        *string
 | 
						|
	TLSServerName    *string
 | 
						|
	Insecure         *bool
 | 
						|
	CertFile         *string
 | 
						|
	KeyFile          *string
 | 
						|
	CAFile           *string
 | 
						|
	BearerToken      *string
 | 
						|
	Impersonate      *string
 | 
						|
	ImpersonateGroup *[]string
 | 
						|
	Username         *string
 | 
						|
	Password         *string
 | 
						|
	Timeout          *string
 | 
						|
	// If non-nil, wrap config function can transform the Config
 | 
						|
	// before it is returned in ToRESTConfig function.
 | 
						|
	WrapConfigFn func(*rest.Config) *rest.Config
 | 
						|
 | 
						|
	clientConfig clientcmd.ClientConfig
 | 
						|
	lock         sync.Mutex
 | 
						|
	// If set to true, will use persistent client config and
 | 
						|
	// propagate the config to the places that need it, rather than
 | 
						|
	// loading the config multiple times
 | 
						|
	usePersistentConfig bool
 | 
						|
	// Allows increasing burst used for discovery, this is useful
 | 
						|
	// in clusters with many registered resources
 | 
						|
	discoveryBurst int
 | 
						|
}
 | 
						|
 | 
						|
// ToRESTConfig implements RESTClientGetter.
 | 
						|
// Returns a REST client configuration based on a provided path
 | 
						|
// to a .kubeconfig file, loading rules, and config flag overrides.
 | 
						|
// Expects the AddFlags method to have been called. If WrapConfigFn
 | 
						|
// is non-nil this function can transform config before return.
 | 
						|
func (f *ConfigFlags) ToRESTConfig() (*rest.Config, error) {
 | 
						|
	c, err := f.ToRawKubeConfigLoader().ClientConfig()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if f.WrapConfigFn != nil {
 | 
						|
		return f.WrapConfigFn(c), nil
 | 
						|
	}
 | 
						|
	return c, nil
 | 
						|
}
 | 
						|
 | 
						|
// ToRawKubeConfigLoader binds config flag values to config overrides
 | 
						|
// Returns an interactive clientConfig if the password flag is enabled,
 | 
						|
// or a non-interactive clientConfig otherwise.
 | 
						|
func (f *ConfigFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig {
 | 
						|
	if f.usePersistentConfig {
 | 
						|
		return f.toRawKubePersistentConfigLoader()
 | 
						|
	}
 | 
						|
	return f.toRawKubeConfigLoader()
 | 
						|
}
 | 
						|
 | 
						|
//nolint:gocyclo
 | 
						|
func (f *ConfigFlags) toRawKubeConfigLoader() clientcmd.ClientConfig {
 | 
						|
	//loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
 | 
						|
	loadingRules := &clientcmd.ClientConfigLoadingRules{}
 | 
						|
	// use the standard defaults for this client command
 | 
						|
	// DEPRECATED: remove and replace with something more accurate
 | 
						|
	loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
 | 
						|
 | 
						|
	if f.KubeConfig != nil {
 | 
						|
		loadingRules.ExplicitPath = *f.KubeConfig
 | 
						|
	}
 | 
						|
 | 
						|
	clusterOverrides := clientcmd.ClusterDefaults
 | 
						|
	if f.CaBundle != nil {
 | 
						|
		clusterOverrides.CertificateAuthorityData = []byte(*f.CaBundle)
 | 
						|
	}
 | 
						|
 | 
						|
	overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clusterOverrides}
 | 
						|
 | 
						|
	// bind auth info flag values to overrides
 | 
						|
	if f.CertFile != nil {
 | 
						|
		overrides.AuthInfo.ClientCertificate = *f.CertFile
 | 
						|
	}
 | 
						|
	if f.KeyFile != nil {
 | 
						|
		overrides.AuthInfo.ClientKey = *f.KeyFile
 | 
						|
	}
 | 
						|
	if f.BearerToken != nil {
 | 
						|
		overrides.AuthInfo.Token = *f.BearerToken
 | 
						|
	}
 | 
						|
 | 
						|
	// bind cluster flags
 | 
						|
	if f.APIServer != nil {
 | 
						|
		overrides.ClusterInfo.Server = *f.APIServer
 | 
						|
	}
 | 
						|
	if f.TLSServerName != nil {
 | 
						|
		overrides.ClusterInfo.TLSServerName = *f.TLSServerName
 | 
						|
	}
 | 
						|
	if f.CAFile != nil {
 | 
						|
		overrides.ClusterInfo.CertificateAuthority = *f.CAFile
 | 
						|
	}
 | 
						|
	if f.Insecure != nil {
 | 
						|
		overrides.ClusterInfo.InsecureSkipTLSVerify = *f.Insecure
 | 
						|
	}
 | 
						|
 | 
						|
	// bind context flags
 | 
						|
	if f.Context != nil {
 | 
						|
		overrides.CurrentContext = *f.Context
 | 
						|
	}
 | 
						|
	if f.ClusterName != nil {
 | 
						|
		overrides.Context.Cluster = *f.ClusterName
 | 
						|
	}
 | 
						|
	if f.AuthInfoName != nil {
 | 
						|
		overrides.Context.AuthInfo = *f.AuthInfoName
 | 
						|
	}
 | 
						|
	if f.Namespace != nil {
 | 
						|
		overrides.Context.Namespace = *f.Namespace
 | 
						|
	}
 | 
						|
 | 
						|
	if f.Timeout != nil {
 | 
						|
		overrides.Timeout = *f.Timeout
 | 
						|
	}
 | 
						|
 | 
						|
	// we only have an interactive prompt when a password is allowed
 | 
						|
	if f.Password == nil {
 | 
						|
		return &clientConfig{clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)}
 | 
						|
	}
 | 
						|
	return &clientConfig{clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin)}
 | 
						|
}
 | 
						|
 | 
						|
// toRawKubePersistentConfigLoader binds config flag values to config overrides
 | 
						|
// Returns a persistent clientConfig for propagation.
 | 
						|
func (f *ConfigFlags) toRawKubePersistentConfigLoader() clientcmd.ClientConfig {
 | 
						|
	f.lock.Lock()
 | 
						|
	defer f.lock.Unlock()
 | 
						|
 | 
						|
	if f.clientConfig == nil {
 | 
						|
		f.clientConfig = f.toRawKubeConfigLoader()
 | 
						|
	}
 | 
						|
 | 
						|
	return f.clientConfig
 | 
						|
}
 | 
						|
 | 
						|
// ToDiscoveryClient implements RESTClientGetter.
 | 
						|
// Expects the AddFlags method to have been called.
 | 
						|
// Returns a CachedDiscoveryInterface using a computed RESTConfig.
 | 
						|
func (f *ConfigFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
 | 
						|
	config, err := f.ToRESTConfig()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// The more groups you have, the more discovery requests you need to make.
 | 
						|
	// given 25 groups (our groups + a few custom resources) with one-ish version each, discovery needs to make 50 requests
 | 
						|
	// double it just so we don't end up here again for a while.  This config is only used for discovery.
 | 
						|
	config.Burst = f.discoveryBurst
 | 
						|
 | 
						|
	cacheDir := defaultCacheDir
 | 
						|
 | 
						|
	// retrieve a user-provided value for the "cache-dir"
 | 
						|
	// override httpCacheDir and discoveryCacheDir if user-value is given.
 | 
						|
	if f.CacheDir != nil {
 | 
						|
		cacheDir = *f.CacheDir
 | 
						|
	}
 | 
						|
	httpCacheDir := filepath.Join(cacheDir, "http")
 | 
						|
	discoveryCacheDir := computeDiscoverCacheDir(filepath.Join(cacheDir, "discovery"), config.Host)
 | 
						|
 | 
						|
	return diskcached.NewCachedDiscoveryClientForConfig(config, discoveryCacheDir, httpCacheDir, time.Duration(10*time.Minute))
 | 
						|
}
 | 
						|
 | 
						|
// ToRESTMapper returns a mapper.
 | 
						|
func (f *ConfigFlags) ToRESTMapper() (meta.RESTMapper, error) {
 | 
						|
	discoveryClient, err := f.ToDiscoveryClient()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
 | 
						|
	expander := restmapper.NewShortcutExpander(mapper, discoveryClient)
 | 
						|
	return expander, nil
 | 
						|
}
 | 
						|
 | 
						|
// AddFlags binds client configuration flags to a given flagset
 | 
						|
func (f *ConfigFlags) AddFlags(flags *pflag.FlagSet) {
 | 
						|
	if f.KubeConfig != nil {
 | 
						|
		flags.StringVar(f.KubeConfig, "kubeconfig", *f.KubeConfig, "Path to the kubeconfig file to use for CLI requests.")
 | 
						|
	}
 | 
						|
	if f.CacheDir != nil {
 | 
						|
		flags.StringVar(f.CacheDir, flagCacheDir, *f.CacheDir, "Default cache directory")
 | 
						|
	}
 | 
						|
 | 
						|
	// add config options
 | 
						|
	if f.CertFile != nil {
 | 
						|
		flags.StringVar(f.CertFile, flagCertFile, *f.CertFile, "Path to a client certificate file for TLS")
 | 
						|
	}
 | 
						|
	if f.KeyFile != nil {
 | 
						|
		flags.StringVar(f.KeyFile, flagKeyFile, *f.KeyFile, "Path to a client key file for TLS")
 | 
						|
	}
 | 
						|
	if f.BearerToken != nil {
 | 
						|
		flags.StringVar(f.BearerToken, flagBearerToken, *f.BearerToken, "Bearer token for authentication to the API server")
 | 
						|
	}
 | 
						|
	if f.ClusterName != nil {
 | 
						|
		flags.StringVar(f.ClusterName, flagClusterName, *f.ClusterName, "The name of the kubeconfig cluster to use")
 | 
						|
	}
 | 
						|
	if f.AuthInfoName != nil {
 | 
						|
		flags.StringVar(f.AuthInfoName, flagAuthInfoName, *f.AuthInfoName, "The name of the kubeconfig user to use")
 | 
						|
	}
 | 
						|
	if f.Namespace != nil {
 | 
						|
		flags.StringVarP(f.Namespace, flagNamespace, "n", *f.Namespace, "If present, the namespace scope for this CLI request")
 | 
						|
	}
 | 
						|
	if f.Context != nil {
 | 
						|
		flags.StringVar(f.Context, flagContext, *f.Context, "The name of the kubeconfig context to use")
 | 
						|
	}
 | 
						|
 | 
						|
	if f.APIServer != nil {
 | 
						|
		flags.StringVarP(f.APIServer, flagAPIServer, "s", *f.APIServer, "The address and port of the Kubernetes API server")
 | 
						|
	}
 | 
						|
	if f.TLSServerName != nil {
 | 
						|
		flags.StringVar(f.TLSServerName, flagTLSServerName, *f.TLSServerName, "Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used")
 | 
						|
	}
 | 
						|
	if f.Insecure != nil {
 | 
						|
		flags.BoolVar(f.Insecure, flagInsecure, *f.Insecure, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
 | 
						|
	}
 | 
						|
	if f.CAFile != nil {
 | 
						|
		flags.StringVar(f.CAFile, flagCAFile, *f.CAFile, "Path to a cert file for the certificate authority")
 | 
						|
	}
 | 
						|
	if f.Timeout != nil {
 | 
						|
		flags.StringVar(f.Timeout, flagTimeout, *f.Timeout, "The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests.")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithDeprecatedPasswordFlag enables the username and password config flags
 | 
						|
func (f *ConfigFlags) WithDeprecatedPasswordFlag() *ConfigFlags {
 | 
						|
	f.Username = stringptr("")
 | 
						|
	f.Password = stringptr("")
 | 
						|
	return f
 | 
						|
}
 | 
						|
 | 
						|
// WithDiscoveryBurst sets the RESTClient burst for discovery.
 | 
						|
func (f *ConfigFlags) WithDiscoveryBurst(discoveryBurst int) *ConfigFlags {
 | 
						|
	f.discoveryBurst = discoveryBurst
 | 
						|
	return f
 | 
						|
}
 | 
						|
 | 
						|
// NewConfigFlags returns ConfigFlags with default values set
 | 
						|
func NewConfigFlags(usePersistentConfig bool) *ConfigFlags {
 | 
						|
	impersonateGroup := []string{}
 | 
						|
	insecure := false
 | 
						|
 | 
						|
	return &ConfigFlags{
 | 
						|
		Insecure:   &insecure,
 | 
						|
		Timeout:    stringptr("0"),
 | 
						|
		KubeConfig: stringptr(""),
 | 
						|
 | 
						|
		CacheDir:         stringptr(defaultCacheDir),
 | 
						|
		ClusterName:      stringptr(""),
 | 
						|
		AuthInfoName:     stringptr(""),
 | 
						|
		Context:          stringptr(""),
 | 
						|
		Namespace:        stringptr(""),
 | 
						|
		APIServer:        stringptr(""),
 | 
						|
		TLSServerName:    stringptr(""),
 | 
						|
		CertFile:         stringptr(""),
 | 
						|
		KeyFile:          stringptr(""),
 | 
						|
		CAFile:           stringptr(""),
 | 
						|
		BearerToken:      stringptr(""),
 | 
						|
		Impersonate:      stringptr(""),
 | 
						|
		ImpersonateGroup: &impersonateGroup,
 | 
						|
 | 
						|
		usePersistentConfig: usePersistentConfig,
 | 
						|
		// The more groups you have, the more discovery requests you need to make.
 | 
						|
		// given 25 groups (our groups + a few custom resources) with one-ish version each, discovery needs to make 50 requests
 | 
						|
		// double it just so we don't end up here again for a while.  This config is only used for discovery.
 | 
						|
		discoveryBurst: 100,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func stringptr(val string) *string {
 | 
						|
	return &val
 | 
						|
}
 | 
						|
 | 
						|
// overlyCautiousIllegalFileCharacters matches characters that *might* not be supported.  Windows is really restrictive, so this is really restrictive
 | 
						|
var overlyCautiousIllegalFileCharacters = regexp.MustCompile(`[^(\w/\.)]`)
 | 
						|
 | 
						|
// computeDiscoverCacheDir takes the parentDir and the host and comes up with a "usually non-colliding" name.
 | 
						|
func computeDiscoverCacheDir(parentDir, host string) string {
 | 
						|
	// strip the optional scheme from host if its there:
 | 
						|
	schemelessHost := strings.Replace(strings.Replace(host, "https://", "", 1), "http://", "", 1)
 | 
						|
	// now do a simple collapse of non-AZ09 characters.  Collisions are possible but unlikely.  Even if we do collide the problem is short lived
 | 
						|
	safeHost := overlyCautiousIllegalFileCharacters.ReplaceAllString(schemelessHost, "_")
 | 
						|
	return filepath.Join(parentDir, safeHost)
 | 
						|
}
 |