From 7eb81343d3372d8690d3c43d95206a61d61fbbd3 Mon Sep 17 00:00:00 2001 From: Alvaro Aleman Date: Sun, 11 Dec 2022 15:19:03 -0500 Subject: [PATCH] :sparkles: Add Get functionality to SubResourceClient This changes enabled the SubResourceClient to retrieve subresources, thereby completing it for CRUD subresources (under the assumption that there is no such thing as a Delete for subresources, which does hold true for core resources). --- pkg/client/client.go | 85 +++++++++++++++++++++++-------- pkg/client/client_test.go | 43 +++++++++++++++- pkg/client/dryrun.go | 22 ++++---- pkg/client/fake/client.go | 16 +++--- pkg/client/interfaces.go | 34 +++++++++---- pkg/client/namespaced_client.go | 42 ++++++++++----- pkg/client/options.go | 5 ++ pkg/client/split.go | 8 +-- pkg/client/typed_client.go | 23 +++++++++ pkg/client/unstructured_client.go | 33 +++++++++++- 10 files changed, 248 insertions(+), 63 deletions(-) diff --git a/pkg/client/client.go b/pkg/client/client.go index 0b0b018a44..7d1ed5c968 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -18,6 +18,7 @@ package client import ( "context" + "errors" "fmt" "strings" @@ -292,18 +293,48 @@ func (c *client) Status() SubResourceWriter { return c.SubResource("status") } -func (c *client) SubResource(subResource string) SubResourceWriter { - return &subResourceWriter{client: c, subResource: subResource} +func (c *client) SubResource(subResource string) SubResourceClient { + return &subResourceClient{client: c, subResource: subResource} } -// subResourceWriter is client.SubResourceWriter that writes to subresources. -type subResourceWriter struct { +// subResourceClient is client.SubResourceWriter that writes to subresources. +type subResourceClient struct { client *client subResource string } -// ensure subResourceWriter implements client.SubResourceWriter. -var _ SubResourceWriter = &subResourceWriter{} +// ensure subResourceClient implements client.SubResourceClient. +var _ SubResourceClient = &subResourceClient{} + +// SubResourceGetOptions holds all the possible configuration +// for a subresource Get request. +type SubResourceGetOptions struct { + Raw *metav1.GetOptions +} + +// ApplyToSubResourceGet updates the configuaration to the given get options. +func (getOpt *SubResourceGetOptions) ApplyToSubResourceGet(o *SubResourceGetOptions) { + if getOpt.Raw != nil { + o.Raw = getOpt.Raw + } +} + +// ApplyOptions applues the given options. +func (getOpt *SubResourceGetOptions) ApplyOptions(opts []SubResourceGetOption) *SubResourceGetOptions { + for _, o := range opts { + o.ApplyToSubResourceGet(getOpt) + } + + return getOpt +} + +// AsGetOptions returns the configured options as *metav1.GetOptions. +func (getOpt *SubResourceGetOptions) AsGetOptions() *metav1.GetOptions { + if getOpt.Raw == nil { + return &metav1.GetOptions{} + } + return getOpt.Raw +} // SubResourceUpdateOptions holds all the possible configuration // for a subresource update request. @@ -398,42 +429,54 @@ func (po *SubResourcePatchOptions) ApplyToSubResourcePatch(o *SubResourcePatchOp } } -func (sw *subResourceWriter) Create(ctx context.Context, obj Object, subResource Object, opts ...SubResourceCreateOption) error { - defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) - defer sw.client.resetGroupVersionKind(subResource, subResource.GetObjectKind().GroupVersionKind()) +func (sc *subResourceClient) Get(ctx context.Context, obj Object, subResource Object, opts ...SubResourceGetOption) error { + switch obj.(type) { + case *unstructured.Unstructured: + return sc.client.unstructuredClient.GetSubResource(ctx, obj, subResource, sc.subResource, opts...) + case *metav1.PartialObjectMetadata: + return errors.New("can not get subresource using only metadata") + default: + return sc.client.typedClient.GetSubResource(ctx, obj, subResource, sc.subResource, opts...) + } +} + +// Create implements client.SubResourceClient +func (sc *subResourceClient) Create(ctx context.Context, obj Object, subResource Object, opts ...SubResourceCreateOption) error { + defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) + defer sc.client.resetGroupVersionKind(subResource, subResource.GetObjectKind().GroupVersionKind()) switch obj.(type) { case *unstructured.Unstructured: - return sw.client.unstructuredClient.CreateSubResource(ctx, obj, subResource, sw.subResource, opts...) + return sc.client.unstructuredClient.CreateSubResource(ctx, obj, subResource, sc.subResource, opts...) case *metav1.PartialObjectMetadata: return fmt.Errorf("cannot update status using only metadata -- did you mean to patch?") default: - return sw.client.typedClient.CreateSubResource(ctx, obj, subResource, sw.subResource, opts...) + return sc.client.typedClient.CreateSubResource(ctx, obj, subResource, sc.subResource, opts...) } } -// Update implements client.SubResourceWriter. -func (sw *subResourceWriter) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error { - defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) +// Update implements client.SubResourceClient +func (sc *subResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error { + defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) switch obj.(type) { case *unstructured.Unstructured: - return sw.client.unstructuredClient.UpdateSubResource(ctx, obj, sw.subResource, opts...) + return sc.client.unstructuredClient.UpdateSubResource(ctx, obj, sc.subResource, opts...) case *metav1.PartialObjectMetadata: return fmt.Errorf("cannot update status using only metadata -- did you mean to patch?") default: - return sw.client.typedClient.UpdateSubResource(ctx, obj, sw.subResource, opts...) + return sc.client.typedClient.UpdateSubResource(ctx, obj, sc.subResource, opts...) } } // Patch implements client.SubResourceWriter. -func (sw *subResourceWriter) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { - defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) +func (sc *subResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { + defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) switch obj.(type) { case *unstructured.Unstructured: - return sw.client.unstructuredClient.PatchSubResource(ctx, obj, sw.subResource, patch, opts...) + return sc.client.unstructuredClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...) case *metav1.PartialObjectMetadata: - return sw.client.metadataClient.PatchSubResource(ctx, obj, sw.subResource, patch, opts...) + return sc.client.metadataClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...) default: - return sw.client.typedClient.PatchSubResource(ctx, obj, sw.subResource, patch, opts...) + return sc.client.typedClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...) } } diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 4ef391625e..856aaa067e 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "reflect" "sync/atomic" "time" @@ -740,8 +741,23 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) }) - Describe("SubResourceWriter", func() { + Describe("SubResourceClient", func() { Context("with structured objects", func() { + It("should be able to read the Scale subresource", func() { + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + Expect(cl).NotTo(BeNil()) + + By("Creating a deployment") + dep, err := clientset.AppsV1().Deployments(dep.Namespace).Create(ctx, dep, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("reading the scale subresource") + scale := &autoscalingv1.Scale{} + err = cl.SubResource("scale").Get(ctx, dep, scale) + Expect(err).NotTo(HaveOccurred()) + Expect(scale.Spec.Replicas).To(Equal(*dep.Spec.Replicas)) + }) It("should be able to create ServiceAccount tokens", func() { cl, err := client.New(cfg, client.Options{}) Expect(err).NotTo(HaveOccurred()) @@ -901,6 +917,31 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) Context("with unstructured objects", func() { + It("should be able to read the Scale subresource", func() { + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + Expect(cl).NotTo(BeNil()) + + By("Creating a deployment") + dep, err := clientset.AppsV1().Deployments(dep.Namespace).Create(ctx, dep, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + dep.APIVersion = appsv1.SchemeGroupVersion.String() + dep.Kind = reflect.TypeOf(dep).Elem().Name() + depUnstructured, err := toUnstructured(dep) + Expect(err).NotTo(HaveOccurred()) + + By("reading the scale subresource") + scale := &unstructured.Unstructured{} + scale.SetAPIVersion("autoscaling/v1") + scale.SetKind("Scale") + err = cl.SubResource("scale").Get(ctx, depUnstructured, scale) + Expect(err).NotTo(HaveOccurred()) + + val, found, err := unstructured.NestedInt64(scale.UnstructuredContent(), "spec", "replicas") + Expect(err).NotTo(HaveOccurred()) + Expect(found).To(BeTrue()) + Expect(int32(val)).To(Equal(*dep.Spec.Replicas)) + }) It("should be able to create ServiceAccount tokens", func() { cl, err := client.New(cfg, client.Options{}) Expect(err).NotTo(HaveOccurred()) diff --git a/pkg/client/dryrun.go b/pkg/client/dryrun.go index d4a5865822..73b56429e7 100644 --- a/pkg/client/dryrun.go +++ b/pkg/client/dryrun.go @@ -87,29 +87,33 @@ func (c *dryRunClient) Status() SubResourceWriter { } // SubResource implements client.SubResourceClient. -func (c *dryRunClient) SubResource(subResource string) SubResourceWriter { - return &dryRunSubResourceWriter{client: c.client.SubResource(subResource)} +func (c *dryRunClient) SubResource(subResource string) SubResourceClient { + return &dryRunSubResourceClient{client: c.client.SubResource(subResource)} } // ensure dryRunSubResourceWriter implements client.SubResourceWriter. -var _ SubResourceWriter = &dryRunSubResourceWriter{} +var _ SubResourceWriter = &dryRunSubResourceClient{} -// dryRunSubResourceWriter is client.SubResourceWriter that writes status subresource with dryRun mode +// dryRunSubResourceClient is client.SubResourceWriter that writes status subresource with dryRun mode // enforced. -type dryRunSubResourceWriter struct { - client SubResourceWriter +type dryRunSubResourceClient struct { + client SubResourceClient } -func (sw *dryRunSubResourceWriter) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error { +func (sw *dryRunSubResourceClient) Get(ctx context.Context, obj, subResource Object, opts ...SubResourceGetOption) error { + return sw.client.Get(ctx, obj, subResource, opts...) +} + +func (sw *dryRunSubResourceClient) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error { return sw.client.Create(ctx, obj, subResource, append(opts, DryRunAll)...) } // Update implements client.SubResourceWriter. -func (sw *dryRunSubResourceWriter) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error { +func (sw *dryRunSubResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error { return sw.client.Update(ctx, obj, append(opts, DryRunAll)...) } // Patch implements client.SubResourceWriter. -func (sw *dryRunSubResourceWriter) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { +func (sw *dryRunSubResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { return sw.client.Patch(ctx, obj, patch, append(opts, DryRunAll)...) } diff --git a/pkg/client/fake/client.go b/pkg/client/fake/client.go index f05bc095df..4da642319a 100644 --- a/pkg/client/fake/client.go +++ b/pkg/client/fake/client.go @@ -756,8 +756,8 @@ func (c *fakeClient) Status() client.SubResourceWriter { return c.SubResource("status") } -func (c *fakeClient) SubResource(subResource string) client.SubResourceWriter { - return &fakeSubResourceWriter{client: c} +func (c *fakeClient) SubResource(subResource string) client.SubResourceClient { + return &fakeSubResourceClient{client: c} } func (c *fakeClient) deleteObject(gvr schema.GroupVersionResource, accessor metav1.Object) error { @@ -786,15 +786,19 @@ func getGVRFromObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupV return gvr, nil } -type fakeSubResourceWriter struct { +type fakeSubResourceClient struct { client *fakeClient } -func (sw *fakeSubResourceWriter) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error { +func (sw *fakeSubResourceClient) Get(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceGetOption) error { + panic("fakeSubResourceClient does not support get") +} + +func (sw *fakeSubResourceClient) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error { panic("fakeSubResourceWriter does not support create") } -func (sw *fakeSubResourceWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { +func (sw *fakeSubResourceClient) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { // TODO(droot): This results in full update of the obj (spec + subresources). Need // a way to update subresource only. updateOptions := client.SubResourceUpdateOptions{} @@ -807,7 +811,7 @@ func (sw *fakeSubResourceWriter) Update(ctx context.Context, obj client.Object, return sw.client.Update(ctx, body, &updateOptions.UpdateOptions) } -func (sw *fakeSubResourceWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { +func (sw *fakeSubResourceClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { // TODO(droot): This results in full update of the obj (spec + subresources). Need // a way to update subresource only. diff --git a/pkg/client/interfaces.go b/pkg/client/interfaces.go index f49176c669..b642f7f88f 100644 --- a/pkg/client/interfaces.go +++ b/pkg/client/interfaces.go @@ -85,21 +85,21 @@ type StatusClient interface { Status() SubResourceWriter } -// SubResourceClient knows how to create a client which can update subresource +// SubResourceClientConstructor knows how to create a client which can update subresource // for kubernetes objects. -type SubResourceClient interface { - // SubResource returns a subresource client for the named subResource. Known - // upstream subResources are: - // - ServiceAccount tokens: +type SubResourceClientConstructor interface { + // SubResourceClientConstructor returns a subresource client for the named subResource. Known + // upstream subResources usages are: + // - ServiceAccount token creation: // sa := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}} // token := &authenticationv1.TokenRequest{} // c.SubResourceClient("token").Create(ctx, sa, token) // - // - Pod evictions: + // - Pod eviction creation: // pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}} // c.SubResourceClient("eviction").Create(ctx, pod, &policyv1.Eviction{}) // - // - Pod bindings: + // - Pod binding creation: // pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}} // binding := &corev1.Binding{Target: corev1.ObjectReference{Name: "my-node"}} // c.SubResourceClient("binding").Create(ctx, pod, binding) @@ -116,16 +116,26 @@ type SubResourceClient interface { // } // c.SubResourceClient("approval").Update(ctx, csr) // + // - Scale retrieval: + // dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}} + // scale := &autoscalingv1.Scale{} + // c.SubResourceClient("scale").Get(ctx, dep, scale) + // // - Scale update: // dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}} // scale := &autoscalingv1.Scale{Spec: autoscalingv1.ScaleSpec{Replicas: 2}} // c.SubResourceClient("scale").Update(ctx, dep, client.WithSubResourceBody(scale)) - SubResource(subResource string) SubResourceWriter + SubResource(subResource string) SubResourceClient } // StatusWriter is kept for backward compatibility. type StatusWriter = SubResourceWriter +// SubResourceReader knows how to read SubResources +type SubResourceReader interface { + Get(ctx context.Context, obj Object, subResource Object, opts ...SubResourceGetOption) error +} + // SubResourceWriter knows how to update subresource of a Kubernetes object. type SubResourceWriter interface { // Create saves the subResource object in the Kubernetes cluster. obj must be a @@ -142,12 +152,18 @@ type SubResourceWriter interface { Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error } +// SubResourceClient knows how to perform CRU operations on Kubernetes objects. +type SubResourceClient interface { + SubResourceReader + SubResourceWriter +} + // Client knows how to perform CRUD operations on Kubernetes objects. type Client interface { Reader Writer StatusClient - SubResourceClient + SubResourceClientConstructor // Scheme returns the scheme this client is using. Scheme() *runtime.Scheme diff --git a/pkg/client/namespaced_client.go b/pkg/client/namespaced_client.go index a8a73e145e..00bc2175ce 100644 --- a/pkg/client/namespaced_client.go +++ b/pkg/client/namespaced_client.go @@ -166,20 +166,20 @@ func (n *namespacedClient) Status() SubResourceWriter { } // SubResource implements client.SubResourceClient. -func (n *namespacedClient) SubResource(subResource string) SubResourceWriter { - return &namespacedClientSubResourceWriter{StatusClient: n.client.SubResource(subResource), namespace: n.namespace, namespacedclient: n} +func (n *namespacedClient) SubResource(subResource string) SubResourceClient { + return &namespacedClientSubResourceClient{client: n.client.SubResource(subResource), namespace: n.namespace, namespacedclient: n} } -// ensure namespacedClientSubResourceWriter implements client.SubResourceWriter. -var _ SubResourceWriter = &namespacedClientSubResourceWriter{} +// ensure namespacedClientSubResourceClient implements client.SubResourceClient. +var _ SubResourceClient = &namespacedClientSubResourceClient{} -type namespacedClientSubResourceWriter struct { - StatusClient SubResourceWriter +type namespacedClientSubResourceClient struct { + client SubResourceClient namespace string namespacedclient Client } -func (nsw *namespacedClientSubResourceWriter) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error { +func (nsw *namespacedClientSubResourceClient) Get(ctx context.Context, obj, subResource Object, opts ...SubResourceGetOption) error { isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper()) if err != nil { return fmt.Errorf("error finding the scope of the object: %w", err) @@ -194,11 +194,29 @@ func (nsw *namespacedClientSubResourceWriter) Create(ctx context.Context, obj, s obj.SetNamespace(nsw.namespace) } - return nsw.StatusClient.Create(ctx, obj, subResource, opts...) + return nsw.client.Get(ctx, obj, subResource, opts...) +} + +func (nsw *namespacedClientSubResourceClient) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error { + isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper()) + if err != nil { + return fmt.Errorf("error finding the scope of the object: %w", err) + } + + objectNamespace := obj.GetNamespace() + if objectNamespace != nsw.namespace && objectNamespace != "" { + return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespace) + } + + if isNamespaceScoped && objectNamespace == "" { + obj.SetNamespace(nsw.namespace) + } + + return nsw.client.Create(ctx, obj, subResource, opts...) } // Update implements client.SubResourceWriter. -func (nsw *namespacedClientSubResourceWriter) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error { +func (nsw *namespacedClientSubResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error { isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper()) if err != nil { return fmt.Errorf("error finding the scope of the object: %w", err) @@ -212,11 +230,11 @@ func (nsw *namespacedClientSubResourceWriter) Update(ctx context.Context, obj Ob if isNamespaceScoped && objectNamespace == "" { obj.SetNamespace(nsw.namespace) } - return nsw.StatusClient.Update(ctx, obj, opts...) + return nsw.client.Update(ctx, obj, opts...) } // Patch implements client.SubResourceWriter. -func (nsw *namespacedClientSubResourceWriter) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { +func (nsw *namespacedClientSubResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper()) if err != nil { @@ -231,5 +249,5 @@ func (nsw *namespacedClientSubResourceWriter) Patch(ctx context.Context, obj Obj if isNamespaceScoped && objectNamespace == "" { obj.SetNamespace(nsw.namespace) } - return nsw.StatusClient.Patch(ctx, obj, patch, opts...) + return nsw.client.Patch(ctx, obj, patch, opts...) } diff --git a/pkg/client/options.go b/pkg/client/options.go index caf14dee67..c8fa0dc797 100644 --- a/pkg/client/options.go +++ b/pkg/client/options.go @@ -67,6 +67,11 @@ type DeleteAllOfOption interface { ApplyToDeleteAllOf(*DeleteAllOfOptions) } +// SubResourceGetOption modifies options for a SubResource Get request. +type SubResourceGetOption interface { + ApplyToSubResourceGet(*SubResourceGetOptions) +} + // SubResourceUpdateOption is some configuration that modifies options for a update request. type SubResourceUpdateOption interface { // ApplyToSubResourceUpdate applies this configuration to the given update options. diff --git a/pkg/client/split.go b/pkg/client/split.go index 42b61152bd..19d1ab4db7 100644 --- a/pkg/client/split.go +++ b/pkg/client/split.go @@ -61,9 +61,9 @@ func NewDelegatingClient(in NewDelegatingClientInput) (Client, error) { uncachedGVKs: uncachedGVKs, cacheUnstructured: in.CacheUnstructured, }, - Writer: in.Client, - StatusClient: in.Client, - SubResourceClient: in.Client, + Writer: in.Client, + StatusClient: in.Client, + SubResourceClientConstructor: in.Client, }, nil } @@ -71,7 +71,7 @@ type delegatingClient struct { Reader Writer StatusClient - SubResourceClient + SubResourceClientConstructor scheme *runtime.Scheme mapper meta.RESTMapper diff --git a/pkg/client/typed_client.go b/pkg/client/typed_client.go index 09cad7b928..ade251572b 100644 --- a/pkg/client/typed_client.go +++ b/pkg/client/typed_client.go @@ -167,6 +167,29 @@ func (c *typedClient) List(ctx context.Context, obj ObjectList, opts ...ListOpti Into(obj) } +func (c *typedClient) GetSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceGetOption) error { + o, err := c.cache.getObjMeta(obj) + if err != nil { + return err + } + + if subResourceObj.GetName() == "" { + subResourceObj.SetName(obj.GetName()) + } + + getOpts := &SubResourceGetOptions{} + getOpts.ApplyOptions(opts) + + return o.Get(). + NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + Resource(o.resource()). + Name(o.GetName()). + SubResource(subResource). + VersionedParams(getOpts.AsGetOptions(), c.paramCodec). + Do(ctx). + Into(subResourceObj) +} + func (c *typedClient) CreateSubResource(ctx context.Context, obj Object, subResourceObj Object, subResource string, opts ...SubResourceCreateOption) error { o, err := c.cache.getObjMeta(obj) if err != nil { diff --git a/pkg/client/unstructured_client.go b/pkg/client/unstructured_client.go index da742632c8..7f25c7be90 100644 --- a/pkg/client/unstructured_client.go +++ b/pkg/client/unstructured_client.go @@ -225,7 +225,7 @@ func (uc *unstructuredClient) List(ctx context.Context, obj ObjectList, opts ... Into(obj) } -func (uc *unstructuredClient) CreateSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceCreateOption) error { +func (uc *unstructuredClient) GetSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceGetOption) error { if _, ok := obj.(*unstructured.Unstructured); !ok { return fmt.Errorf("unstructured client did not understand object: %T", subResource) } @@ -243,6 +243,37 @@ func (uc *unstructuredClient) CreateSubResource(ctx context.Context, obj, subRes return err } + getOpts := &SubResourceGetOptions{} + getOpts.ApplyOptions(opts) + + return o.Get(). + NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + Resource(o.resource()). + Name(o.GetName()). + SubResource(subResource). + VersionedParams(getOpts.AsGetOptions(), uc.paramCodec). + Do(ctx). + Into(subResourceObj) +} + +func (uc *unstructuredClient) CreateSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceCreateOption) error { + if _, ok := obj.(*unstructured.Unstructured); !ok { + return fmt.Errorf("unstructured client did not understand object: %T", subResourceObj) + } + + if _, ok := subResourceObj.(*unstructured.Unstructured); !ok { + return fmt.Errorf("unstructured client did not understand object: %T", obj) + } + + if subResourceObj.GetName() == "" { + subResourceObj.SetName(obj.GetName()) + } + + o, err := uc.cache.getObjMeta(obj) + if err != nil { + return err + } + createOpts := &SubResourceCreateOptions{} createOpts.ApplyOptions(opts)