Skip to content

Commit 7eb8134

Browse files
committed
✨ 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).
1 parent 222fb66 commit 7eb8134

File tree

10 files changed

+248
-63
lines changed

10 files changed

+248
-63
lines changed

pkg/client/client.go

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package client
1818

1919
import (
2020
"context"
21+
"errors"
2122
"fmt"
2223
"strings"
2324

@@ -292,18 +293,48 @@ func (c *client) Status() SubResourceWriter {
292293
return c.SubResource("status")
293294
}
294295

295-
func (c *client) SubResource(subResource string) SubResourceWriter {
296-
return &subResourceWriter{client: c, subResource: subResource}
296+
func (c *client) SubResource(subResource string) SubResourceClient {
297+
return &subResourceClient{client: c, subResource: subResource}
297298
}
298299

299-
// subResourceWriter is client.SubResourceWriter that writes to subresources.
300-
type subResourceWriter struct {
300+
// subResourceClient is client.SubResourceWriter that writes to subresources.
301+
type subResourceClient struct {
301302
client *client
302303
subResource string
303304
}
304305

305-
// ensure subResourceWriter implements client.SubResourceWriter.
306-
var _ SubResourceWriter = &subResourceWriter{}
306+
// ensure subResourceClient implements client.SubResourceClient.
307+
var _ SubResourceClient = &subResourceClient{}
308+
309+
// SubResourceGetOptions holds all the possible configuration
310+
// for a subresource Get request.
311+
type SubResourceGetOptions struct {
312+
Raw *metav1.GetOptions
313+
}
314+
315+
// ApplyToSubResourceGet updates the configuaration to the given get options.
316+
func (getOpt *SubResourceGetOptions) ApplyToSubResourceGet(o *SubResourceGetOptions) {
317+
if getOpt.Raw != nil {
318+
o.Raw = getOpt.Raw
319+
}
320+
}
321+
322+
// ApplyOptions applues the given options.
323+
func (getOpt *SubResourceGetOptions) ApplyOptions(opts []SubResourceGetOption) *SubResourceGetOptions {
324+
for _, o := range opts {
325+
o.ApplyToSubResourceGet(getOpt)
326+
}
327+
328+
return getOpt
329+
}
330+
331+
// AsGetOptions returns the configured options as *metav1.GetOptions.
332+
func (getOpt *SubResourceGetOptions) AsGetOptions() *metav1.GetOptions {
333+
if getOpt.Raw == nil {
334+
return &metav1.GetOptions{}
335+
}
336+
return getOpt.Raw
337+
}
307338

308339
// SubResourceUpdateOptions holds all the possible configuration
309340
// for a subresource update request.
@@ -398,42 +429,54 @@ func (po *SubResourcePatchOptions) ApplyToSubResourcePatch(o *SubResourcePatchOp
398429
}
399430
}
400431

401-
func (sw *subResourceWriter) Create(ctx context.Context, obj Object, subResource Object, opts ...SubResourceCreateOption) error {
402-
defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
403-
defer sw.client.resetGroupVersionKind(subResource, subResource.GetObjectKind().GroupVersionKind())
432+
func (sc *subResourceClient) Get(ctx context.Context, obj Object, subResource Object, opts ...SubResourceGetOption) error {
433+
switch obj.(type) {
434+
case *unstructured.Unstructured:
435+
return sc.client.unstructuredClient.GetSubResource(ctx, obj, subResource, sc.subResource, opts...)
436+
case *metav1.PartialObjectMetadata:
437+
return errors.New("can not get subresource using only metadata")
438+
default:
439+
return sc.client.typedClient.GetSubResource(ctx, obj, subResource, sc.subResource, opts...)
440+
}
441+
}
442+
443+
// Create implements client.SubResourceClient
444+
func (sc *subResourceClient) Create(ctx context.Context, obj Object, subResource Object, opts ...SubResourceCreateOption) error {
445+
defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
446+
defer sc.client.resetGroupVersionKind(subResource, subResource.GetObjectKind().GroupVersionKind())
404447

405448
switch obj.(type) {
406449
case *unstructured.Unstructured:
407-
return sw.client.unstructuredClient.CreateSubResource(ctx, obj, subResource, sw.subResource, opts...)
450+
return sc.client.unstructuredClient.CreateSubResource(ctx, obj, subResource, sc.subResource, opts...)
408451
case *metav1.PartialObjectMetadata:
409452
return fmt.Errorf("cannot update status using only metadata -- did you mean to patch?")
410453
default:
411-
return sw.client.typedClient.CreateSubResource(ctx, obj, subResource, sw.subResource, opts...)
454+
return sc.client.typedClient.CreateSubResource(ctx, obj, subResource, sc.subResource, opts...)
412455
}
413456
}
414457

415-
// Update implements client.SubResourceWriter.
416-
func (sw *subResourceWriter) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error {
417-
defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
458+
// Update implements client.SubResourceClient
459+
func (sc *subResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error {
460+
defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
418461
switch obj.(type) {
419462
case *unstructured.Unstructured:
420-
return sw.client.unstructuredClient.UpdateSubResource(ctx, obj, sw.subResource, opts...)
463+
return sc.client.unstructuredClient.UpdateSubResource(ctx, obj, sc.subResource, opts...)
421464
case *metav1.PartialObjectMetadata:
422465
return fmt.Errorf("cannot update status using only metadata -- did you mean to patch?")
423466
default:
424-
return sw.client.typedClient.UpdateSubResource(ctx, obj, sw.subResource, opts...)
467+
return sc.client.typedClient.UpdateSubResource(ctx, obj, sc.subResource, opts...)
425468
}
426469
}
427470

428471
// Patch implements client.SubResourceWriter.
429-
func (sw *subResourceWriter) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error {
430-
defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
472+
func (sc *subResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error {
473+
defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
431474
switch obj.(type) {
432475
case *unstructured.Unstructured:
433-
return sw.client.unstructuredClient.PatchSubResource(ctx, obj, sw.subResource, patch, opts...)
476+
return sc.client.unstructuredClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...)
434477
case *metav1.PartialObjectMetadata:
435-
return sw.client.metadataClient.PatchSubResource(ctx, obj, sw.subResource, patch, opts...)
478+
return sc.client.metadataClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...)
436479
default:
437-
return sw.client.typedClient.PatchSubResource(ctx, obj, sw.subResource, patch, opts...)
480+
return sc.client.typedClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...)
438481
}
439482
}

pkg/client/client_test.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"encoding/json"
2222
"fmt"
23+
"reflect"
2324
"sync/atomic"
2425
"time"
2526

@@ -740,8 +741,23 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC
740741
})
741742
})
742743

