-
Notifications
You must be signed in to change notification settings - Fork 166
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(factory): abstraction to generate clients and builders given the…
… config and flags combination Notice this code is from kubernetes source code. Cannot be imported atm because of issues with go modules otherwise.
- Loading branch information
Showing
3 changed files
with
316 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
package factory | ||
|
||
import ( | ||
"sync" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/api/meta" | ||
"k8s.io/cli-runtime/pkg/genericclioptions" | ||
"k8s.io/cli-runtime/pkg/genericclioptions/resource" | ||
"k8s.io/client-go/discovery" | ||
"k8s.io/client-go/dynamic" | ||
"k8s.io/client-go/kubernetes" | ||
restclient "k8s.io/client-go/rest" | ||
"k8s.io/client-go/tools/clientcmd" | ||
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" | ||
openapivalidation "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation" | ||
"k8s.io/kubernetes/pkg/kubectl/validation" | ||
) | ||
|
||
// Factory provides abstractions that allow the kubectl command to be extended across multiple types of resources and different API sets. | ||
// The rings are here for a reason. | ||
// In order for composers to be able to provide alternative factory implementations | ||
// they need to provide low level pieces of *certain* functions so that when the factory calls back into itself | ||
// it uses the custom version of the function. Rather than try to enumerate everything that someone would want to override | ||
// we split the factory into rings, where each ring can depend on methods in an earlier ring, but cannot depend | ||
// upon peer methods in its own ring. | ||
// TODO: make the functions interfaces | ||
type Factory interface { | ||
genericclioptions.RESTClientGetter | ||
|
||
// DynamicClient returns a dynamic client ready for use | ||
DynamicClient() (dynamic.Interface, error) | ||
|
||
// KubernetesClientSet gives you back an external clientset | ||
KubernetesClientSet() (*kubernetes.Clientset, error) | ||
|
||
// Returns a RESTClient for accessing Kubernetes resources or an error. | ||
RESTClient() (*restclient.RESTClient, error) | ||
|
||
// NewBuilder returns an object that assists in loading objects from both disk and the server | ||
// and which implements the common patterns for CLI interactions with generic resources. | ||
NewBuilder() *resource.Builder | ||
|
||
// Returns a RESTClient for working with the specified RESTMapping or an error. This is intended | ||
// for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer. | ||
ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) | ||
|
||
// Returns a RESTClient for working with Unstructured objects. | ||
UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) | ||
|
||
// Returns a schema that can validate objects stored on disk. | ||
Validator(validate bool) (validation.Schema, error) | ||
|
||
// OpenAPISchema returns the schema openapi schema definition | ||
OpenAPISchema() (openapi.Resources, error) | ||
} | ||
|
||
type factoryImpl struct { | ||
clientGetter genericclioptions.RESTClientGetter | ||
|
||
// openAPIGetter loads and caches openapi specs | ||
openAPIGetter openAPIGetter | ||
} | ||
|
||
type openAPIGetter struct { | ||
once sync.Once | ||
getter openapi.Getter | ||
} | ||
|
||
func NewFactory(clientGetter genericclioptions.RESTClientGetter) Factory { | ||
if clientGetter == nil { | ||
panic("attempt to instantiate client_access_factory with nil clientGetter") | ||
} | ||
|
||
f := &factoryImpl{ | ||
clientGetter: clientGetter, | ||
} | ||
|
||
return f | ||
} | ||
|
||
func (f *factoryImpl) ToRESTConfig() (*restclient.Config, error) { | ||
return f.clientGetter.ToRESTConfig() | ||
} | ||
|
||
func (f *factoryImpl) ToRESTMapper() (meta.RESTMapper, error) { | ||
return f.clientGetter.ToRESTMapper() | ||
} | ||
|
||
func (f *factoryImpl) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { | ||
return f.clientGetter.ToDiscoveryClient() | ||
} | ||
|
||
func (f *factoryImpl) ToRawKubeConfigLoader() clientcmd.ClientConfig { | ||
return f.clientGetter.ToRawKubeConfigLoader() | ||
} | ||
|
||
func (f *factoryImpl) KubernetesClientSet() (*kubernetes.Clientset, error) { | ||
clientConfig, err := f.ToRESTConfig() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return kubernetes.NewForConfig(clientConfig) | ||
} | ||
|
||
func (f *factoryImpl) DynamicClient() (dynamic.Interface, error) { | ||
clientConfig, err := f.ToRESTConfig() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return dynamic.NewForConfig(clientConfig) | ||
} | ||
|
||
// NewBuilder returns a new resource builder for structured api objects. | ||
func (f *factoryImpl) NewBuilder() *resource.Builder { | ||
return resource.NewBuilder(f.clientGetter) | ||
} | ||
|
||
func (f *factoryImpl) RESTClient() (*restclient.RESTClient, error) { | ||
clientConfig, err := f.ToRESTConfig() | ||
if err != nil { | ||
return nil, err | ||
} | ||
setKubernetesDefaults(clientConfig) | ||
return restclient.RESTClientFor(clientConfig) | ||
} | ||
|
||
func (f *factoryImpl) ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) { | ||
cfg, err := f.clientGetter.ToRESTConfig() | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := setKubernetesDefaults(cfg); err != nil { | ||
return nil, err | ||
} | ||
gvk := mapping.GroupVersionKind | ||
switch gvk.Group { | ||
case corev1.GroupName: | ||
cfg.APIPath = "/api" | ||
default: | ||
cfg.APIPath = "/apis" | ||
} | ||
gv := gvk.GroupVersion() | ||
cfg.GroupVersion = &gv | ||
return restclient.RESTClientFor(cfg) | ||
} | ||
|
||
func (f *factoryImpl) UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) { | ||
cfg, err := f.clientGetter.ToRESTConfig() | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := restclient.SetKubernetesDefaults(cfg); err != nil { | ||
return nil, err | ||
} | ||
cfg.APIPath = "/apis" | ||
if mapping.GroupVersionKind.Group == corev1.GroupName { | ||
cfg.APIPath = "/api" | ||
} | ||
gv := mapping.GroupVersionKind.GroupVersion() | ||
cfg.ContentConfig = resource.UnstructuredPlusDefaultContentConfig() | ||
cfg.GroupVersion = &gv | ||
return restclient.RESTClientFor(cfg) | ||
} | ||
|
||
func (f *factoryImpl) Validator(validate bool) (validation.Schema, error) { | ||
if !validate { | ||
return validation.NullSchema{}, nil | ||
} | ||
|
||
resources, err := f.OpenAPISchema() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return validation.ConjunctiveSchema{ | ||
openapivalidation.NewSchemaValidation(resources), | ||
validation.NoDoubleKeySchema{}, | ||
}, nil | ||
} | ||
|
||
// OpenAPISchema returns metadata and structural information about Kubernetes object definitions. | ||
func (f *factoryImpl) OpenAPISchema() (openapi.Resources, error) { | ||
discovery, err := f.clientGetter.ToDiscoveryClient() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Lazily initialize the OpenAPIGetter once | ||
f.openAPIGetter.once.Do(func() { | ||
// Create the caching OpenAPIGetter | ||
f.openAPIGetter.getter = openapi.NewOpenAPIGetter(discovery) | ||
}) | ||
|
||
// Delegate to the OpenAPIGetter | ||
return f.openAPIGetter.getter.Get() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package factory | ||
|
||
import ( | ||
"sync" | ||
|
||
"github.com/spf13/pflag" | ||
|
||
"k8s.io/apimachinery/pkg/api/meta" | ||
"k8s.io/client-go/discovery" | ||
"k8s.io/client-go/rest" | ||
"k8s.io/client-go/tools/clientcmd" | ||
|
||
"k8s.io/cli-runtime/pkg/genericclioptions" | ||
"k8s.io/kubernetes/pkg/version" | ||
) | ||
|
||
const ( | ||
flagMatchBinaryVersion = "match-server-version" | ||
) | ||
|
||
// MatchVersionFlags is for setting the "match server version" function. | ||
type MatchVersionFlags struct { | ||
Delegate genericclioptions.RESTClientGetter | ||
|
||
RequireMatchedServerVersion bool | ||
checkServerVersion sync.Once | ||
matchesServerVersionErr error | ||
} | ||
|
||
var _ genericclioptions.RESTClientGetter = &MatchVersionFlags{} | ||
|
||
func (f *MatchVersionFlags) checkMatchingServerVersion() error { | ||
f.checkServerVersion.Do(func() { | ||
if !f.RequireMatchedServerVersion { | ||
return | ||
} | ||
discoveryClient, err := f.Delegate.ToDiscoveryClient() | ||
if err != nil { | ||
f.matchesServerVersionErr = err | ||
return | ||
} | ||
f.matchesServerVersionErr = discovery.MatchesServerVersion(version.Get(), discoveryClient) | ||
}) | ||
|
||
return f.matchesServerVersionErr | ||
} | ||
|
||
// 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. | ||
func (f *MatchVersionFlags) ToRESTConfig() (*rest.Config, error) { | ||
if err := f.checkMatchingServerVersion(); err != nil { | ||
return nil, err | ||
} | ||
clientConfig, err := f.Delegate.ToRESTConfig() | ||
if err != nil { | ||
return nil, err | ||
} | ||
// TODO we should not have to do this. It smacks of something going wrong. | ||
setKubernetesDefaults(clientConfig) | ||
return clientConfig, nil | ||
} | ||
|
||
func (f *MatchVersionFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig { | ||
return f.Delegate.ToRawKubeConfigLoader() | ||
} | ||
|
||
func (f *MatchVersionFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { | ||
if err := f.checkMatchingServerVersion(); err != nil { | ||
return nil, err | ||
} | ||
return f.Delegate.ToDiscoveryClient() | ||
} | ||
|
||
// RESTMapper returns a mapper. | ||
func (f *MatchVersionFlags) ToRESTMapper() (meta.RESTMapper, error) { | ||
if err := f.checkMatchingServerVersion(); err != nil { | ||
return nil, err | ||
} | ||
return f.Delegate.ToRESTMapper() | ||
} | ||
|
||
func (f *MatchVersionFlags) AddFlags(flags *pflag.FlagSet) { | ||
flags.BoolVar(&f.RequireMatchedServerVersion, flagMatchBinaryVersion, f.RequireMatchedServerVersion, "Require server version to match client version") | ||
} | ||
|
||
func NewMatchVersionFlags(delegate genericclioptions.RESTClientGetter) *MatchVersionFlags { | ||
return &MatchVersionFlags{ | ||
Delegate: delegate, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package factory | ||
|
||
import ( | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
"k8s.io/apimachinery/pkg/runtime/serializer" | ||
"k8s.io/client-go/kubernetes/scheme" | ||
"k8s.io/client-go/rest" | ||
) | ||
|
||
// setKubernetesDefaults sets default values on the provided client config for accessing the | ||
// Kubernetes API or returns an error if any of the defaults are impossible or invalid. | ||
// TODO this isn't what we want. Each clientset should be setting defaults as it sees fit. | ||
func setKubernetesDefaults(config *rest.Config) error { | ||
// TODO remove this hack. This is allowing the GetOptions to be serialized. | ||
config.GroupVersion = &schema.GroupVersion{Group: "", Version: "v1"} | ||
|
||
if config.APIPath == "" { | ||
config.APIPath = "/api" | ||
} | ||
if config.NegotiatedSerializer == nil { | ||
// This codec factory ensures the resources are not converted. Therefore, resources | ||
// will not be round-tripped through internal versions. Defaulting does not happen | ||
// on the client. | ||
config.NegotiatedSerializer = &serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} | ||
} | ||
return rest.SetKubernetesDefaults(config) | ||
} |