mirror of
https://github.com/kubernetes/client-go.git
synced 2025-06-25 14:41:53 +00:00
Dynamic client support subresource create/get/update/patch verbs
Kubernetes-commit: e10cdb3b0f42bafcdf1d1a95e6fb14cbfe2b4ab7
This commit is contained in:
parent
5f85fe426e
commit
93a87a9af9
@ -145,6 +145,19 @@ type ResourceClient struct {
|
|||||||
parameterCodec runtime.ParameterCodec
|
parameterCodec runtime.ParameterCodec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rc *ResourceClient) parseResourceSubresourceName() (string, []string) {
|
||||||
|
var resourceName string
|
||||||
|
var subresourceName []string
|
||||||
|
if strings.Contains(rc.resource.Name, "/") {
|
||||||
|
resourceName = strings.Split(rc.resource.Name, "/")[0]
|
||||||
|
subresourceName = strings.Split(rc.resource.Name, "/")[1:]
|
||||||
|
} else {
|
||||||
|
resourceName = rc.resource.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceName, subresourceName
|
||||||
|
}
|
||||||
|
|
||||||
// List returns a list of objects for this resource.
|
// List returns a list of objects for this resource.
|
||||||
func (rc *ResourceClient) List(opts metav1.ListOptions) (runtime.Object, error) {
|
func (rc *ResourceClient) List(opts metav1.ListOptions) (runtime.Object, error) {
|
||||||
parameterEncoder := rc.parameterCodec
|
parameterEncoder := rc.parameterCodec
|
||||||
@ -166,9 +179,11 @@ func (rc *ResourceClient) Get(name string, opts metav1.GetOptions) (*unstructure
|
|||||||
parameterEncoder = defaultParameterEncoder
|
parameterEncoder = defaultParameterEncoder
|
||||||
}
|
}
|
||||||
result := new(unstructured.Unstructured)
|
result := new(unstructured.Unstructured)
|
||||||
|
resourceName, subresourceName := rc.parseResourceSubresourceName()
|
||||||
err := rc.cl.Get().
|
err := rc.cl.Get().
|
||||||
NamespaceIfScoped(rc.ns, rc.resource.Namespaced).
|
NamespaceIfScoped(rc.ns, rc.resource.Namespaced).
|
||||||
Resource(rc.resource.Name).
|
Resource(resourceName).
|
||||||
|
SubResource(subresourceName...).
|
||||||
VersionedParams(&opts, parameterEncoder).
|
VersionedParams(&opts, parameterEncoder).
|
||||||
Name(name).
|
Name(name).
|
||||||
Do().
|
Do().
|
||||||
@ -205,11 +220,26 @@ func (rc *ResourceClient) DeleteCollection(deleteOptions *metav1.DeleteOptions,
|
|||||||
// Create creates the provided resource.
|
// Create creates the provided resource.
|
||||||
func (rc *ResourceClient) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
func (rc *ResourceClient) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||||
result := new(unstructured.Unstructured)
|
result := new(unstructured.Unstructured)
|
||||||
err := rc.cl.Post().
|
resourceName, subresourceName := rc.parseResourceSubresourceName()
|
||||||
|
req := rc.cl.Post().
|
||||||
NamespaceIfScoped(rc.ns, rc.resource.Namespaced).
|
NamespaceIfScoped(rc.ns, rc.resource.Namespaced).
|
||||||
Resource(rc.resource.Name).
|
Resource(resourceName).
|
||||||
Body(obj).
|
Body(obj)
|
||||||
Do().
|
if len(subresourceName) > 0 {
|
||||||
|
// If the provided resource is a subresource, the POST request should contain
|
||||||
|
// object name. Examples of subresources that support Create operation:
|
||||||
|
// core/v1/pods/{name}/binding
|
||||||
|
// core/v1/pods/{name}/eviction
|
||||||
|
// extensions/v1beta1/deployments/{name}/rollback
|
||||||
|
// apps/v1beta1/deployments/{name}/rollback
|
||||||
|
// NOTE: Currently our system assumes every subresource object has the same
|
||||||
|
// name as the parent resource object. E.g. a pods/binding object having
|
||||||
|
// metadada.name "foo" means pod "foo" is being bound. We may need to
|
||||||
|
// change this if we break the assumption in the future.
|
||||||
|
req = req.SubResource(subresourceName...).
|
||||||
|
Name(obj.GetName())
|
||||||
|
}
|
||||||
|
err := req.Do().
|
||||||
Into(result)
|
Into(result)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
@ -220,9 +250,15 @@ func (rc *ResourceClient) Update(obj *unstructured.Unstructured) (*unstructured.
|
|||||||
if len(obj.GetName()) == 0 {
|
if len(obj.GetName()) == 0 {
|
||||||
return result, errors.New("object missing name")
|
return result, errors.New("object missing name")
|
||||||
}
|
}
|
||||||
|
resourceName, subresourceName := rc.parseResourceSubresourceName()
|
||||||
err := rc.cl.Put().
|
err := rc.cl.Put().
|
||||||
NamespaceIfScoped(rc.ns, rc.resource.Namespaced).
|
NamespaceIfScoped(rc.ns, rc.resource.Namespaced).
|
||||||
Resource(rc.resource.Name).
|
Resource(resourceName).
|
||||||
|
SubResource(subresourceName...).
|
||||||
|
// NOTE: Currently our system assumes every subresource object has the same
|
||||||
|
// name as the parent resource object. E.g. a pods/binding object having
|
||||||
|
// metadada.name "foo" means pod "foo" is being bound. We may need to
|
||||||
|
// change this if we break the assumption in the future.
|
||||||
Name(obj.GetName()).
|
Name(obj.GetName()).
|
||||||
Body(obj).
|
Body(obj).
|
||||||
Do().
|
Do().
|
||||||
@ -246,9 +282,11 @@ func (rc *ResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error
|
|||||||
|
|
||||||
func (rc *ResourceClient) Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error) {
|
func (rc *ResourceClient) Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error) {
|
||||||
result := new(unstructured.Unstructured)
|
result := new(unstructured.Unstructured)
|
||||||
|
resourceName, subresourceName := rc.parseResourceSubresourceName()
|
||||||
err := rc.cl.Patch(pt).
|
err := rc.cl.Patch(pt).
|
||||||
NamespaceIfScoped(rc.ns, rc.resource.Namespaced).
|
NamespaceIfScoped(rc.ns, rc.resource.Namespaced).
|
||||||
Resource(rc.resource.Name).
|
Resource(resourceName).
|
||||||
|
SubResource(subresourceName...).
|
||||||
Name(name).
|
Name(name).
|
||||||
Body(data).
|
Body(data).
|
||||||
Do().
|
Do().
|
||||||
|
@ -150,6 +150,7 @@ func TestList(t *testing.T) {
|
|||||||
|
|
||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) {
|
||||||
tcs := []struct {
|
tcs := []struct {
|
||||||
|
resource string
|
||||||
namespace string
|
namespace string
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
@ -157,22 +158,39 @@ func TestGet(t *testing.T) {
|
|||||||
want *unstructured.Unstructured
|
want *unstructured.Unstructured
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
resource: "rtest",
|
||||||
name: "normal_get",
|
name: "normal_get",
|
||||||
path: "/api/gtest/vtest/rtest/normal_get",
|
path: "/api/gtest/vtest/rtest/normal_get",
|
||||||
resp: getJSON("vTest", "rTest", "normal_get"),
|
resp: getJSON("vTest", "rTest", "normal_get"),
|
||||||
want: getObject("vTest", "rTest", "normal_get"),
|
want: getObject("vTest", "rTest", "normal_get"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
resource: "rtest",
|
||||||
namespace: "nstest",
|
namespace: "nstest",
|
||||||
name: "namespaced_get",
|
name: "namespaced_get",
|
||||||
path: "/api/gtest/vtest/namespaces/nstest/rtest/namespaced_get",
|
path: "/api/gtest/vtest/namespaces/nstest/rtest/namespaced_get",
|
||||||
resp: getJSON("vTest", "rTest", "namespaced_get"),
|
resp: getJSON("vTest", "rTest", "namespaced_get"),
|
||||||
want: getObject("vTest", "rTest", "namespaced_get"),
|
want: getObject("vTest", "rTest", "namespaced_get"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
resource: "rtest/srtest",
|
||||||
|
name: "normal_subresource_get",
|
||||||
|
path: "/api/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: "/api/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 {
|
for _, tc := range tcs {
|
||||||
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
||||||
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
|
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
|
||||||
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != "GET" {
|
if r.Method != "GET" {
|
||||||
t.Errorf("Get(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
|
t.Errorf("Get(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
|
||||||
@ -303,26 +321,42 @@ func TestDeleteCollection(t *testing.T) {
|
|||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
func TestCreate(t *testing.T) {
|
||||||
tcs := []struct {
|
tcs := []struct {
|
||||||
|
resource string
|
||||||
name string
|
name string
|
||||||
namespace string
|
namespace string
|
||||||
obj *unstructured.Unstructured
|
obj *unstructured.Unstructured
|
||||||
path string
|
path string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
resource: "rtest",
|
||||||
name: "normal_create",
|
name: "normal_create",
|
||||||
path: "/api/gtest/vtest/rtest",
|
path: "/api/gtest/vtest/rtest",
|
||||||
obj: getObject("vTest", "rTest", "normal_create"),
|
obj: getObject("vTest", "rTest", "normal_create"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
resource: "rtest",
|
||||||
name: "namespaced_create",
|
name: "namespaced_create",
|
||||||
namespace: "nstest",
|
namespace: "nstest",
|
||||||
path: "/api/gtest/vtest/namespaces/nstest/rtest",
|
path: "/api/gtest/vtest/namespaces/nstest/rtest",
|
||||||
obj: getObject("vTest", "rTest", "namespaced_create"),
|
obj: getObject("vTest", "rTest", "namespaced_create"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
resource: "rtest/srtest",
|
||||||
|
name: "normal_subresource_create",
|
||||||
|
path: "/api/gtest/vtest/rtest/normal_subresource_create/srtest",
|
||||||
|
obj: getObject("vTest", "srTest", "normal_subresource_create"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource: "rtest/srtest",
|
||||||
|
name: "namespaced_subresource_create",
|
||||||
|
namespace: "nstest",
|
||||||
|
path: "/api/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_create/srtest",
|
||||||
|
obj: getObject("vTest", "srTest", "namespaced_subresource_create"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
||||||
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
|
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
|
||||||
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != "POST" {
|
if r.Method != "POST" {
|
||||||
t.Errorf("Create(%q) got HTTP method %s. wanted POST", tc.name, r.Method)
|
t.Errorf("Create(%q) got HTTP method %s. wanted POST", tc.name, r.Method)
|
||||||
@ -362,26 +396,42 @@ func TestCreate(t *testing.T) {
|
|||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
func TestUpdate(t *testing.T) {
|
||||||
tcs := []struct {
|
tcs := []struct {
|
||||||
|
resource string
|
||||||
name string
|
name string
|
||||||
namespace string
|
namespace string
|
||||||
obj *unstructured.Unstructured
|
obj *unstructured.Unstructured
|
||||||
path string
|
path string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
resource: "rtest",
|
||||||
name: "normal_update",
|
name: "normal_update",
|
||||||
path: "/api/gtest/vtest/rtest/normal_update",
|
path: "/api/gtest/vtest/rtest/normal_update",
|
||||||
obj: getObject("vTest", "rTest", "normal_update"),
|
obj: getObject("vTest", "rTest", "normal_update"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
resource: "rtest",
|
||||||
name: "namespaced_update",
|
name: "namespaced_update",
|
||||||
namespace: "nstest",
|
namespace: "nstest",
|
||||||
path: "/api/gtest/vtest/namespaces/nstest/rtest/namespaced_update",
|
path: "/api/gtest/vtest/namespaces/nstest/rtest/namespaced_update",
|
||||||
obj: getObject("vTest", "rTest", "namespaced_update"),
|
obj: getObject("vTest", "rTest", "namespaced_update"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
resource: "rtest/srtest",
|
||||||
|
name: "normal_subresource_update",
|
||||||
|
path: "/api/gtest/vtest/rtest/normal_update/srtest",
|
||||||
|
obj: getObject("vTest", "srTest", "normal_update"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource: "rtest/srtest",
|
||||||
|
name: "namespaced_subresource_update",
|
||||||
|
namespace: "nstest",
|
||||||
|
path: "/api/gtest/vtest/namespaces/nstest/rtest/namespaced_update/srtest",
|
||||||
|
obj: getObject("vTest", "srTest", "namespaced_update"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
||||||
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
|
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
|
||||||
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != "PUT" {
|
if r.Method != "PUT" {
|
||||||
t.Errorf("Update(%q) got HTTP method %s. wanted PUT", tc.name, r.Method)
|
t.Errorf("Update(%q) got HTTP method %s. wanted PUT", tc.name, r.Method)
|
||||||
@ -492,6 +542,7 @@ func TestWatch(t *testing.T) {
|
|||||||
|
|
||||||
func TestPatch(t *testing.T) {
|
func TestPatch(t *testing.T) {
|
||||||
tcs := []struct {
|
tcs := []struct {
|
||||||
|
resource string
|
||||||
name string
|
name string
|
||||||
namespace string
|
namespace string
|
||||||
patch []byte
|
patch []byte
|
||||||
@ -499,22 +550,39 @@ func TestPatch(t *testing.T) {
|
|||||||
path string
|
path string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
resource: "rtest",
|
||||||
name: "normal_patch",
|
name: "normal_patch",
|
||||||
path: "/api/gtest/vtest/rtest/normal_patch",
|
path: "/api/gtest/vtest/rtest/normal_patch",
|
||||||
patch: getJSON("vTest", "rTest", "normal_patch"),
|
patch: getJSON("vTest", "rTest", "normal_patch"),
|
||||||
want: getObject("vTest", "rTest", "normal_patch"),
|
want: getObject("vTest", "rTest", "normal_patch"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
resource: "rtest",
|
||||||
name: "namespaced_patch",
|
name: "namespaced_patch",
|
||||||
namespace: "nstest",
|
namespace: "nstest",
|
||||||
path: "/api/gtest/vtest/namespaces/nstest/rtest/namespaced_patch",
|
path: "/api/gtest/vtest/namespaces/nstest/rtest/namespaced_patch",
|
||||||
patch: getJSON("vTest", "rTest", "namespaced_patch"),
|
patch: getJSON("vTest", "rTest", "namespaced_patch"),
|
||||||
want: getObject("vTest", "rTest", "namespaced_patch"),
|
want: getObject("vTest", "rTest", "namespaced_patch"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
resource: "rtest/srtest",
|
||||||
|
name: "normal_subresource_patch",
|
||||||
|
path: "/api/gtest/vtest/rtest/normal_subresource_patch/srtest",
|
||||||
|
patch: getJSON("vTest", "srTest", "normal_subresource_patch"),
|
||||||
|
want: getObject("vTest", "srTest", "normal_subresource_patch"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource: "rtest/srtest",
|
||||||
|
name: "namespaced_subresource_patch",
|
||||||
|
namespace: "nstest",
|
||||||
|
path: "/api/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_patch/srtest",
|
||||||
|
patch: getJSON("vTest", "srTest", "namespaced_subresource_patch"),
|
||||||
|
want: getObject("vTest", "srTest", "namespaced_subresource_patch"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
||||||
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
|
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
|
||||||
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != "PATCH" {
|
if r.Method != "PATCH" {
|
||||||
t.Errorf("Patch(%q) got HTTP method %s. wanted PATCH", tc.name, r.Method)
|
t.Errorf("Patch(%q) got HTTP method %s. wanted PATCH", tc.name, r.Method)
|
||||||
|
Loading…
Reference in New Issue
Block a user