From 77541e5cb7c30d58b052f8c43a64a54d53cd5514 Mon Sep 17 00:00:00 2001 From: David Eads Date: Wed, 9 May 2018 12:52:12 -0400 Subject: [PATCH 1/3] add subresource support for the dynamic client Kubernetes-commit: 82e32d2a3260807237a69bc40fb371db640d26fc --- dynamic/client_test.go | 210 +++++++++++++++++++++++------------------ dynamic/fake/simple.go | 199 +++++++++++++++++++++++++++++++------- dynamic/simple.go | 77 +++++---------- testing/actions.go | 21 +++++ 4 files changed, 326 insertions(+), 181 deletions(-) diff --git a/dynamic/client_test.go b/dynamic/client_test.go index 3cc6e80c..3e116cb8 100644 --- a/dynamic/client_test.go +++ b/dynamic/client_test.go @@ -58,12 +58,11 @@ func getObject(version, kind, name string) *unstructured.Unstructured { } } -func getClientServer(gv *schema.GroupVersion, h func(http.ResponseWriter, *http.Request)) (Interface, *httptest.Server, error) { +func getClientServer(h func(http.ResponseWriter, *http.Request)) (DynamicInterface, *httptest.Server, error) { srv := httptest.NewServer(http.HandlerFunc(h)) - cl, err := NewClient(&restclient.Config{ - Host: srv.URL, - ContentConfig: restclient.ContentConfig{GroupVersion: gv}, - }, *gv) + cl, err := NewForConfig(&restclient.Config{ + Host: srv.URL, + }) if err != nil { srv.Close() return nil, nil, err @@ -116,9 +115,8 @@ func TestList(t *testing.T) { }, } for _, tc := range tcs { - gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} - resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} - cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"} + cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { t.Errorf("List(%q) got HTTP method %s. wanted GET", tc.name, r.Method) } @@ -136,7 +134,7 @@ func TestList(t *testing.T) { } defer srv.Close() - got, err := cl.Resource(resource, tc.namespace).List(metav1.ListOptions{}) + got, err := cl.Resource(resource).Namespace(tc.namespace).List(metav1.ListOptions{}) if err != nil { t.Errorf("unexpected error when listing %q: %v", tc.name, err) continue @@ -150,12 +148,13 @@ func TestList(t *testing.T) { func TestGet(t *testing.T) { tcs := []struct { - resource string - namespace string - name string - path string - resp []byte - want *unstructured.Unstructured + resource string + subresource []string + namespace string + name string + path string + resp []byte + want *unstructured.Unstructured }{ { resource: "rtest", @@ -173,25 +172,26 @@ func TestGet(t *testing.T) { want: getObject("vTest", "rTest", "namespaced_get"), }, { - resource: "rtest/srtest", - name: "normal_subresource_get", - path: "/apis/gtest/vtest/rtest/normal_subresource_get/srtest", - resp: getJSON("vTest", "srTest", "normal_subresource_get"), - want: getObject("vTest", "srTest", "normal_subresource_get"), + resource: "rtest", + subresource: []string{"srtest"}, + name: "normal_subresource_get", + path: "/apis/gtest/vtest/rtest/normal_subresource_get/srtest", + resp: getJSON("vTest", "srTest", "normal_subresource_get"), + want: getObject("vTest", "srTest", "normal_subresource_get"), }, { - resource: "rtest/srtest", - namespace: "nstest", - name: "namespaced_subresource_get", - path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_get/srtest", - resp: getJSON("vTest", "srTest", "namespaced_subresource_get"), - want: getObject("vTest", "srTest", "namespaced_subresource_get"), + resource: "rtest", + subresource: []string{"srtest"}, + namespace: "nstest", + name: "namespaced_subresource_get", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_get/srtest", + resp: getJSON("vTest", "srTest", "namespaced_subresource_get"), + want: getObject("vTest", "srTest", "namespaced_subresource_get"), }, } for _, tc := range tcs { - gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} - resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0} - cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: tc.resource} + cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { t.Errorf("Get(%q) got HTTP method %s. wanted GET", tc.name, r.Method) } @@ -209,7 +209,7 @@ func TestGet(t *testing.T) { } defer srv.Close() - got, err := cl.Resource(resource, tc.namespace).Get(tc.name, metav1.GetOptions{}) + got, err := cl.Resource(resource).Namespace(tc.namespace).Get(tc.name, metav1.GetOptions{}, tc.subresource...) if err != nil { t.Errorf("unexpected error when getting %q: %v", tc.name, err) continue @@ -230,6 +230,7 @@ func TestDelete(t *testing.T) { Status: metav1.StatusSuccess, } tcs := []struct { + subresource []string namespace string name string path string @@ -244,6 +245,17 @@ func TestDelete(t *testing.T) { name: "namespaced_delete", path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete", }, + { + subresource: []string{"srtest"}, + name: "normal_delete", + path: "/apis/gtest/vtest/rtest/normal_delete/srtest", + }, + { + subresource: []string{"srtest"}, + namespace: "nstest", + name: "namespaced_delete", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete/srtest", + }, { namespace: "nstest", name: "namespaced_delete_with_options", @@ -252,9 +264,8 @@ func TestDelete(t *testing.T) { }, } for _, tc := range tcs { - gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} - resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} - cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"} + cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) { if r.Method != "DELETE" { t.Errorf("Delete(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method) } @@ -272,7 +283,7 @@ func TestDelete(t *testing.T) { } defer srv.Close() - err = cl.Resource(resource, tc.namespace).Delete(tc.name, tc.deleteOptions) + err = cl.Resource(resource).Namespace(tc.namespace).Delete(tc.name, tc.deleteOptions, tc.subresource...) if err != nil { t.Errorf("unexpected error when deleting %q: %v", tc.name, err) continue @@ -301,9 +312,8 @@ func TestDeleteCollection(t *testing.T) { }, } for _, tc := range tcs { - gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} - resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} - cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"} + cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) { if r.Method != "DELETE" { t.Errorf("DeleteCollection(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method) } @@ -321,7 +331,7 @@ func TestDeleteCollection(t *testing.T) { } defer srv.Close() - err = cl.Resource(resource, tc.namespace).DeleteCollection(nil, metav1.ListOptions{}) + err = cl.Resource(resource).Namespace(tc.namespace).DeleteCollection(nil, metav1.ListOptions{}) if err != nil { t.Errorf("unexpected error when deleting collection %q: %v", tc.name, err) continue @@ -331,11 +341,12 @@ func TestDeleteCollection(t *testing.T) { func TestCreate(t *testing.T) { tcs := []struct { - resource string - name string - namespace string - obj *unstructured.Unstructured - path string + resource string + subresource []string + name string + namespace string + obj *unstructured.Unstructured + path string }{ { resource: "rtest", @@ -350,11 +361,25 @@ func TestCreate(t *testing.T) { path: "/apis/gtest/vtest/namespaces/nstest/rtest", obj: getObject("gtest/vTest", "rTest", "namespaced_create"), }, + { + resource: "rtest", + subresource: []string{"srtest"}, + name: "normal_subresource_create", + path: "/apis/gtest/vtest/rtest/normal_subresource_create/srtest", + obj: getObject("vTest", "srTest", "normal_subresource_create"), + }, + { + resource: "rtest/", + subresource: []string{"srtest"}, + name: "namespaced_subresource_create", + namespace: "nstest", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_create/srtest", + obj: getObject("vTest", "srTest", "namespaced_subresource_create"), + }, } for _, tc := range tcs { - gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} - resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0} - cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: tc.resource} + cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { t.Errorf("Create(%q) got HTTP method %s. wanted POST", tc.name, r.Method) } @@ -379,7 +404,7 @@ func TestCreate(t *testing.T) { } defer srv.Close() - got, err := cl.Resource(resource, tc.namespace).Create(tc.obj) + got, err := cl.Resource(resource).Namespace(tc.namespace).Create(tc.obj, tc.subresource...) if err != nil { t.Errorf("unexpected error when creating %q: %v", tc.name, err) continue @@ -393,11 +418,12 @@ func TestCreate(t *testing.T) { func TestUpdate(t *testing.T) { tcs := []struct { - resource string - name string - namespace string - obj *unstructured.Unstructured - path string + resource string + subresource []string + name string + namespace string + obj *unstructured.Unstructured + path string }{ { resource: "rtest", @@ -413,23 +439,24 @@ func TestUpdate(t *testing.T) { obj: getObject("gtest/vTest", "rTest", "namespaced_update"), }, { - resource: "rtest/srtest", - name: "normal_subresource_update", - path: "/apis/gtest/vtest/rtest/normal_update/srtest", - obj: getObject("gtest/vTest", "srTest", "normal_update"), + resource: "rtest", + subresource: []string{"srtest"}, + name: "normal_subresource_update", + path: "/apis/gtest/vtest/rtest/normal_update/srtest", + obj: getObject("gtest/vTest", "srTest", "normal_update"), }, { - resource: "rtest/srtest", - name: "namespaced_subresource_update", - namespace: "nstest", - path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_update/srtest", - obj: getObject("gtest/vTest", "srTest", "namespaced_update"), + resource: "rtest", + subresource: []string{"srtest"}, + name: "namespaced_subresource_update", + namespace: "nstest", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_update/srtest", + obj: getObject("gtest/vTest", "srTest", "namespaced_update"), }, } for _, tc := range tcs { - gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} - resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0} - cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: tc.resource} + cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) { if r.Method != "PUT" { t.Errorf("Update(%q) got HTTP method %s. wanted PUT", tc.name, r.Method) } @@ -454,7 +481,7 @@ func TestUpdate(t *testing.T) { } defer srv.Close() - got, err := cl.Resource(resource, tc.namespace).Update(tc.obj) + got, err := cl.Resource(resource).Namespace(tc.namespace).Update(tc.obj, tc.subresource...) if err != nil { t.Errorf("unexpected error when updating %q: %v", tc.name, err) continue @@ -497,9 +524,8 @@ func TestWatch(t *testing.T) { }, } for _, tc := range tcs { - gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} - resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} - cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"} + cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { t.Errorf("Watch(%q) got HTTP method %s. wanted GET", tc.name, r.Method) } @@ -511,7 +537,7 @@ func TestWatch(t *testing.T) { t.Errorf("Watch(%q) got query %s. wanted %s", tc.name, r.URL.RawQuery, tc.query) } - enc := restclientwatch.NewEncoder(streaming.NewEncoder(w, dynamicCodec{}), dynamicCodec{}) + enc := restclientwatch.NewEncoder(streaming.NewEncoder(w, unstructured.UnstructuredJSONScheme), unstructured.UnstructuredJSONScheme) for _, e := range tc.events { enc.Encode(&e) } @@ -522,7 +548,7 @@ func TestWatch(t *testing.T) { } defer srv.Close() - watcher, err := cl.Resource(resource, tc.namespace).Watch(metav1.ListOptions{}) + watcher, err := cl.Resource(resource).Namespace(tc.namespace).Watch(metav1.ListOptions{}) if err != nil { t.Errorf("unexpected error when watching %q: %v", tc.name, err) continue @@ -539,12 +565,13 @@ func TestWatch(t *testing.T) { func TestPatch(t *testing.T) { tcs := []struct { - resource string - name string - namespace string - patch []byte - want *unstructured.Unstructured - path string + resource string + subresource []string + name string + namespace string + patch []byte + want *unstructured.Unstructured + path string }{ { resource: "rtest", @@ -562,25 +589,26 @@ func TestPatch(t *testing.T) { want: getObject("gtest/vTest", "rTest", "namespaced_patch"), }, { - resource: "rtest/srtest", - name: "normal_subresource_patch", - path: "/apis/gtest/vtest/rtest/normal_subresource_patch/srtest", - patch: getJSON("gtest/vTest", "srTest", "normal_subresource_patch"), - want: getObject("gtest/vTest", "srTest", "normal_subresource_patch"), + resource: "rtest", + subresource: []string{"srtest"}, + name: "normal_subresource_patch", + path: "/apis/gtest/vtest/rtest/normal_subresource_patch/srtest", + patch: getJSON("gtest/vTest", "srTest", "normal_subresource_patch"), + want: getObject("gtest/vTest", "srTest", "normal_subresource_patch"), }, { - resource: "rtest/srtest", - name: "namespaced_subresource_patch", - namespace: "nstest", - path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_patch/srtest", - patch: getJSON("gtest/vTest", "srTest", "namespaced_subresource_patch"), - want: getObject("gtest/vTest", "srTest", "namespaced_subresource_patch"), + resource: "rtest", + subresource: []string{"srtest"}, + name: "namespaced_subresource_patch", + namespace: "nstest", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_patch/srtest", + patch: getJSON("gtest/vTest", "srTest", "namespaced_subresource_patch"), + want: getObject("gtest/vTest", "srTest", "namespaced_subresource_patch"), }, } for _, tc := range tcs { - gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} - resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0} - cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: tc.resource} + cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) { if r.Method != "PATCH" { t.Errorf("Patch(%q) got HTTP method %s. wanted PATCH", tc.name, r.Method) } @@ -610,7 +638,7 @@ func TestPatch(t *testing.T) { } defer srv.Close() - got, err := cl.Resource(resource, tc.namespace).Patch(tc.name, types.StrategicMergePatchType, tc.patch) + got, err := cl.Resource(resource).Namespace(tc.namespace).Patch(tc.name, types.StrategicMergePatchType, tc.patch, tc.subresource...) if err != nil { t.Errorf("unexpected error when patching %q: %v", tc.name, err) continue diff --git a/dynamic/fake/simple.go b/dynamic/fake/simple.go index 9aafb81a..20b7b3c2 100644 --- a/dynamic/fake/simple.go +++ b/dynamic/fake/simple.go @@ -17,6 +17,8 @@ limitations under the License. package fake import ( + "strings" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -63,10 +65,9 @@ type FakeDynamicClient struct { } type dynamicResourceClient struct { - client *FakeDynamicClient - namespace string - resource schema.GroupVersionResource - subresource string + client *FakeDynamicClient + namespace string + resource schema.GroupVersionResource } var _ dynamic.DynamicInterface = &FakeDynamicClient{} @@ -75,25 +76,43 @@ func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynam return &dynamicResourceClient{client: c, resource: resource} } -// Deprecated, this isn't how we want to do it -func (c *FakeDynamicClient) ClusterSubresource(resource schema.GroupVersionResource, subresource string) dynamic.DynamicResourceInterface { - return &dynamicResourceClient{client: c, resource: resource, subresource: subresource} -} - -// Deprecated, this isn't how we want to do it -func (c *FakeDynamicClient) NamespacedSubresource(resource schema.GroupVersionResource, subresource, namespace string) dynamic.DynamicResourceInterface { - return &dynamicResourceClient{client: c, resource: resource, namespace: namespace, subresource: subresource} -} - func (c *dynamicResourceClient) Namespace(ns string) dynamic.DynamicResourceInterface { ret := *c ret.namespace = ns return &ret } -func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - uncastRet, err := c.client.Fake. - Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj) +func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootCreateAction(c.resource, obj), obj) + + case len(c.namespace) == 0 && len(subresources) > 0: + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + name := accessor.GetName() + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), obj), obj) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj) + + case len(c.namespace) > 0 && len(subresources) > 0: + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + name := accessor.GetName() + uncastRet, err = c.client.Fake. + Invokes(testing.NewCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), c.namespace, obj), obj) + + } if err != nil { return nil, err @@ -109,9 +128,27 @@ func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured) (*unstruc return ret, err } -func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - uncastRet, err := c.client.Fake. - Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj) +func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootUpdateAction(c.resource, obj), obj) + + case len(c.namespace) == 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), obj), obj) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj) + + case len(c.namespace) > 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, obj), obj) + + } if err != nil { return nil, err @@ -128,8 +165,18 @@ func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured) (*unstruc } func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - uncastRet, err := c.client.Fake. - Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj) + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(c.resource, "status", obj), obj) + + case len(c.namespace) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj) + + } if err != nil { return nil, err @@ -145,23 +192,65 @@ func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured) (*u return ret, err } -func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions) error { - _, err := c.client.Fake. - Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic delete fail"}) +func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error { + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + _, err = c.client.Fake. + Invokes(testing.NewRootDeleteAction(c.resource, name), &metav1.Status{Status: "dynamic delete fail"}) + + case len(c.namespace) == 0 && len(subresources) > 0: + _, err = c.client.Fake. + Invokes(testing.NewRootDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic delete fail"}) + + case len(c.namespace) > 0 && len(subresources) == 0: + _, err = c.client.Fake. + Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic delete fail"}) + + case len(c.namespace) > 0 && len(subresources) > 0: + _, err = c.client.Fake. + Invokes(testing.NewDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, name), &metav1.Status{Status: "dynamic delete fail"}) + } return err } func (c *dynamicResourceClient) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error { - action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions) + var err error + switch { + case len(c.namespace) == 0: + action := testing.NewRootDeleteCollectionAction(c.resource, listOptions) + _, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"}) + + case len(c.namespace) > 0: + action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions) + _, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"}) + + } - _, err := c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"}) return err } -func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) { - uncastRet, err := c.client.Fake. - Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic get fail"}) +func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootGetAction(c.resource, name), &metav1.Status{Status: "dynamic get fail"}) + + case len(c.namespace) == 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootGetSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"}) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic get fail"}) + + case len(c.namespace) > 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewGetSubresourceAction(c.resource, c.namespace, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"}) + } if err != nil { return nil, err @@ -178,8 +267,18 @@ func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions) (*unstr } func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { - obj, err := c.client.Fake. - Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Version: "v1", Kind: "List"}, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"}) + var obj runtime.Object + var err error + switch { + case len(c.namespace) == 0: + obj, err = c.client.Fake. + Invokes(testing.NewRootListAction(c.resource, schema.GroupVersionKind{Version: "v1", Kind: "List"}, opts), &metav1.Status{Status: "dynamic list fail"}) + + case len(c.namespace) > 0: + obj, err = c.client.Fake. + Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Version: "v1", Kind: "List"}, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"}) + + } if obj == nil { return nil, err @@ -213,13 +312,41 @@ func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.Uns } func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { - return c.client.Fake. - InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts)) + switch { + case len(c.namespace) == 0: + return c.client.Fake. + InvokesWatch(testing.NewRootWatchAction(c.resource, opts)) + + case len(c.namespace) > 0: + return c.client.Fake. + InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts)) + + } + + panic("math broke") } func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*unstructured.Unstructured, error) { - uncastRet, err := c.client.Fake. - Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, data, subresources...), &metav1.Status{Status: "dynamic patch fail"}) + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootPatchAction(c.resource, name, data), &metav1.Status{Status: "dynamic patch fail"}) + + case len(c.namespace) == 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, data, subresources...), &metav1.Status{Status: "dynamic patch fail"}) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewPatchAction(c.resource, c.namespace, name, data), &metav1.Status{Status: "dynamic patch fail"}) + + case len(c.namespace) > 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, data, subresources...), &metav1.Status{Status: "dynamic patch fail"}) + + } if err != nil { return nil, err diff --git a/dynamic/simple.go b/dynamic/simple.go index 396ff876..e87d99d7 100644 --- a/dynamic/simple.go +++ b/dynamic/simple.go @@ -17,7 +17,6 @@ limitations under the License. package dynamic import ( - "fmt" "io" "k8s.io/apimachinery/pkg/api/meta" @@ -33,20 +32,15 @@ import ( type DynamicInterface interface { Resource(resource schema.GroupVersionResource) NamespaceableDynamicResourceInterface - - // Deprecated, this isn't how we want to do it - ClusterSubresource(resource schema.GroupVersionResource, subresource string) DynamicResourceInterface - // Deprecated, this isn't how we want to do it - NamespacedSubresource(resource schema.GroupVersionResource, subresource, namespace string) DynamicResourceInterface } type DynamicResourceInterface interface { - Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) - Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) + Create(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) + Update(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) UpdateStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) - Delete(name string, options *metav1.DeleteOptions) error + Delete(name string, options *metav1.DeleteOptions, subresources ...string) error DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error - Get(name string, options metav1.GetOptions) (*unstructured.Unstructured, error) + Get(name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) Watch(opts metav1.ListOptions) (watch.Interface, error) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*unstructured.Unstructured, error) @@ -84,40 +78,36 @@ func NewForConfig(inConfig *rest.Config) (DynamicInterface, error) { } type dynamicResourceClient struct { - client *dynamicClient - namespace string - resource schema.GroupVersionResource - subresource string + client *dynamicClient + namespace string + resource schema.GroupVersionResource } func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableDynamicResourceInterface { return &dynamicResourceClient{client: c, resource: resource} } -func (c *dynamicClient) ClusterSubresource(resource schema.GroupVersionResource, subresource string) DynamicResourceInterface { - return &dynamicResourceClient{client: c, resource: resource, subresource: subresource} -} -func (c *dynamicClient) NamespacedSubresource(resource schema.GroupVersionResource, subresource, namespace string) DynamicResourceInterface { - return &dynamicResourceClient{client: c, resource: resource, namespace: namespace, subresource: subresource} -} - func (c *dynamicResourceClient) Namespace(ns string) DynamicResourceInterface { ret := *c ret.namespace = ns return &ret } -func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - if len(c.subresource) > 0 { - return nil, fmt.Errorf("create not supported for subresources") - } - +func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) { outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) if err != nil { return nil, err } + name := "" + if len(subresources) > 0 { + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + name = accessor.GetName() + } - result := c.client.client.Post().AbsPath(c.makeURLSegments("")...).Body(outBytes).Do() + result := c.client.client.Post().AbsPath(append(c.makeURLSegments(name), subresources...)...).Body(outBytes).Do() if err := result.Error(); err != nil { return nil, err } @@ -133,7 +123,7 @@ func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured) (*unstruc return uncastObj.(*unstructured.Unstructured), nil } -func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { +func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) { accessor, err := meta.Accessor(obj) if err != nil { return nil, err @@ -143,7 +133,7 @@ func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured) (*unstruc return nil, err } - result := c.client.client.Put().AbsPath(c.makeURLSegments(accessor.GetName())...).Body(outBytes).Do() + result := c.client.client.Put().AbsPath(append(c.makeURLSegments(accessor.GetName()), subresources...)...).Body(outBytes).Do() if err := result.Error(); err != nil { return nil, err } @@ -182,7 +172,7 @@ func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured) (*u return uncastObj.(*unstructured.Unstructured), nil } -func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions) error { +func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error { if opts == nil { opts = &metav1.DeleteOptions{} } @@ -191,15 +181,11 @@ func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions) return err } - result := c.client.client.Delete().AbsPath(c.makeURLSegments(name)...).Body(deleteOptionsByte).Do() + result := c.client.client.Delete().AbsPath(append(c.makeURLSegments(name), subresources...)...).Body(deleteOptionsByte).Do() return result.Error() } func (c *dynamicResourceClient) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error { - if len(c.subresource) > 0 { - return fmt.Errorf("deletecollection not supported for subresources") - } - if opts == nil { opts = &metav1.DeleteOptions{} } @@ -212,8 +198,8 @@ func (c *dynamicResourceClient) DeleteCollection(opts *metav1.DeleteOptions, lis return result.Error() } -func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) { - result := c.client.client.Get().AbsPath(c.makeURLSegments(name)...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do() +func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) { + result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do() if err := result.Error(); err != nil { return nil, err } @@ -229,10 +215,6 @@ func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions) (*unstr } func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { - if len(c.subresource) > 0 { - return nil, fmt.Errorf("list not supported for subresources") - } - result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do() if err := result.Error(); err != nil { return nil, err @@ -257,10 +239,6 @@ func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.Uns } func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { - if len(c.subresource) > 0 { - return nil, fmt.Errorf("watch not supported for subresources") - } - internalGV := schema.GroupVersions{ {Group: c.resource.Group, Version: runtime.APIVersionInternal}, // always include the legacy group as a decoding target to handle non-error `Status` return types @@ -320,15 +298,6 @@ func (c *dynamicResourceClient) makeURLSegments(name string) []string { if len(name) > 0 { url = append(url, name) - - // subresources only work on things with names - if len(c.subresource) > 0 { - url = append(url, c.subresource) - } - } else { - if len(c.subresource) > 0 { - panic("somehow snuck a subresource and an empty name. programmer error") - } } return url diff --git a/testing/actions.go b/testing/actions.go index 232cf21e..b99f231c 100644 --- a/testing/actions.go +++ b/testing/actions.go @@ -225,6 +225,16 @@ func NewRootDeleteAction(resource schema.GroupVersionResource, name string) Dele return action } +func NewRootDeleteSubresourceAction(resource schema.GroupVersionResource, subresource string, name string) DeleteActionImpl { + action := DeleteActionImpl{} + action.Verb = "delete" + action.Resource = resource + action.Subresource = subresource + action.Name = name + + return action +} + func NewDeleteAction(resource schema.GroupVersionResource, namespace, name string) DeleteActionImpl { action := DeleteActionImpl{} action.Verb = "delete" @@ -235,6 +245,17 @@ func NewDeleteAction(resource schema.GroupVersionResource, namespace, name strin return action } +func NewDeleteSubresourceAction(resource schema.GroupVersionResource, subresource, namespace, name string) DeleteActionImpl { + action := DeleteActionImpl{} + action.Verb = "delete" + action.Resource = resource + action.Subresource = subresource + action.Namespace = namespace + action.Name = name + + return action +} + func NewRootDeleteCollectionAction(resource schema.GroupVersionResource, opts interface{}) DeleteCollectionActionImpl { action := DeleteCollectionActionImpl{} action.Verb = "delete-collection" From 1c46f3b96c52e0da5ccff6a3c1a4a2be6ea73f5a Mon Sep 17 00:00:00 2001 From: David Eads Date: Wed, 9 May 2018 12:54:24 -0400 Subject: [PATCH 2/3] move old dynamic client to deprecated-client Kubernetes-commit: d8924bc1c914c63fd7e93f7e562edfddd911d9d7 --- {dynamic => deprecated-dynamic}/bad_debt.go | 2 +- {dynamic => deprecated-dynamic}/client.go | 40 +- .../client_pool.go | 2 +- deprecated-dynamic/client_test.go | 623 ++++++++++++++++++ dynamic/dynamic_util.go | 87 --- dynamic/dynamic_util_test.go | 79 --- dynamic/fake/client.go | 163 ----- dynamic/fake/client_pool.go | 48 -- dynamic/interface.go | 34 + 9 files changed, 688 insertions(+), 390 deletions(-) rename {dynamic => deprecated-dynamic}/bad_debt.go (98%) rename {dynamic => deprecated-dynamic}/client.go (73%) rename {dynamic => deprecated-dynamic}/client_pool.go (99%) create mode 100644 deprecated-dynamic/client_test.go delete mode 100644 dynamic/dynamic_util.go delete mode 100644 dynamic/dynamic_util_test.go delete mode 100644 dynamic/fake/client.go delete mode 100644 dynamic/fake/client_pool.go create mode 100644 dynamic/interface.go diff --git a/dynamic/bad_debt.go b/deprecated-dynamic/bad_debt.go similarity index 98% rename from dynamic/bad_debt.go rename to deprecated-dynamic/bad_debt.go index 8492d56a..51e4a583 100644 --- a/dynamic/bad_debt.go +++ b/deprecated-dynamic/bad_debt.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package dynamic +package deprecated_dynamic import ( "encoding/json" diff --git a/dynamic/client.go b/deprecated-dynamic/client.go similarity index 73% rename from dynamic/client.go rename to deprecated-dynamic/client.go index 43db68c4..46c7535a 100644 --- a/dynamic/client.go +++ b/deprecated-dynamic/client.go @@ -17,7 +17,7 @@ limitations under the License. // Package dynamic provides a client interface to arbitrary Kubernetes // APIs that exposes common high level operations and exposes common // metadata. -package dynamic +package deprecated_dynamic import ( "strings" @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" restclient "k8s.io/client-go/rest" ) @@ -65,13 +66,13 @@ type ResourceInterface interface { // and manipulate metadata of a Kubernetes API group, and implements Interface. type Client struct { version schema.GroupVersion - delegate DynamicInterface + delegate dynamic.DynamicInterface } // NewClient returns a new client based on the passed in config. The // codec is ignored, as the dynamic client uses it's own codec. func NewClient(conf *restclient.Config, version schema.GroupVersion) (*Client, error) { - delegate, err := NewForConfig(conf) + delegate, err := dynamic.NewForConfig(conf) if err != nil { return nil, err } @@ -84,24 +85,41 @@ func NewClient(conf *restclient.Config, version schema.GroupVersion) (*Client, e // is ignored. The ResourceInterface inherits the parameter codec of c. func (c *Client) Resource(resource *metav1.APIResource, namespace string) ResourceInterface { resourceTokens := strings.SplitN(resource.Name, "/", 2) - subresource := "" + subresources := []string{} if len(resourceTokens) > 1 { - subresource = resourceTokens[1] + subresources = strings.Split(resourceTokens[1], "/") } if len(namespace) == 0 { - return oldResourceShim(c.delegate.ClusterSubresource(c.version.WithResource(resourceTokens[0]), subresource)) + return oldResourceShim(c.delegate.Resource(c.version.WithResource(resourceTokens[0])), subresources) } - return oldResourceShim(c.delegate.NamespacedSubresource(c.version.WithResource(resourceTokens[0]), subresource, namespace)) + return oldResourceShim(c.delegate.Resource(c.version.WithResource(resourceTokens[0])).Namespace(namespace), subresources) } // the old interfaces used the wrong type for lists. this fixes that -func oldResourceShim(in DynamicResourceInterface) ResourceInterface { - return oldResourceShimType{DynamicResourceInterface: in} +func oldResourceShim(in dynamic.DynamicResourceInterface, subresources []string) ResourceInterface { + return oldResourceShimType{DynamicResourceInterface: in, subresources: subresources} } type oldResourceShimType struct { - DynamicResourceInterface + dynamic.DynamicResourceInterface + subresources []string +} + +func (s oldResourceShimType) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + return s.DynamicResourceInterface.Create(obj, s.subresources...) +} + +func (s oldResourceShimType) Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + return s.DynamicResourceInterface.Update(obj, s.subresources...) +} + +func (s oldResourceShimType) Delete(name string, opts *metav1.DeleteOptions) error { + return s.DynamicResourceInterface.Delete(name, opts, s.subresources...) +} + +func (s oldResourceShimType) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) { + return s.DynamicResourceInterface.Get(name, opts, s.subresources...) } func (s oldResourceShimType) List(opts metav1.ListOptions) (runtime.Object, error) { @@ -109,5 +127,5 @@ func (s oldResourceShimType) List(opts metav1.ListOptions) (runtime.Object, erro } func (s oldResourceShimType) Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error) { - return s.DynamicResourceInterface.Patch(name, pt, data) + return s.DynamicResourceInterface.Patch(name, pt, data, s.subresources...) } diff --git a/dynamic/client_pool.go b/deprecated-dynamic/client_pool.go similarity index 99% rename from dynamic/client_pool.go rename to deprecated-dynamic/client_pool.go index f4d258be..36dc54ce 100644 --- a/dynamic/client_pool.go +++ b/deprecated-dynamic/client_pool.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package dynamic +package deprecated_dynamic import ( "sync" diff --git a/deprecated-dynamic/client_test.go b/deprecated-dynamic/client_test.go new file mode 100644 index 00000000..79047452 --- /dev/null +++ b/deprecated-dynamic/client_test.go @@ -0,0 +1,623 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package deprecated_dynamic + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "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/streaming" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + restclient "k8s.io/client-go/rest" + restclientwatch "k8s.io/client-go/rest/watch" +) + +func getJSON(version, kind, name string) []byte { + return []byte(fmt.Sprintf(`{"apiVersion": %q, "kind": %q, "metadata": {"name": %q}}`, version, kind, name)) +} + +func getListJSON(version, kind string, items ...[]byte) []byte { + json := fmt.Sprintf(`{"apiVersion": %q, "kind": %q, "items": [%s]}`, + version, kind, bytes.Join(items, []byte(","))) + return []byte(json) +} + +func getObject(version, kind, name string) *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": version, + "kind": kind, + "metadata": map[string]interface{}{ + "name": name, + }, + }, + } +} + +func getClientServer(gv *schema.GroupVersion, h func(http.ResponseWriter, *http.Request)) (Interface, *httptest.Server, error) { + srv := httptest.NewServer(http.HandlerFunc(h)) + cl, err := NewClient(&restclient.Config{ + Host: srv.URL, + ContentConfig: restclient.ContentConfig{GroupVersion: gv}, + }, *gv) + if err != nil { + srv.Close() + return nil, nil, err + } + return cl, srv, nil +} + +func TestList(t *testing.T) { + tcs := []struct { + name string + namespace string + path string + resp []byte + want *unstructured.UnstructuredList + }{ + { + name: "normal_list", + path: "/apis/gtest/vtest/rtest", + resp: getListJSON("vTest", "rTestList", + getJSON("vTest", "rTest", "item1"), + getJSON("vTest", "rTest", "item2")), + want: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "apiVersion": "vTest", + "kind": "rTestList", + }, + Items: []unstructured.Unstructured{ + *getObject("vTest", "rTest", "item1"), + *getObject("vTest", "rTest", "item2"), + }, + }, + }, + { + name: "namespaced_list", + namespace: "nstest", + path: "/apis/gtest/vtest/namespaces/nstest/rtest", + resp: getListJSON("vTest", "rTestList", + getJSON("vTest", "rTest", "item1"), + getJSON("vTest", "rTest", "item2")), + want: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "apiVersion": "vTest", + "kind": "rTestList", + }, + Items: []unstructured.Unstructured{ + *getObject("vTest", "rTest", "item1"), + *getObject("vTest", "rTest", "item2"), + }, + }, + }, + } + for _, tc := range tcs { + gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + t.Errorf("List(%q) got HTTP method %s. wanted GET", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("List(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + w.Header().Set("Content-Type", runtime.ContentTypeJSON) + w.Write(tc.resp) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + got, err := cl.Resource(resource, tc.namespace).List(metav1.ListOptions{}) + if err != nil { + t.Errorf("unexpected error when listing %q: %v", tc.name, err) + continue + } + + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("List(%q) want: %v\ngot: %v", tc.name, tc.want, got) + } + } +} + +func TestGet(t *testing.T) { + tcs := []struct { + resource string + namespace string + name string + path string + resp []byte + want *unstructured.Unstructured + }{ + { + resource: "rtest", + name: "normal_get", + path: "/apis/gtest/vtest/rtest/normal_get", + resp: getJSON("vTest", "rTest", "normal_get"), + want: getObject("vTest", "rTest", "normal_get"), + }, + { + resource: "rtest", + namespace: "nstest", + name: "namespaced_get", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_get", + resp: getJSON("vTest", "rTest", "namespaced_get"), + want: getObject("vTest", "rTest", "namespaced_get"), + }, + { + resource: "rtest/srtest", + name: "normal_subresource_get", + path: "/apis/gtest/vtest/rtest/normal_subresource_get/srtest", + resp: getJSON("vTest", "srTest", "normal_subresource_get"), + want: getObject("vTest", "srTest", "normal_subresource_get"), + }, + { + resource: "rtest/srtest", + namespace: "nstest", + name: "namespaced_subresource_get", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_get/srtest", + resp: getJSON("vTest", "srTest", "namespaced_subresource_get"), + want: getObject("vTest", "srTest", "namespaced_subresource_get"), + }, + } + for _, tc := range tcs { + gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + t.Errorf("Get(%q) got HTTP method %s. wanted GET", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("Get(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + w.Header().Set("Content-Type", runtime.ContentTypeJSON) + w.Write(tc.resp) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + got, err := cl.Resource(resource, tc.namespace).Get(tc.name, metav1.GetOptions{}) + if err != nil { + t.Errorf("unexpected error when getting %q: %v", tc.name, err) + continue + } + + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("Get(%q) want: %v\ngot: %v", tc.name, tc.want, got) + } + } +} + +func TestDelete(t *testing.T) { + background := metav1.DeletePropagationBackground + uid := types.UID("uid") + + statusOK := &metav1.Status{ + TypeMeta: metav1.TypeMeta{Kind: "Status"}, + Status: metav1.StatusSuccess, + } + tcs := []struct { + namespace string + name string + path string + deleteOptions *metav1.DeleteOptions + }{ + { + name: "normal_delete", + path: "/apis/gtest/vtest/rtest/normal_delete", + }, + { + namespace: "nstest", + name: "namespaced_delete", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete", + }, + { + namespace: "nstest", + name: "namespaced_delete_with_options", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete_with_options", + deleteOptions: &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &uid}, PropagationPolicy: &background}, + }, + } + for _, tc := range tcs { + gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "DELETE" { + t.Errorf("Delete(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("Delete(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + w.Header().Set("Content-Type", runtime.ContentTypeJSON) + unstructured.UnstructuredJSONScheme.Encode(statusOK, w) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + err = cl.Resource(resource, tc.namespace).Delete(tc.name, tc.deleteOptions) + if err != nil { + t.Errorf("unexpected error when deleting %q: %v", tc.name, err) + continue + } + } +} + +func TestDeleteCollection(t *testing.T) { + statusOK := &metav1.Status{ + TypeMeta: metav1.TypeMeta{Kind: "Status"}, + Status: metav1.StatusSuccess, + } + tcs := []struct { + namespace string + name string + path string + }{ + { + name: "normal_delete_collection", + path: "/apis/gtest/vtest/rtest", + }, + { + namespace: "nstest", + name: "namespaced_delete_collection", + path: "/apis/gtest/vtest/namespaces/nstest/rtest", + }, + } + for _, tc := range tcs { + gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "DELETE" { + t.Errorf("DeleteCollection(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("DeleteCollection(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + w.Header().Set("Content-Type", runtime.ContentTypeJSON) + unstructured.UnstructuredJSONScheme.Encode(statusOK, w) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + err = cl.Resource(resource, tc.namespace).DeleteCollection(nil, metav1.ListOptions{}) + if err != nil { + t.Errorf("unexpected error when deleting collection %q: %v", tc.name, err) + continue + } + } +} + +func TestCreate(t *testing.T) { + tcs := []struct { + resource string + name string + namespace string + obj *unstructured.Unstructured + path string + }{ + { + resource: "rtest", + name: "normal_create", + path: "/apis/gtest/vtest/rtest", + obj: getObject("gtest/vTest", "rTest", "normal_create"), + }, + { + resource: "rtest", + name: "namespaced_create", + namespace: "nstest", + path: "/apis/gtest/vtest/namespaces/nstest/rtest", + obj: getObject("gtest/vTest", "rTest", "namespaced_create"), + }, + } + for _, tc := range tcs { + gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + t.Errorf("Create(%q) got HTTP method %s. wanted POST", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("Create(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + w.Header().Set("Content-Type", runtime.ContentTypeJSON) + data, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Create(%q) unexpected error reading body: %v", tc.name, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Write(data) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + got, err := cl.Resource(resource, tc.namespace).Create(tc.obj) + if err != nil { + t.Errorf("unexpected error when creating %q: %v", tc.name, err) + continue + } + + if !reflect.DeepEqual(got, tc.obj) { + t.Errorf("Create(%q) want: %v\ngot: %v", tc.name, tc.obj, got) + } + } +} + +func TestUpdate(t *testing.T) { + tcs := []struct { + resource string + name string + namespace string + obj *unstructured.Unstructured + path string + }{ + { + resource: "rtest", + name: "normal_update", + path: "/apis/gtest/vtest/rtest/normal_update", + obj: getObject("gtest/vTest", "rTest", "normal_update"), + }, + { + resource: "rtest", + name: "namespaced_update", + namespace: "nstest", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_update", + obj: getObject("gtest/vTest", "rTest", "namespaced_update"), + }, + { + resource: "rtest/srtest", + name: "normal_subresource_update", + path: "/apis/gtest/vtest/rtest/normal_update/srtest", + obj: getObject("gtest/vTest", "srTest", "normal_update"), + }, + { + resource: "rtest/srtest", + name: "namespaced_subresource_update", + namespace: "nstest", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_update/srtest", + obj: getObject("gtest/vTest", "srTest", "namespaced_update"), + }, + } + for _, tc := range tcs { + gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "PUT" { + t.Errorf("Update(%q) got HTTP method %s. wanted PUT", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("Update(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + w.Header().Set("Content-Type", runtime.ContentTypeJSON) + data, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Update(%q) unexpected error reading body: %v", tc.name, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Write(data) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + got, err := cl.Resource(resource, tc.namespace).Update(tc.obj) + if err != nil { + t.Errorf("unexpected error when updating %q: %v", tc.name, err) + continue + } + + if !reflect.DeepEqual(got, tc.obj) { + t.Errorf("Update(%q) want: %v\ngot: %v", tc.name, tc.obj, got) + } + } +} + +func TestWatch(t *testing.T) { + tcs := []struct { + name string + namespace string + events []watch.Event + path string + query string + }{ + { + name: "normal_watch", + path: "/apis/gtest/vtest/rtest", + query: "watch=true", + events: []watch.Event{ + {Type: watch.Added, Object: getObject("gtest/vTest", "rTest", "normal_watch")}, + {Type: watch.Modified, Object: getObject("gtest/vTest", "rTest", "normal_watch")}, + {Type: watch.Deleted, Object: getObject("gtest/vTest", "rTest", "normal_watch")}, + }, + }, + { + name: "namespaced_watch", + namespace: "nstest", + path: "/apis/gtest/vtest/namespaces/nstest/rtest", + query: "watch=true", + events: []watch.Event{ + {Type: watch.Added, Object: getObject("gtest/vTest", "rTest", "namespaced_watch")}, + {Type: watch.Modified, Object: getObject("gtest/vTest", "rTest", "namespaced_watch")}, + {Type: watch.Deleted, Object: getObject("gtest/vTest", "rTest", "namespaced_watch")}, + }, + }, + } + for _, tc := range tcs { + gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + t.Errorf("Watch(%q) got HTTP method %s. wanted GET", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("Watch(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + if r.URL.RawQuery != tc.query { + t.Errorf("Watch(%q) got query %s. wanted %s", tc.name, r.URL.RawQuery, tc.query) + } + + enc := restclientwatch.NewEncoder(streaming.NewEncoder(w, dynamicCodec{}), dynamicCodec{}) + for _, e := range tc.events { + enc.Encode(&e) + } + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + watcher, err := cl.Resource(resource, tc.namespace).Watch(metav1.ListOptions{}) + if err != nil { + t.Errorf("unexpected error when watching %q: %v", tc.name, err) + continue + } + + for _, want := range tc.events { + got := <-watcher.ResultChan() + if !reflect.DeepEqual(got, want) { + t.Errorf("Watch(%q) want: %v\ngot: %v", tc.name, want, got) + } + } + } +} + +func TestPatch(t *testing.T) { + tcs := []struct { + resource string + name string + namespace string + patch []byte + want *unstructured.Unstructured + path string + }{ + { + resource: "rtest", + name: "normal_patch", + path: "/apis/gtest/vtest/rtest/normal_patch", + patch: getJSON("gtest/vTest", "rTest", "normal_patch"), + want: getObject("gtest/vTest", "rTest", "normal_patch"), + }, + { + resource: "rtest", + name: "namespaced_patch", + namespace: "nstest", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_patch", + patch: getJSON("gtest/vTest", "rTest", "namespaced_patch"), + want: getObject("gtest/vTest", "rTest", "namespaced_patch"), + }, + { + resource: "rtest/srtest", + name: "normal_subresource_patch", + path: "/apis/gtest/vtest/rtest/normal_subresource_patch/srtest", + patch: getJSON("gtest/vTest", "srTest", "normal_subresource_patch"), + want: getObject("gtest/vTest", "srTest", "normal_subresource_patch"), + }, + { + resource: "rtest/srtest", + name: "namespaced_subresource_patch", + namespace: "nstest", + path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_patch/srtest", + patch: getJSON("gtest/vTest", "srTest", "namespaced_subresource_patch"), + want: getObject("gtest/vTest", "srTest", "namespaced_subresource_patch"), + }, + } + for _, tc := range tcs { + gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "PATCH" { + t.Errorf("Patch(%q) got HTTP method %s. wanted PATCH", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("Patch(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + content := r.Header.Get("Content-Type") + if content != string(types.StrategicMergePatchType) { + t.Errorf("Patch(%q) got Content-Type %s. wanted %s", tc.name, content, types.StrategicMergePatchType) + } + + data, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Patch(%q) unexpected error reading body: %v", tc.name, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(data) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + got, err := cl.Resource(resource, tc.namespace).Patch(tc.name, types.StrategicMergePatchType, tc.patch) + if err != nil { + t.Errorf("unexpected error when patching %q: %v", tc.name, err) + continue + } + + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("Patch(%q) want: %v\ngot: %v", tc.name, tc.want, got) + } + } +} diff --git a/dynamic/dynamic_util.go b/dynamic/dynamic_util.go deleted file mode 100644 index 570f9f17..00000000 --- a/dynamic/dynamic_util.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package dynamic - -import ( - "fmt" - - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// NewDiscoveryRESTMapper returns a RESTMapper based on discovery information. -func NewDiscoveryRESTMapper(resources []*metav1.APIResourceList) (*meta.DefaultRESTMapper, error) { - rm := meta.NewDefaultRESTMapper(nil) - for _, resourceList := range resources { - gv, err := schema.ParseGroupVersion(resourceList.GroupVersion) - if err != nil { - return nil, err - } - - for _, resource := range resourceList.APIResources { - gvk := gv.WithKind(resource.Kind) - scope := meta.RESTScopeRoot - if resource.Namespaced { - scope = meta.RESTScopeNamespace - } - rm.Add(gvk, scope) - } - } - return rm, nil -} - -// ObjectTyper provides an ObjectTyper implementation for -// unstructured.Unstructured object based on discovery information. -type ObjectTyper struct { - registered map[schema.GroupVersionKind]bool -} - -// NewObjectTyper constructs an ObjectTyper from discovery information. -func NewObjectTyper(resources []*metav1.APIResourceList) (runtime.ObjectTyper, error) { - ot := &ObjectTyper{registered: make(map[schema.GroupVersionKind]bool)} - for _, resourceList := range resources { - gv, err := schema.ParseGroupVersion(resourceList.GroupVersion) - if err != nil { - return nil, err - } - - for _, resource := range resourceList.APIResources { - ot.registered[gv.WithKind(resource.Kind)] = true - } - } - return ot, nil -} - -// ObjectKinds returns a slice of one element with the -// group,version,kind of the provided object, or an error if the -// object is not *unstructured.Unstructured or has no group,version,kind -// information. -func (ot *ObjectTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { - if _, ok := obj.(*unstructured.Unstructured); !ok { - return nil, false, fmt.Errorf("type %T is invalid for determining dynamic object types", obj) - } - return []schema.GroupVersionKind{obj.GetObjectKind().GroupVersionKind()}, false, nil -} - -// Recognizes returns true if the provided group,version,kind was in -// the discovery information. -func (ot *ObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool { - return ot.registered[gvk] -} diff --git a/dynamic/dynamic_util_test.go b/dynamic/dynamic_util_test.go deleted file mode 100644 index 37113d4a..00000000 --- a/dynamic/dynamic_util_test.go +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package dynamic - -import ( - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -func TestDiscoveryRESTMapper(t *testing.T) { - resources := []*metav1.APIResourceList{ - { - GroupVersion: "test/beta1", - APIResources: []metav1.APIResource{ - { - Name: "test_kinds", - Namespaced: true, - Kind: "test_kind", - }, - }, - }, - } - - gvk := schema.GroupVersionKind{ - Group: "test", - Version: "beta1", - Kind: "test_kind", - } - - mapper, err := NewDiscoveryRESTMapper(resources) - if err != nil { - t.Fatalf("unexpected error creating mapper: %s", err) - } - - for _, res := range []schema.GroupVersionResource{ - { - Group: "test", - Version: "beta1", - Resource: "test_kinds", - }, - { - Version: "beta1", - Resource: "test_kinds", - }, - { - Group: "test", - Resource: "test_kinds", - }, - { - Resource: "test_kinds", - }, - } { - got, err := mapper.KindFor(res) - if err != nil { - t.Errorf("KindFor(%#v) unexpected error: %s", res, err) - continue - } - - if got != gvk { - t.Errorf("KindFor(%#v) = %#v; want %#v", res, got, gvk) - } - } -} diff --git a/dynamic/fake/client.go b/dynamic/fake/client.go deleted file mode 100644 index 8399076c..00000000 --- a/dynamic/fake/client.go +++ /dev/null @@ -1,163 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package fake provides a fake client interface to arbitrary Kubernetes -// APIs that exposes common high level operations and exposes common -// metadata. -package fake - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/testing" - "k8s.io/client-go/util/flowcontrol" -) - -// FakeClient is a fake implementation of dynamic.Interface. -type FakeClient struct { - GroupVersion schema.GroupVersion - - *testing.Fake -} - -// GetRateLimiter returns the rate limiter for this client. -func (c *FakeClient) GetRateLimiter() flowcontrol.RateLimiter { - return nil -} - -// 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 ResourceClient inherits the parameter codec of this client -func (c *FakeClient) Resource(resource *metav1.APIResource, namespace string) dynamic.ResourceInterface { - return &FakeResourceClient{ - Resource: c.GroupVersion.WithResource(resource.Name), - Kind: c.GroupVersion.WithKind(resource.Kind), - Namespace: namespace, - - Fake: c.Fake, - } -} - -// ParameterCodec returns a client with the provided parameter codec. -func (c *FakeClient) ParameterCodec(parameterCodec runtime.ParameterCodec) dynamic.Interface { - return &FakeClient{ - Fake: c.Fake, - } -} - -// FakeResourceClient is a fake implementation of dynamic.ResourceInterface -type FakeResourceClient struct { - Resource schema.GroupVersionResource - Kind schema.GroupVersionKind - Namespace string - - *testing.Fake -} - -// List returns a list of objects for this resource. -func (c *FakeResourceClient) List(opts metav1.ListOptions) (runtime.Object, error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(c.Resource, c.Kind, c.Namespace, opts), &unstructured.UnstructuredList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &unstructured.UnstructuredList{} - for _, item := range obj.(*unstructured.UnstructuredList).Items { - if label.Matches(labels.Set(item.GetLabels())) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Get gets the resource with the specified name. -func (c *FakeResourceClient) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(c.Resource, c.Namespace, name), &unstructured.Unstructured{}) - - if obj == nil { - return nil, err - } - - return obj.(*unstructured.Unstructured), err -} - -// Delete deletes the resource with the specified name. -func (c *FakeResourceClient) Delete(name string, opts *metav1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(c.Resource, c.Namespace, name), &unstructured.Unstructured{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeResourceClient) DeleteCollection(deleteOptions *metav1.DeleteOptions, listOptions metav1.ListOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteCollectionAction(c.Resource, c.Namespace, listOptions), &unstructured.Unstructured{}) - - return err -} - -// Create creates the provided resource. -func (c *FakeResourceClient) Create(inObj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(c.Resource, c.Namespace, inObj), &unstructured.Unstructured{}) - - if obj == nil { - return nil, err - } - return obj.(*unstructured.Unstructured), err -} - -// Update updates the provided resource. -func (c *FakeResourceClient) Update(inObj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(c.Resource, c.Namespace, inObj), &unstructured.Unstructured{}) - - if obj == nil { - return nil, err - } - return obj.(*unstructured.Unstructured), err -} - -// Watch returns a watch.Interface that watches the resource. -func (c *FakeResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(c.Resource, c.Namespace, opts)) -} - -// Patch patches the provided resource. -func (c *FakeResourceClient) Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error) { - obj, err := c.Fake. - Invokes(testing.NewPatchAction(c.Resource, c.Namespace, name, data), &unstructured.Unstructured{}) - - if obj == nil { - return nil, err - } - return obj.(*unstructured.Unstructured), err -} diff --git a/dynamic/fake/client_pool.go b/dynamic/fake/client_pool.go deleted file mode 100644 index 7ec11489..00000000 --- a/dynamic/fake/client_pool.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package fake provides a fake client interface to arbitrary Kubernetes -// APIs that exposes common high level operations and exposes common -// metadata. -package fake - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/testing" -) - -// FakeClientPool provides a fake implementation of dynamic.ClientPool. -// It assumes resource GroupVersions are the same as their corresponding kind GroupVersions. -type FakeClientPool struct { - testing.Fake -} - -// ClientForGroupVersionKind returns a client configured for the specified groupVersionResource. -// Resource may be empty. -func (p *FakeClientPool) ClientForGroupVersionResource(resource schema.GroupVersionResource) (dynamic.Interface, error) { - return p.ClientForGroupVersionKind(resource.GroupVersion().WithKind("")) -} - -// ClientForGroupVersionKind returns a client configured for the specified groupVersionKind. -// Kind may be empty. -func (p *FakeClientPool) ClientForGroupVersionKind(kind schema.GroupVersionKind) (dynamic.Interface, error) { - // we can just create a new client every time for testing purposes - return &FakeClient{ - GroupVersion: kind.GroupVersion(), - Fake: &p.Fake, - }, nil -} diff --git a/dynamic/interface.go b/dynamic/interface.go new file mode 100644 index 00000000..4503af6b --- /dev/null +++ b/dynamic/interface.go @@ -0,0 +1,34 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dynamic + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// APIPathResolverFunc knows how to convert a groupVersion to its API path. The Kind field is optional. +// TODO find a better place to move this for existing callers +type APIPathResolverFunc func(kind schema.GroupVersionKind) string + +// LegacyAPIPathResolverFunc can resolve paths properly with the legacy API. +// TODO find a better place to move this for existing callers +func LegacyAPIPathResolverFunc(kind schema.GroupVersionKind) string { + if len(kind.Group) == 0 { + return "/api" + } + return "/apis" +} From f6af22edb3e726b2726772faef71d1baa4b5c0b1 Mon Sep 17 00:00:00 2001 From: David Eads Date: Wed, 9 May 2018 12:58:12 -0400 Subject: [PATCH 3/3] fix dynamic client name Kubernetes-commit: fd044d152ee13a6cb812e4c3e7504ee8e24b5b8c --- deprecated-dynamic/client.go | 20 ++++++++++---------- dynamic/client_test.go | 2 +- dynamic/fake/simple.go | 6 +++--- dynamic/interface.go | 25 +++++++++++++++++++++++++ dynamic/simple.go | 33 ++++++++------------------------- 5 files changed, 47 insertions(+), 39 deletions(-) diff --git a/deprecated-dynamic/client.go b/deprecated-dynamic/client.go index 46c7535a..0974fe64 100644 --- a/deprecated-dynamic/client.go +++ b/deprecated-dynamic/client.go @@ -66,7 +66,7 @@ type ResourceInterface interface { // and manipulate metadata of a Kubernetes API group, and implements Interface. type Client struct { version schema.GroupVersion - delegate dynamic.DynamicInterface + delegate dynamic.Interface } // NewClient returns a new client based on the passed in config. The @@ -97,35 +97,35 @@ func (c *Client) Resource(resource *metav1.APIResource, namespace string) Resour } // the old interfaces used the wrong type for lists. this fixes that -func oldResourceShim(in dynamic.DynamicResourceInterface, subresources []string) ResourceInterface { - return oldResourceShimType{DynamicResourceInterface: in, subresources: subresources} +func oldResourceShim(in dynamic.ResourceInterface, subresources []string) ResourceInterface { + return oldResourceShimType{ResourceInterface: in, subresources: subresources} } type oldResourceShimType struct { - dynamic.DynamicResourceInterface + dynamic.ResourceInterface subresources []string } func (s oldResourceShimType) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - return s.DynamicResourceInterface.Create(obj, s.subresources...) + return s.ResourceInterface.Create(obj, s.subresources...) } func (s oldResourceShimType) Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - return s.DynamicResourceInterface.Update(obj, s.subresources...) + return s.ResourceInterface.Update(obj, s.subresources...) } func (s oldResourceShimType) Delete(name string, opts *metav1.DeleteOptions) error { - return s.DynamicResourceInterface.Delete(name, opts, s.subresources...) + return s.ResourceInterface.Delete(name, opts, s.subresources...) } func (s oldResourceShimType) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) { - return s.DynamicResourceInterface.Get(name, opts, s.subresources...) + return s.ResourceInterface.Get(name, opts, s.subresources...) } func (s oldResourceShimType) List(opts metav1.ListOptions) (runtime.Object, error) { - return s.DynamicResourceInterface.List(opts) + return s.ResourceInterface.List(opts) } func (s oldResourceShimType) Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error) { - return s.DynamicResourceInterface.Patch(name, pt, data, s.subresources...) + return s.ResourceInterface.Patch(name, pt, data, s.subresources...) } diff --git a/dynamic/client_test.go b/dynamic/client_test.go index 3e116cb8..e8fe9386 100644 --- a/dynamic/client_test.go +++ b/dynamic/client_test.go @@ -58,7 +58,7 @@ func getObject(version, kind, name string) *unstructured.Unstructured { } } -func getClientServer(h func(http.ResponseWriter, *http.Request)) (DynamicInterface, *httptest.Server, error) { +func getClientServer(h func(http.ResponseWriter, *http.Request)) (Interface, *httptest.Server, error) { srv := httptest.NewServer(http.HandlerFunc(h)) cl, err := NewForConfig(&restclient.Config{ Host: srv.URL, diff --git a/dynamic/fake/simple.go b/dynamic/fake/simple.go index 20b7b3c2..a71cec50 100644 --- a/dynamic/fake/simple.go +++ b/dynamic/fake/simple.go @@ -70,13 +70,13 @@ type dynamicResourceClient struct { resource schema.GroupVersionResource } -var _ dynamic.DynamicInterface = &FakeDynamicClient{} +var _ dynamic.Interface = &FakeDynamicClient{} -func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableDynamicResourceInterface { +func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface { return &dynamicResourceClient{client: c, resource: resource} } -func (c *dynamicResourceClient) Namespace(ns string) dynamic.DynamicResourceInterface { +func (c *dynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface { ret := *c ret.namespace = ns return &ret diff --git a/dynamic/interface.go b/dynamic/interface.go index 4503af6b..3f364f87 100644 --- a/dynamic/interface.go +++ b/dynamic/interface.go @@ -17,9 +17,34 @@ limitations under the License. package dynamic import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" ) +type Interface interface { + Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface +} + +type ResourceInterface interface { + Create(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) + Update(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) + UpdateStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) + Delete(name string, options *metav1.DeleteOptions, subresources ...string) error + DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error + Get(name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) + List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) + Watch(opts metav1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*unstructured.Unstructured, error) +} + +type NamespaceableResourceInterface interface { + Namespace(string) ResourceInterface + ResourceInterface +} + // APIPathResolverFunc knows how to convert a groupVersion to its API path. The Kind field is optional. // TODO find a better place to move this for existing callers type APIPathResolverFunc func(kind schema.GroupVersionKind) string diff --git a/dynamic/simple.go b/dynamic/simple.go index e87d99d7..88e9cc2b 100644 --- a/dynamic/simple.go +++ b/dynamic/simple.go @@ -30,34 +30,13 @@ import ( "k8s.io/client-go/rest" ) -type DynamicInterface interface { - Resource(resource schema.GroupVersionResource) NamespaceableDynamicResourceInterface -} - -type DynamicResourceInterface interface { - Create(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) - Update(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) - UpdateStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) - Delete(name string, options *metav1.DeleteOptions, subresources ...string) error - DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error - Get(name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) - List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) - Watch(opts metav1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*unstructured.Unstructured, error) -} - -type NamespaceableDynamicResourceInterface interface { - Namespace(string) DynamicResourceInterface - DynamicResourceInterface -} - type dynamicClient struct { client *rest.RESTClient } -var _ DynamicInterface = &dynamicClient{} +var _ Interface = &dynamicClient{} -func NewForConfig(inConfig *rest.Config) (DynamicInterface, error) { +func NewForConfig(inConfig *rest.Config) (Interface, error) { config := rest.CopyConfig(inConfig) // for serializing the options config.GroupVersion = &schema.GroupVersion{} @@ -83,11 +62,11 @@ type dynamicResourceClient struct { resource schema.GroupVersionResource } -func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableDynamicResourceInterface { +func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface { return &dynamicResourceClient{client: c, resource: resource} } -func (c *dynamicResourceClient) Namespace(ns string) DynamicResourceInterface { +func (c *dynamicResourceClient) Namespace(ns string) ResourceInterface { ret := *c ret.namespace = ns return &ret @@ -161,6 +140,10 @@ func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured) (*u } result := c.client.client.Put().AbsPath(append(c.makeURLSegments(accessor.GetName()), "status")...).Body(outBytes).Do() + if err := result.Error(); err != nil { + return nil, err + } + retBytes, err := result.Raw() if err != nil { return nil, err