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"