package kcli import ( "flag" "fmt" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) // KubeConfig contains configuration related to loading and parsing a Kubeconfig // file for instantiating K8s clients. // We don't use the built-in clientcmd flag registration utilities because // we don't want to expose the full sets of flags as K8s does (eg API server URL, // namespace, etc). type KubeConfig struct { Context string Path string clientCfg clientcmd.ClientConfig } // RegisterFlags binds common flags to an instance of the KubeConfig struct, // enabling flag reuse for any binary which needs to build a K8s client and wants // to allow users to override defaults via flags. func (k *KubeConfig) RegisterFlags(fs *flag.FlagSet) { fs.StringVar(&k.Path, "kubeconfig", "", "Path to the kubeconfig file. "+ "Uses standard config loading rules if one is not provided.") fs.StringVar(&k.Context, "context", "", "Name of the kubeconfig context to use. "+ "Defaults to current context if not provided.") } // SetupClientConfig creates client loading configuration based on the values // of bound flags, falling back to bog standard defaults if nothing is provided. func (k *KubeConfig) SetupClientConfig() error { cfgOverrides := clientcmd.ConfigOverrides{} loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() if k.Path != "" { loadingRules = &clientcmd.ClientConfigLoadingRules{ExplicitPath: k.Path} } if k.Context != "" { cfgOverrides.CurrentContext = k.Context } clientCfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( loadingRules, &cfgOverrides, ) k.clientCfg = clientCfg return nil } // RESTConfig returns the rest.Config required to create a K8s client. func (k *KubeConfig) RESTConfig() (*rest.Config, error) { if k.clientCfg == nil { if err := k.SetupClientConfig(); err != nil { return nil, err } } return k.clientCfg.ClientConfig() } // Mapper instantiates the API resource mapper from the REST config. func (k *KubeConfig) Mapper() (meta.RESTMapper, error) { cfg, err := k.RESTConfig() if err != nil { return nil, fmt.Errorf("failed to create k8s client configuration: %w", err) } httpClient, err := rest.HTTPClientFor(cfg) if err != nil { return nil, fmt.Errorf("failed to create http client for mapper: %w", err) } return apiutil.NewDynamicRESTMapper(cfg, httpClient) } // RawConfig returns the raw client configuration file (aka kubeconfig), // which can be used to check contexts, cluster names, etc. func (k *KubeConfig) RawConfig() (clientcmdapi.Config, error) { if k.clientCfg == nil { if err := k.SetupClientConfig(); err != nil { return clientcmdapi.Config{}, err } } return k.clientCfg.RawConfig() } // Client creates a K8s client from the parsed KubeConfig. This is a helper // function for binaries binding kubeconfig flags using this library. func (k *KubeConfig) Client(opts client.Options) (client.Client, error) { cfg, err := k.RESTConfig() if err != nil { return nil, fmt.Errorf("failed to create k8s client configuration: %w", err) } kclient, err := client.New(cfg, opts) if err != nil { return nil, fmt.Errorf("failed to create k8s client: %w", err) } return kclient, nil } // DynamicClient creates a dynamic client from the parsed KubeConfig. func (k *KubeConfig) DynamicClient() (dynamic.Interface, error) { cfg, err := k.RESTConfig() if err != nil { return nil, fmt.Errorf("failed to create k8s client configuration: %w", err) } return dynamic.NewForConfig(cfg) }