Skip to content

Commit

Permalink
Add scheme support to fake client
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
grantr committed Nov 14, 2018
1 parent 0f0740d commit 8f3407b
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 99 deletions.
50 changes: 42 additions & 8 deletions pkg/client/fake/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ package fake
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -38,14 +40,22 @@ var (

type fakeClient struct {
tracker testing.ObjectTracker
scheme *runtime.Scheme
}

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 {
Expand All @@ -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
}
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -134,15 +154,29 @@ 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
}
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
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
}
Expand Down
201 changes: 110 additions & 91 deletions pkg/client/fake/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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()
})
})

0 comments on commit 8f3407b

Please sign in to comment.