From 8f3407b22850a0f812220d8a030ce131f089550a Mon Sep 17 00:00:00 2001 From: Grant Rodgers Date: Wed, 14 Nov 2018 13:09:56 -0800 Subject: [PATCH] Add scheme support to fake client Adds a NewFakeClientWithScheme function that takes a *runtime.Scheme as its first argument. NewFakeClient is now a wrapper around NewFakeClientWithScheme, passing it the default scheme.Scheme. As part of this, the scheme can now be used to derive the correct List GVK. This eliminates the need to use a metav1.List and opts.Raw.TypeMeta. Now it's possible to pass in a typed List, same as with the real client. --- pkg/client/fake/client.go | 50 ++++++-- pkg/client/fake/client_test.go | 201 ++++++++++++++++++--------------- 2 files changed, 152 insertions(+), 99 deletions(-) diff --git a/pkg/client/fake/client.go b/pkg/client/fake/client.go index 8597cbde6c..0f61457f2f 100644 --- a/pkg/client/fake/client.go +++ b/pkg/client/fake/client.go @@ -19,7 +19,9 @@ package fake import ( "context" "encoding/json" + "fmt" "os" + "strings" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" @@ -38,6 +40,7 @@ var ( type fakeClient struct { tracker testing.ObjectTracker + scheme *runtime.Scheme } var _ client.Client = &fakeClient{} @@ -45,7 +48,14 @@ var _ client.Client = &fakeClient{} // NewFakeClient creates a new fake client for testing. // You can choose to initialize it with a slice of runtime.Object. func NewFakeClient(initObjs ...runtime.Object) client.Client { - tracker := testing.NewObjectTracker(scheme.Scheme, scheme.Codecs.UniversalDecoder()) + return NewFakeClientWithScheme(scheme.Scheme, initObjs...) +} + +// NewFakeClientWithScheme creates a new fake client with the given scheme +// for testing. +// You can choose to initialize it with a slice of runtime.Object. +func NewFakeClientWithScheme(clientScheme *runtime.Scheme, initObjs ...runtime.Object) client.Client { + tracker := testing.NewObjectTracker(clientScheme, scheme.Codecs.UniversalDecoder()) for _, obj := range initObjs { err := tracker.Add(obj) if err != nil { @@ -56,11 +66,12 @@ func NewFakeClient(initObjs ...runtime.Object) client.Client { } return &fakeClient{ tracker: tracker, + scheme: clientScheme, } } func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj runtime.Object) error { - gvr, err := getGVRFromObject(obj) + gvr, err := getGVRFromObject(obj, c.scheme) if err != nil { return err } @@ -78,7 +89,16 @@ func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj runtime. } func (c *fakeClient) List(ctx context.Context, opts *client.ListOptions, list runtime.Object) error { - gvk := opts.Raw.TypeMeta.GroupVersionKind() + gvk, err := getGVKFromList(list, c.scheme) + if err != nil { + // The old fake client required GVK info in Raw.TypeMeta, so check there + // before giving up + if opts.Raw.TypeMeta.APIVersion == "" || opts.Raw.TypeMeta.Kind == "" { + return err + } + gvk = opts.Raw.TypeMeta.GroupVersionKind() + } + gvr, _ := meta.UnsafeGuessKindToResource(gvk) o, err := c.tracker.List(gvr, gvk, opts.Namespace) if err != nil { @@ -94,7 +114,7 @@ func (c *fakeClient) List(ctx context.Context, opts *client.ListOptions, list ru } func (c *fakeClient) Create(ctx context.Context, obj runtime.Object) error { - gvr, err := getGVRFromObject(obj) + gvr, err := getGVRFromObject(obj, c.scheme) if err != nil { return err } @@ -106,7 +126,7 @@ func (c *fakeClient) Create(ctx context.Context, obj runtime.Object) error { } func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object, opts ...client.DeleteOptionFunc) error { - gvr, err := getGVRFromObject(obj) + gvr, err := getGVRFromObject(obj, c.scheme) if err != nil { return err } @@ -119,7 +139,7 @@ func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object, opts ...cli } func (c *fakeClient) Update(ctx context.Context, obj runtime.Object) error { - gvr, err := getGVRFromObject(obj) + gvr, err := getGVRFromObject(obj, c.scheme) if err != nil { return err } @@ -134,8 +154,8 @@ func (c *fakeClient) Status() client.StatusWriter { return &fakeStatusWriter{client: c} } -func getGVRFromObject(obj runtime.Object) (schema.GroupVersionResource, error) { - gvk, err := apiutil.GVKForObject(obj, scheme.Scheme) +func getGVRFromObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionResource, error) { + gvk, err := apiutil.GVKForObject(obj, scheme) if err != nil { return schema.GroupVersionResource{}, err } @@ -143,6 +163,20 @@ func getGVRFromObject(obj runtime.Object) (schema.GroupVersionResource, error) { return gvr, nil } +func getGVKFromList(list runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) { + gvk, err := apiutil.GVKForObject(list, scheme) + if err != nil { + return schema.GroupVersionKind{}, err + } + + if !strings.HasSuffix(gvk.Kind, "List") { + return schema.GroupVersionKind{}, fmt.Errorf("non-list type %T (kind %q) passed as output", list, gvk) + } + // we need the non-list GVK, so chop off the "List" from the end of the kind + gvk.Kind = gvk.Kind[:len(gvk.Kind)-4] + return gvk, nil +} + type fakeStatusWriter struct { client *fakeClient } diff --git a/pkg/client/fake/client_test.go b/pkg/client/fake/client_test.go index 635c8c1058..fc2a6c9dbe 100644 --- a/pkg/client/fake/client_test.go +++ b/pkg/client/fake/client_test.go @@ -35,7 +35,7 @@ var _ = Describe("Fake client", func() { var cm *corev1.ConfigMap var cl client.Client - BeforeEach(func(done Done) { + BeforeEach(func() { dep = &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "test-deployment", @@ -51,106 +51,125 @@ var _ = Describe("Fake client", func() { "test-key": "test-value", }, } - cl = NewFakeClient(dep, cm) - close(done) }) - It("should be able to Get", func() { - By("Getting a deployment") - namespacedName := types.NamespacedName{ - Name: "test-deployment", - Namespace: "ns1", - } - obj := &appsv1.Deployment{} - err := cl.Get(nil, namespacedName, obj) - Expect(err).To(BeNil()) - Expect(obj).To(Equal(dep)) - }) - - It("should be able to List", func() { - By("Listing all deployments in a namespace") - list := &metav1.List{} - err := cl.List(nil, &client.ListOptions{ - Namespace: "ns1", - Raw: &metav1.ListOptions{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "apps/v1", - Kind: "Deployment", + AssertClientBehavior := func() { + It("should be able to Get", func() { + By("Getting a deployment") + namespacedName := types.NamespacedName{ + Name: "test-deployment", + Namespace: "ns1", + } + obj := &appsv1.Deployment{} + err := cl.Get(nil, namespacedName, obj) + Expect(err).To(BeNil()) + Expect(obj).To(Equal(dep)) + }) + + It("should be able to List", func() { + By("Listing all deployments in a namespace") + list := &metav1.List{} + err := cl.List(nil, &client.ListOptions{ + Namespace: "ns1", + Raw: &metav1.ListOptions{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, }, - }, - }, list) - Expect(err).To(BeNil()) - Expect(list.Items).To(HaveLen(1)) - j, err := json.Marshal(dep) - Expect(err).To(BeNil()) - expectedDep := runtime.RawExtension{Raw: j} - Expect(list.Items).To(ConsistOf(expectedDep)) - }) + }, list) + Expect(err).To(BeNil()) + Expect(list.Items).To(HaveLen(1)) + j, err := json.Marshal(dep) + Expect(err).To(BeNil()) + expectedDep := runtime.RawExtension{Raw: j} + Expect(list.Items).To(ConsistOf(expectedDep)) + }) + + It("should be able to Create", func() { + By("Creating a new configmap") + newcm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "new-test-cm", + Namespace: "ns2", + }, + } + err := cl.Create(nil, newcm) + Expect(err).To(BeNil()) - It("should be able to Create", func() { - By("Creating a new configmap") - newcm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ + By("Getting the new configmap") + namespacedName := types.NamespacedName{ Name: "new-test-cm", Namespace: "ns2", - }, - } - err := cl.Create(nil, newcm) - Expect(err).To(BeNil()) - - By("Getting the new configmap") - namespacedName := types.NamespacedName{ - Name: "new-test-cm", - Namespace: "ns2", - } - obj := &corev1.ConfigMap{} - err = cl.Get(nil, namespacedName, obj) - Expect(err).To(BeNil()) - Expect(obj).To(Equal(newcm)) - }) + } + obj := &corev1.ConfigMap{} + err = cl.Get(nil, namespacedName, obj) + Expect(err).To(BeNil()) + Expect(obj).To(Equal(newcm)) + }) + + It("should be able to Update", func() { + By("Updating a new configmap") + newcm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cm", + Namespace: "ns2", + }, + Data: map[string]string{ + "test-key": "new-value", + }, + } + err := cl.Update(nil, newcm) + Expect(err).To(BeNil()) - It("should be able to Update", func() { - By("Updating a new configmap") - newcm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ + By("Getting the new configmap") + namespacedName := types.NamespacedName{ Name: "test-cm", Namespace: "ns2", - }, - Data: map[string]string{ - "test-key": "new-value", - }, - } - err := cl.Update(nil, newcm) - Expect(err).To(BeNil()) - - By("Getting the new configmap") - namespacedName := types.NamespacedName{ - Name: "test-cm", - Namespace: "ns2", - } - obj := &corev1.ConfigMap{} - err = cl.Get(nil, namespacedName, obj) - Expect(err).To(BeNil()) - Expect(obj).To(Equal(newcm)) + } + obj := &corev1.ConfigMap{} + err = cl.Get(nil, namespacedName, obj) + Expect(err).To(BeNil()) + Expect(obj).To(Equal(newcm)) + }) + + It("should be able to Delete", func() { + By("Deleting a deployment") + err := cl.Delete(nil, dep) + Expect(err).To(BeNil()) + + By("Listing all deployments in the namespace") + list := &metav1.List{} + err = cl.List(nil, &client.ListOptions{ + Namespace: "ns1", + Raw: &metav1.ListOptions{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + }, + }, list) + Expect(err).To(BeNil()) + Expect(list.Items).To(HaveLen(0)) + }) + } + + Context("with default scheme.Scheme", func() { + BeforeEach(func(done Done) { + cl = NewFakeClient(dep, cm) + close(done) + }) + AssertClientBehavior() }) - It("should be able to Delete", func() { - By("Deleting a deployment") - err := cl.Delete(nil, dep) - Expect(err).To(BeNil()) - - By("Listing all deployments in the namespace") - list := &metav1.List{} - err = cl.List(nil, &client.ListOptions{ - Namespace: "ns1", - Raw: &metav1.ListOptions{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, - }, - }, list) - Expect(err).To(BeNil()) - Expect(list.Items).To(HaveLen(0)) + Context("with given scheme", func() { + BeforeEach(func(done Done) { + scheme := runtime.NewScheme() + corev1.AddToScheme(scheme) + appsv1.AddToScheme(scheme) + cl = NewFakeClientWithScheme(scheme, dep, cm) + close(done) + }) + AssertClientBehavior() }) })