Skip to content

Commit

Permalink
Merge pull request #2094 from alvaroaleman/subresoruce-get
Browse files Browse the repository at this point in the history
⚠️ Add Get functionality to SubResourceClient
  • Loading branch information
k8s-ci-robot authored Dec 14, 2022
2 parents 8738e91 + 7eb8134 commit 69f0938
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 63 deletions.
85 changes: 64 additions & 21 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package client

import (
"context"
"errors"
"fmt"
"strings"

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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...)
}
}
43 changes: 42 additions & 1 deletion pkg/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"encoding/json"
"fmt"
"reflect"
"sync/atomic"
"time"

Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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())
Expand Down
22 changes: 13 additions & 9 deletions pkg/client/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)...)
}
16 changes: 10 additions & 6 deletions pkg/client/fake/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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{}
Expand All @@ -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.

Expand Down
34 changes: 25 additions & 9 deletions pkg/client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand Down
Loading

0 comments on commit 69f0938

Please sign in to comment.