743-
Describe("SubResourceWriter", func() {
744+
Describe("SubResourceClient", func() {
744745
Context("with structured objects", func() {
746+
It("should be able to read the Scale subresource", func() {
747+
cl, err := client.New(cfg, client.Options{})
748+
Expect(err).NotTo(HaveOccurred())
749+
Expect(cl).NotTo(BeNil())
750+
751+
By("Creating a deployment")
752+
dep, err := clientset.AppsV1().Deployments(dep.Namespace).Create(ctx, dep, metav1.CreateOptions{})
753+
Expect(err).NotTo(HaveOccurred())
754+
755+
By("reading the scale subresource")
756+
scale := &autoscalingv1.Scale{}
757+
err = cl.SubResource("scale").Get(ctx, dep, scale)
758+
Expect(err).NotTo(HaveOccurred())
759+
Expect(scale.Spec.Replicas).To(Equal(*dep.Spec.Replicas))
760+
})
745761
It("should be able to create ServiceAccount tokens", func() {
746762
cl, err := client.New(cfg, client.Options{})
747763
Expect(err).NotTo(HaveOccurred())
@@ -901,6 +917,31 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC
901917
})
902918

903919
Context("with unstructured objects", func() {
920+
It("should be able to read the Scale subresource", func() {
921+
cl, err := client.New(cfg, client.Options{})
922+
Expect(err).NotTo(HaveOccurred())
923+
Expect(cl).NotTo(BeNil())
924+
925+
By("Creating a deployment")
926+
dep, err := clientset.AppsV1().Deployments(dep.Namespace).Create(ctx, dep, metav1.CreateOptions{})
927+
Expect(err).NotTo(HaveOccurred())
928+
dep.APIVersion = appsv1.SchemeGroupVersion.String()
929+
dep.Kind = reflect.TypeOf(dep).Elem().Name()
930+
depUnstructured, err := toUnstructured(dep)
931+
Expect(err).NotTo(HaveOccurred())
932+
933+
By("reading the scale subresource")
934+
scale := &unstructured.Unstructured{}
935+
scale.SetAPIVersion("autoscaling/v1")
936+
scale.SetKind("Scale")
937+
err = cl.SubResource("scale").Get(ctx, depUnstructured, scale)
938+
Expect(err).NotTo(HaveOccurred())
939+
940+
val, found, err := unstructured.NestedInt64(scale.UnstructuredContent(), "spec", "replicas")
941+
Expect(err).NotTo(HaveOccurred())
942+
Expect(found).To(BeTrue())
943+
Expect(int32(val)).To(Equal(*dep.Spec.Replicas))
944+
})
904945
It("should be able to create ServiceAccount tokens", func() {
905946
cl, err := client.New(cfg, client.Options{})
906947
Expect(err).NotTo(HaveOccurred())

pkg/client/dryrun.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,29 +87,33 @@ func (c *dryRunClient) Status() SubResourceWriter {
8787
}
8888

8989
// SubResource implements client.SubResourceClient.
90-
func (c *dryRunClient) SubResource(subResource string) SubResourceWriter {
91-
return &dryRunSubResourceWriter{client: c.client.SubResource(subResource)}
90+
func (c *dryRunClient) SubResource(subResource string) SubResourceClient {
91+
return &dryRunSubResourceClient{client: c.client.SubResource(subResource)}
9292
}
9393

9494
// ensure dryRunSubResourceWriter implements client.SubResourceWriter.
95-
var _ SubResourceWriter = &dryRunSubResourceWriter{}
95+
var _ SubResourceWriter = &dryRunSubResourceClient{}
9696

97-
// dryRunSubResourceWriter is client.SubResourceWriter that writes status subresource with dryRun mode
97+
// dryRunSubResourceClient is client.SubResourceWriter that writes status subresource with dryRun mode
9898
// enforced.
99-
type dryRunSubResourceWriter struct {
100-
client SubResourceWriter
99+
type dryRunSubResourceClient struct {
100+
client SubResourceClient
101101
}
102102

103-
func (sw *dryRunSubResourceWriter) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error {
103+
func (sw *dryRunSubResourceClient) Get(ctx context.Context, obj, subResource Object, opts ...SubResourceGetOption) error {
104+
return sw.client.Get(ctx, obj, subResource, opts...)
105+
}
106+
107+
func (sw *dryRunSubResourceClient) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error {
104108
return sw.client.Create(ctx, obj, subResource, append(opts, DryRunAll)...)
105109
}
106110

107111
// Update implements client.SubResourceWriter.
108-
func (sw *dryRunSubResourceWriter) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error {
112+
func (sw *dryRunSubResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error {
109113
return sw.client.Update(ctx, obj, append(opts, DryRunAll)...)
110114
}
111115

112116
// Patch implements client.SubResourceWriter.
113-
func (sw *dryRunSubResourceWriter) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error {
117+
func (sw *dryRunSubResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error {
114118
return sw.client.Patch(ctx, obj, patch, append(opts, DryRunAll)...)
115119
}

pkg/client/fake/client.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -756,8 +756,8 @@ func (c *fakeClient) Status() client.SubResourceWriter {
756756
return c.SubResource("status")
757757
}
758758

759-
func (c *fakeClient) SubResource(subResource string) client.SubResourceWriter {
760-
return &fakeSubResourceWriter{client: c}
759+
func (c *fakeClient) SubResource(subResource string) client.SubResourceClient {
760+
return &fakeSubResourceClient{client: c}
761761
}
762762

763763
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
786786
return gvr, nil
787787
}
788788

789-
type fakeSubResourceWriter struct {
789+
type fakeSubResourceClient struct {
790790
client *fakeClient
791791
}
792792

793-
func (sw *fakeSubResourceWriter) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error {
793+
func (sw *fakeSubResourceClient) Get(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceGetOption) error {
794+
panic("fakeSubResourceClient does not support get")
795+
}
796+
797+
func (sw *fakeSubResourceClient) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error {
794798
panic("fakeSubResourceWriter does not support create")
795799
}
796800

797-
func (sw *fakeSubResourceWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
801+
func (sw *fakeSubResourceClient) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
798802
// TODO(droot): This results in full update of the obj (spec + subresources). Need
799803
// a way to update subresource only.
800804
updateOptions := client.SubResourceUpdateOptions{}
@@ -807,7 +811,7 @@ func (sw *fakeSubResourceWriter) Update(ctx context.Context, obj client.Object,
807811
return sw.client.Update(ctx, body, &updateOptions.UpdateOptions)
808812
}
809813

810-
func (sw *fakeSubResourceWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error {
814+
func (sw *fakeSubResourceClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error {
811815
// TODO(droot): This results in full update of the obj (spec + subresources). Need
812816
// a way to update subresource only.
813817

pkg/client/interfaces.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,21 @@ type StatusClient interface {
8585
Status() SubResourceWriter
8686
}
8787

88-
// SubResourceClient knows how to create a client which can update subresource
88+
// SubResourceClientConstructor knows how to create a client which can update subresource
8989
// for kubernetes objects.
90-
type SubResourceClient interface {
91-
// SubResource returns a subresource client for the named subResource. Known
92-
// upstream subResources are:
93-
// - ServiceAccount tokens:
90+
type SubResourceClientConstructor interface {
91+
// SubResourceClientConstructor returns a subresource client for the named subResource. Known
92+
// upstream subResources usages are:
93+
// - ServiceAccount token creation:
9494
// sa := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}}
9595
// token := &authenticationv1.TokenRequest{}
9696
// c.SubResourceClient("token").Create(ctx, sa, token)
9797
//
98-
// - Pod evictions:
98+
// - Pod eviction creation:
9999
// pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}}
100100
// c.SubResourceClient("eviction").Create(ctx, pod, &policyv1.Eviction{})
101101
//
102-
// - Pod bindings:
102+
// - Pod binding creation:
103103
// pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}}
104104
// binding := &corev1.Binding{Target: corev1.ObjectReference{Name: "my-node"}}
105105
// c.SubResourceClient("binding").Create(ctx, pod, binding)
@@ -116,16 +116,26 @@ type SubResourceClient interface {
116116
// }
117117
// c.SubResourceClient("approval").Update(ctx, csr)
118118
//
119+
// - Scale retrieval:
120+
// dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}}
121+
// scale := &autoscalingv1.Scale{}
122+
// c.SubResourceClient("scale").Get(ctx, dep, scale)
123+
//
119124
// - Scale update:
120125
// dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}}
121126
// scale := &autoscalingv1.Scale{Spec: autoscalingv1.ScaleSpec{Replicas: 2}}
122127
// c.SubResourceClient("scale").Update(ctx, dep, client.WithSubResourceBody(scale))
123-
SubResource(subResource string) SubResourceWriter
128+
SubResource(subResource string) SubResourceClient
124129
}
125130

126131
// StatusWriter is kept for backward compatibility.
127132
type StatusWriter = SubResourceWriter
128133

134+
// SubResourceReader knows how to read SubResources
135+
type SubResourceReader interface {
136+
Get(ctx context.Context, obj Object, subResource Object, opts ...SubResourceGetOption) error
137+
}
138+
129139
// SubResourceWriter knows how to update subresource of a Kubernetes object.
130140
type SubResourceWriter interface {
131141
// Create saves the subResource object in the Kubernetes cluster. obj must be a
@@ -142,12 +152,18 @@ type SubResourceWriter interface {
142152
Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error
143153
}
144154

155+
// SubResourceClient knows how to perform CRU operations on Kubernetes objects.
156+
type SubResourceClient interface {
157+
SubResourceReader
158+
SubResourceWriter
159+
}
160+
145161
// Client knows how to perform CRUD operations on Kubernetes objects.
146162
type Client interface {
147163
Reader
148164
Writer
149165
StatusClient
150-
SubResourceClient
166+
SubResourceClientConstructor
151167

152168
// Scheme returns the scheme this client is using.
153169
Scheme() *runtime.Scheme

0 commit comments

Comments
 (0)