package client import ( "context" "fmt" "sync" "time" "github.com/rancher/lasso/pkg/mapper" "github.com/rancher/lasso/pkg/scheme" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/client-go/discovery" "k8s.io/client-go/rest" ) type SharedClientFactoryOptions struct { Mapper meta.RESTMapper Scheme *runtime.Scheme } type SharedClientFactory interface { ForKind(gvk schema.GroupVersionKind) (*Client, error) ForResource(gvr schema.GroupVersionResource, namespaced bool) (*Client, error) ForResourceKind(gvr schema.GroupVersionResource, kind string, namespaced bool) *Client NewObjects(gvk schema.GroupVersionKind) (runtime.Object, runtime.Object, error) GVKForObject(obj runtime.Object) (schema.GroupVersionKind, error) GVKForResource(gvr schema.GroupVersionResource) (schema.GroupVersionKind, error) IsNamespaced(gvk schema.GroupVersionKind) (bool, error) ResourceForGVK(gvk schema.GroupVersionKind) (schema.GroupVersionResource, bool, error) IsHealthy(ctx context.Context) bool } type sharedClientFactory struct { createLock sync.RWMutex discovery discovery.DiscoveryInterface clients map[schema.GroupVersionResource]*Client timeout time.Duration rest rest.Interface Mapper meta.RESTMapper Scheme *runtime.Scheme } func NewSharedClientFactoryForConfig(config *rest.Config) (SharedClientFactory, error) { return NewSharedClientFactory(config, nil) } func NewSharedClientFactory(config *rest.Config, opts *SharedClientFactoryOptions) (_ SharedClientFactory, err error) { opts, err = applyDefaults(config, opts) if err != nil { return nil, err } config, timeout := populateConfig(opts.Scheme, config) rest, err := rest.UnversionedRESTClientFor(config) if err != nil { return nil, err } discovery, err := discovery.NewDiscoveryClientForConfig(config) if err != nil { return nil, err } return &sharedClientFactory{ timeout: timeout, clients: map[schema.GroupVersionResource]*Client{}, Scheme: opts.Scheme, Mapper: opts.Mapper, rest: rest, discovery: discovery, }, nil } func applyDefaults(config *rest.Config, opts *SharedClientFactoryOptions) (*SharedClientFactoryOptions, error) { var newOpts SharedClientFactoryOptions if opts != nil { newOpts = *opts } if newOpts.Scheme == nil { newOpts.Scheme = scheme.All } if newOpts.Mapper == nil { mapperOpt, err := mapper.New(config) if err != nil { return nil, err } newOpts.Mapper = mapperOpt } return &newOpts, nil } func (s *sharedClientFactory) GVKForResource(gvr schema.GroupVersionResource) (schema.GroupVersionKind, error) { return s.Mapper.KindFor(gvr) } func (s *sharedClientFactory) IsHealthy(ctx context.Context) bool { _, err := s.rest.Get().AbsPath("/version").Do(ctx).Raw() return err == nil } func (s *sharedClientFactory) IsNamespaced(gvk schema.GroupVersionKind) (bool, error) { mapping, err := s.Mapper.RESTMapping(gvk.GroupKind(), gvk.Version) if err != nil { return false, err } return IsNamespaced(mapping.Resource, s.Mapper) } func (s *sharedClientFactory) ResourceForGVK(gvk schema.GroupVersionKind) (schema.GroupVersionResource, bool, error) { mapping, err := s.Mapper.RESTMapping(gvk.GroupKind(), gvk.Version) if err != nil { return schema.GroupVersionResource{}, false, err } nsed, err := IsNamespaced(mapping.Resource, s.Mapper) if err != nil { return schema.GroupVersionResource{}, false, err } return mapping.Resource, nsed, nil } func (s *sharedClientFactory) GVKForObject(obj runtime.Object) (schema.GroupVersionKind, error) { gvks, _, err := s.Scheme.ObjectKinds(obj) if err != nil { return schema.GroupVersionKind{}, err } if len(gvks) == 0 { return schema.GroupVersionKind{}, fmt.Errorf("failed to find schema.GroupVersionKind for %T", obj) } return gvks[0], nil } func (s *sharedClientFactory) NewObjects(gvk schema.GroupVersionKind) (runtime.Object, runtime.Object, error) { obj, err := s.Scheme.New(gvk) if runtime.IsNotRegisteredError(err) { return &unstructured.Unstructured{}, &unstructured.UnstructuredList{}, nil } else if err != nil { return nil, nil, err } objList, err := s.Scheme.New(schema.GroupVersionKind{ Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind + "List", }) return obj, objList, err } func (s *sharedClientFactory) ForKind(gvk schema.GroupVersionKind) (*Client, error) { gvr, nsed, err := s.ResourceForGVK(gvk) if err != nil { return nil, err } return s.ForResourceKind(gvr, gvk.Kind, nsed), nil } func (s *sharedClientFactory) ForResource(gvr schema.GroupVersionResource, namespaced bool) (*Client, error) { gvk, err := s.GVKForResource(gvr) if err != nil { return nil, err } return s.ForResourceKind(gvr, gvk.Kind, namespaced), nil } func (s *sharedClientFactory) ForResourceKind(gvr schema.GroupVersionResource, kind string, namespaced bool) *Client { client := s.getClient(gvr) if client != nil { return client } s.createLock.Lock() defer s.createLock.Unlock() client = s.clients[gvr] if client != nil { return client } client = NewClient(gvr, kind, namespaced, s.rest, s.timeout) s.clients[gvr] = client return client } func (s *sharedClientFactory) getClient(gvr schema.GroupVersionResource) *Client { s.createLock.RLock() defer s.createLock.RUnlock() return s.clients[gvr] } func populateConfig(scheme *runtime.Scheme, config *rest.Config) (*rest.Config, time.Duration) { config = rest.CopyConfig(config) config.NegotiatedSerializer = unstructuredNegotiator{ NegotiatedSerializer: serializer.NewCodecFactory(scheme).WithoutConversion(), } if config.UserAgent == "" { config.UserAgent = rest.DefaultKubernetesUserAgent() } timeout := config.Timeout config.Timeout = 0 return config, timeout } type unstructuredNegotiator struct { runtime.NegotiatedSerializer } func (u unstructuredNegotiator) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { result := u.NegotiatedSerializer.DecoderToVersion(serializer, gv) return unstructuredDecoder{ Decoder: result, } } type unstructuredDecoder struct { runtime.Decoder } func (u unstructuredDecoder) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { obj, gvk, err := u.Decoder.Decode(data, defaults, into) if into == nil && runtime.IsNotRegisteredError(err) { return u.Decode(data, defaults, &unstructured.Unstructured{}) } return obj, gvk, err }