[client-go] Add dynamic.Interface

This adds an interface form of dynamic.Client and
dynamic.ResourceClient, making those two follow the general client
conventions: `Interface` is an interface, and `Client` is the concrete
implementation.  `ClientPool` retains it's interface status.

This allows us to create a fake implemenation of dyanmic.Interface,
dynamic.ResourceInterface, and dynamic.ClientPool for testing.
This commit is contained in:
Solly Ross
2017-05-05 14:25:26 -04:00
parent 6b78eeca84
commit f78d61e7c2
10 changed files with 57 additions and 23 deletions

View File

@@ -97,7 +97,7 @@ type GraphBuilder struct {
ignoredResources map[schema.GroupResource]struct{} ignoredResources map[schema.GroupResource]struct{}
} }
func listWatcher(client *dynamic.Client, resource schema.GroupVersionResource) *cache.ListWatch { func listWatcher(client dynamic.Interface, resource schema.GroupVersionResource) *cache.ListWatch {
return &cache.ListWatch{ return &cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
// APIResource.Kind is not used by the dynamic client, so // APIResource.Kind is not used by the dynamic client, so

View File

@@ -46,7 +46,7 @@ func NewRegisteredRateLimiter(resources map[schema.GroupVersionResource]struct{}
return &RegisteredRateLimiter{rateLimiters: rateLimiters} return &RegisteredRateLimiter{rateLimiters: rateLimiters}
} }
func (r *RegisteredRateLimiter) registerIfNotPresent(gv schema.GroupVersion, client *dynamic.Client, prefix string) { func (r *RegisteredRateLimiter) registerIfNotPresent(gv schema.GroupVersion, client dynamic.Interface, prefix string) {
once, found := r.rateLimiters[gv] once, found := r.rateLimiters[gv]
if !found { if !found {
return return

View File

@@ -325,7 +325,7 @@ func (d *namespacedResourcesDeleter) finalizeNamespace(namespace *v1.Namespace)
// it returns true if the operation was supported on the server. // it returns true if the operation was supported on the server.
// it returns an error if the operation was supported on the server but was unable to complete. // it returns an error if the operation was supported on the server but was unable to complete.
func (d *namespacedResourcesDeleter) deleteCollection( func (d *namespacedResourcesDeleter) deleteCollection(
dynamicClient *dynamic.Client, gvr schema.GroupVersionResource, dynamicClient dynamic.Interface, gvr schema.GroupVersionResource,
namespace string) (bool, error) { namespace string) (bool, error) {
glog.V(5).Infof("namespace controller - deleteCollection - namespace: %s, gvr: %v", namespace, gvr) glog.V(5).Infof("namespace controller - deleteCollection - namespace: %s, gvr: %v", namespace, gvr)
@@ -370,7 +370,7 @@ func (d *namespacedResourcesDeleter) deleteCollection(
// a boolean if the operation is supported // a boolean if the operation is supported
// an error if the operation is supported but could not be completed. // an error if the operation is supported but could not be completed.
func (d *namespacedResourcesDeleter) listCollection( func (d *namespacedResourcesDeleter) listCollection(
dynamicClient *dynamic.Client, gvr schema.GroupVersionResource, namespace string) (*unstructured.UnstructuredList, bool, error) { dynamicClient dynamic.Interface, gvr schema.GroupVersionResource, namespace string) (*unstructured.UnstructuredList, bool, error) {
glog.V(5).Infof("namespace controller - listCollection - namespace: %s, gvr: %v", namespace, gvr) glog.V(5).Infof("namespace controller - listCollection - namespace: %s, gvr: %v", namespace, gvr)
key := operationKey{operation: operationList, gvr: gvr} key := operationKey{operation: operationList, gvr: gvr}
@@ -406,7 +406,7 @@ func (d *namespacedResourcesDeleter) listCollection(
// deleteEachItem is a helper function that will list the collection of resources and delete each item 1 by 1. // deleteEachItem is a helper function that will list the collection of resources and delete each item 1 by 1.
func (d *namespacedResourcesDeleter) deleteEachItem( func (d *namespacedResourcesDeleter) deleteEachItem(
dynamicClient *dynamic.Client, gvr schema.GroupVersionResource, namespace string) error { dynamicClient dynamic.Interface, gvr schema.GroupVersionResource, namespace string) error {
glog.V(5).Infof("namespace controller - deleteEachItem - namespace: %s, gvr: %v", namespace, gvr) glog.V(5).Infof("namespace controller - deleteEachItem - namespace: %s, gvr: %v", namespace, gvr)
unstructuredList, listSupported, err := d.listCollection(dynamicClient, gvr, namespace) unstructuredList, listSupported, err := d.listCollection(dynamicClient, gvr, namespace)

View File

@@ -176,13 +176,13 @@ func DescriberFor(kind schema.GroupKind, c clientset.Interface) (printers.Descri
// GenericDescriberFor returns a generic describer for the specified mapping // GenericDescriberFor returns a generic describer for the specified mapping
// that uses only information available from runtime.Unstructured // that uses only information available from runtime.Unstructured
func GenericDescriberFor(mapping *meta.RESTMapping, dynamic *dynamic.Client, events coreclient.EventsGetter) printers.Describer { func GenericDescriberFor(mapping *meta.RESTMapping, dynamic dynamic.Interface, events coreclient.EventsGetter) printers.Describer {
return &genericDescriber{mapping, dynamic, events} return &genericDescriber{mapping, dynamic, events}
} }
type genericDescriber struct { type genericDescriber struct {
mapping *meta.RESTMapping mapping *meta.RESTMapping
dynamic *dynamic.Client dynamic dynamic.Interface
events coreclient.EventsGetter events coreclient.EventsGetter
} }

View File

@@ -75,7 +75,7 @@ func TestClusterScopedCRUD(t *testing.T) {
testSimpleCRUD(t, ns, noxuDefinition, noxuVersionClient) testSimpleCRUD(t, ns, noxuDefinition, noxuVersionClient)
} }
func testSimpleCRUD(t *testing.T, ns string, noxuDefinition *apiextensionsv1beta1.CustomResourceDefinition, noxuVersionClient *dynamic.Client) { func testSimpleCRUD(t *testing.T, ns string, noxuDefinition *apiextensionsv1beta1.CustomResourceDefinition, noxuVersionClient dynamic.Interface) {
noxuResourceClient := NewNamespacedCustomResourceClient(ns, noxuVersionClient, noxuDefinition) noxuResourceClient := NewNamespacedCustomResourceClient(ns, noxuVersionClient, noxuDefinition)
initialList, err := noxuResourceClient.List(metav1.ListOptions{}) initialList, err := noxuResourceClient.List(metav1.ListOptions{})
if err != nil { if err != nil {
@@ -501,7 +501,7 @@ func TestCrossNamespaceListWatch(t *testing.T) {
checkNamespacesWatchHelper(t, ns2, noxuNamespacesWatch2) checkNamespacesWatchHelper(t, ns2, noxuNamespacesWatch2)
} }
func createInstanceWithNamespaceHelper(t *testing.T, ns string, name string, noxuNamespacedResourceClient *dynamic.ResourceClient, noxuDefinition *apiextensionsv1beta1.CustomResourceDefinition) *unstructured.Unstructured { func createInstanceWithNamespaceHelper(t *testing.T, ns string, name string, noxuNamespacedResourceClient dynamic.ResourceInterface, noxuDefinition *apiextensionsv1beta1.CustomResourceDefinition) *unstructured.Unstructured {
createdInstance, err := instantiateCustomResource(t, testserver.NewNoxuInstance(ns, name), noxuNamespacedResourceClient, noxuDefinition) createdInstance, err := instantiateCustomResource(t, testserver.NewNoxuInstance(ns, name), noxuNamespacedResourceClient, noxuDefinition)
if err != nil { if err != nil {
t.Fatalf("unable to create noxu Instance:%v", err) t.Fatalf("unable to create noxu Instance:%v", err)

View File

@@ -39,7 +39,7 @@ import (
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
) )
func instantiateCustomResource(t *testing.T, instanceToCreate *unstructured.Unstructured, client *dynamic.ResourceClient, definition *apiextensionsv1beta1.CustomResourceDefinition) (*unstructured.Unstructured, error) { func instantiateCustomResource(t *testing.T, instanceToCreate *unstructured.Unstructured, client dynamic.ResourceInterface, definition *apiextensionsv1beta1.CustomResourceDefinition) (*unstructured.Unstructured, error) {
createdInstance, err := client.Create(instanceToCreate) createdInstance, err := client.Create(instanceToCreate)
if err != nil { if err != nil {
t.Logf("%#v", createdInstance) t.Logf("%#v", createdInstance)
@@ -66,7 +66,7 @@ func instantiateCustomResource(t *testing.T, instanceToCreate *unstructured.Unst
return createdInstance, nil return createdInstance, nil
} }
func NewNamespacedCustomResourceClient(ns string, client *dynamic.Client, definition *apiextensionsv1beta1.CustomResourceDefinition) *dynamic.ResourceClient { func NewNamespacedCustomResourceClient(ns string, client dynamic.Interface, definition *apiextensionsv1beta1.CustomResourceDefinition) dynamic.ResourceInterface {
return client.Resource(&metav1.APIResource{ return client.Resource(&metav1.APIResource{
Name: definition.Spec.Names.Plural, Name: definition.Spec.Names.Plural,
Namespaced: definition.Spec.Scope == apiextensionsv1beta1.NamespaceScoped, Namespaced: definition.Spec.Scope == apiextensionsv1beta1.NamespaceScoped,

View File

@@ -141,7 +141,7 @@ func NewCurletInstance(namespace, name string) *unstructured.Unstructured {
} }
} }
func CreateNewCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, clientPool dynamic.ClientPool) (*dynamic.Client, error) { func CreateNewCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, clientPool dynamic.ClientPool) (dynamic.Interface, error) {
_, err := apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Create(crd) _, err := apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Create(crd)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -194,7 +194,7 @@ func CreateNewCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceD
return dynamicClient, nil return dynamicClient, nil
} }
func checkForWatchCachePrimed(crd *apiextensionsv1beta1.CustomResourceDefinition, dynamicClient *dynamic.Client) error { func checkForWatchCachePrimed(crd *apiextensionsv1beta1.CustomResourceDefinition, dynamicClient dynamic.Interface) error {
ns := "" ns := ""
if crd.Spec.Scope != apiextensionsv1beta1.ClusterScoped { if crd.Spec.Scope != apiextensionsv1beta1.ClusterScoped {
ns = "aval" ns = "aval"

View File

@@ -40,8 +40,42 @@ import (
"k8s.io/client-go/util/flowcontrol" "k8s.io/client-go/util/flowcontrol"
) )
// Client is a Kubernetes client that allows you to access metadata // Interface is a Kubernetes client that allows you to access metadata
// and manipulate metadata of a Kubernetes API group. // and manipulate metadata of a Kubernetes API group.
type Interface interface {
// GetRateLimiter returns the rate limiter for this client.
GetRateLimiter() flowcontrol.RateLimiter
// Resource returns an API interface to the specified resource for this client's
// group and version. If resource is not a namespaced resource, then namespace
// is ignored. The ResourceInterface inherits the paramater codec of this client.
Resource(resource *metav1.APIResource, namespace string) ResourceInterface
// ParameterCodec returns a client with the provided parameter codec.
ParameterCodec(parameterCodec runtime.ParameterCodec) Interface
}
// ResourceInterface is an API interface to a specific resource under a
// dynamic client.
type ResourceInterface interface {
// List returns a list of objects for this resource.
List(opts metav1.ListOptions) (runtime.Object, error)
// Get gets the resource with the specified name.
Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error)
// Delete deletes the resource with the specified name.
Delete(name string, opts *metav1.DeleteOptions) error
// DeleteCollection deletes a collection of objects.
DeleteCollection(deleteOptions *metav1.DeleteOptions, listOptions metav1.ListOptions) error
// Create creates the provided resource.
Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error)
// Update updates the provided resource.
Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error)
// Watch returns a watch.Interface that watches the resource.
Watch(opts metav1.ListOptions) (watch.Interface, error)
// Patch patches the provided resource.
Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error)
}
// Client is a Kubernetes client that allows you to access metadata
// and manipulate metadata of a Kubernetes API group, and implements Interface.
type Client struct { type Client struct {
cl *restclient.RESTClient cl *restclient.RESTClient
parameterCodec runtime.ParameterCodec parameterCodec runtime.ParameterCodec
@@ -84,8 +118,8 @@ func (c *Client) GetRateLimiter() flowcontrol.RateLimiter {
// Resource returns an API interface to the specified resource for this client's // Resource returns an API interface to the specified resource for this client's
// group and version. If resource is not a namespaced resource, then namespace // group and version. If resource is not a namespaced resource, then namespace
// is ignored. The ResourceClient inherits the parameter codec of c. // is ignored. The ResourceInterface inherits the parameter codec of c.
func (c *Client) Resource(resource *metav1.APIResource, namespace string) *ResourceClient { func (c *Client) Resource(resource *metav1.APIResource, namespace string) ResourceInterface {
return &ResourceClient{ return &ResourceClient{
cl: c.cl, cl: c.cl,
resource: resource, resource: resource,
@@ -95,7 +129,7 @@ func (c *Client) Resource(resource *metav1.APIResource, namespace string) *Resou
} }
// ParameterCodec returns a client with the provided parameter codec. // ParameterCodec returns a client with the provided parameter codec.
func (c *Client) ParameterCodec(parameterCodec runtime.ParameterCodec) *Client { func (c *Client) ParameterCodec(parameterCodec runtime.ParameterCodec) Interface {
return &Client{ return &Client{
cl: c.cl, cl: c.cl,
parameterCodec: parameterCodec, parameterCodec: parameterCodec,
@@ -103,7 +137,7 @@ func (c *Client) ParameterCodec(parameterCodec runtime.ParameterCodec) *Client {
} }
// ResourceClient is an API interface to a specific resource under a // ResourceClient is an API interface to a specific resource under a
// dynamic client. // dynamic client, and implements ResourceInterface.
type ResourceClient struct { type ResourceClient struct {
cl *restclient.RESTClient cl *restclient.RESTClient
resource *metav1.APIResource resource *metav1.APIResource

View File

@@ -28,10 +28,10 @@ import (
type ClientPool interface { type ClientPool interface {
// ClientForGroupVersionKind returns a client configured for the specified groupVersionResource. // ClientForGroupVersionKind returns a client configured for the specified groupVersionResource.
// Resource may be empty. // Resource may be empty.
ClientForGroupVersionResource(resource schema.GroupVersionResource) (*Client, error) ClientForGroupVersionResource(resource schema.GroupVersionResource) (Interface, error)
// ClientForGroupVersionKind returns a client configured for the specified groupVersionKind. // ClientForGroupVersionKind returns a client configured for the specified groupVersionKind.
// Kind may be empty. // Kind may be empty.
ClientForGroupVersionKind(kind schema.GroupVersionKind) (*Client, error) ClientForGroupVersionKind(kind schema.GroupVersionKind) (Interface, error)
} }
// APIPathResolverFunc knows how to convert a groupVersion to its API path. The Kind field is // APIPathResolverFunc knows how to convert a groupVersion to its API path. The Kind field is
@@ -79,7 +79,7 @@ func NewDynamicClientPool(cfg *restclient.Config) ClientPool {
// ClientForGroupVersionResource uses the provided RESTMapper to identify the appropriate resource. Resource may // ClientForGroupVersionResource uses the provided RESTMapper to identify the appropriate resource. Resource may
// be empty. If no matching kind is found the underlying client for that group is still returned. // be empty. If no matching kind is found the underlying client for that group is still returned.
func (c *clientPoolImpl) ClientForGroupVersionResource(resource schema.GroupVersionResource) (*Client, error) { func (c *clientPoolImpl) ClientForGroupVersionResource(resource schema.GroupVersionResource) (Interface, error) {
kinds, err := c.mapper.KindsFor(resource) kinds, err := c.mapper.KindsFor(resource)
if err != nil { if err != nil {
if meta.IsNoMatchError(err) { if meta.IsNoMatchError(err) {
@@ -92,7 +92,7 @@ func (c *clientPoolImpl) ClientForGroupVersionResource(resource schema.GroupVers
// ClientForGroupVersion returns a client for the specified groupVersion, creates one if none exists. Kind // ClientForGroupVersion returns a client for the specified groupVersion, creates one if none exists. Kind
// in the GroupVersionKind may be empty. // in the GroupVersionKind may be empty.
func (c *clientPoolImpl) ClientForGroupVersionKind(kind schema.GroupVersionKind) (*Client, error) { func (c *clientPoolImpl) ClientForGroupVersionKind(kind schema.GroupVersionKind) (Interface, error) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()

View File

@@ -58,7 +58,7 @@ func getObject(version, kind, name string) *unstructured.Unstructured {
} }
} }
func getClientServer(gv *schema.GroupVersion, h func(http.ResponseWriter, *http.Request)) (*Client, *httptest.Server, error) { func getClientServer(gv *schema.GroupVersion, h func(http.ResponseWriter, *http.Request)) (Interface, *httptest.Server, error) {
srv := httptest.NewServer(http.HandlerFunc(h)) srv := httptest.NewServer(http.HandlerFunc(h))
cl, err := NewClient(&restclient.Config{ cl, err := NewClient(&restclient.Config{
Host: srv.URL, Host: srv.URL,