Skip to content

Commit 8f3407b

Browse files
committed
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.
1 parent 0f0740d commit 8f3407b

File tree

2 files changed

+152
-99
lines changed

2 files changed

+152
-99
lines changed

pkg/client/fake/client.go

+42-8
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ package fake
1919
import (
2020
"context"
2121
"encoding/json"
22+
"fmt"
2223
"os"
24+
"strings"
2325

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

3941
type fakeClient struct {
4042
tracker testing.ObjectTracker
43+
scheme *runtime.Scheme
4144
}
4245

4346
var _ client.Client = &fakeClient{}
4447

4548
// NewFakeClient creates a new fake client for testing.
4649
// You can choose to initialize it with a slice of runtime.Object.
4750
func NewFakeClient(initObjs ...runtime.Object) client.Client {
48-
tracker := testing.NewObjectTracker(scheme.Scheme, scheme.Codecs.UniversalDecoder())
51+
return NewFakeClientWithScheme(scheme.Scheme, initObjs...)
52+
}
53+
54+
// NewFakeClientWithScheme creates a new fake client with the given scheme
55+
// for testing.
56+
// You can choose to initialize it with a slice of runtime.Object.
57+
func NewFakeClientWithScheme(clientScheme *runtime.Scheme, initObjs ...runtime.Object) client.Client {
58+
tracker := testing.NewObjectTracker(clientScheme, scheme.Codecs.UniversalDecoder())
4959
for _, obj := range initObjs {
5060
err := tracker.Add(obj)
5161
if err != nil {
@@ -56,11 +66,12 @@ func NewFakeClient(initObjs ...runtime.Object) client.Client {
5666
}
5767
return &fakeClient{
5868
tracker: tracker,
69+
scheme: clientScheme,
5970
}
6071
}
6172

6273
func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
63-
gvr, err := getGVRFromObject(obj)
74+
gvr, err := getGVRFromObject(obj, c.scheme)
6475
if err != nil {
6576
return err
6677
}
@@ -78,7 +89,16 @@ func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj runtime.
7889
}
7990

8091
func (c *fakeClient) List(ctx context.Context, opts *client.ListOptions, list runtime.Object) error {
81-
gvk := opts.Raw.TypeMeta.GroupVersionKind()
92+
gvk, err := getGVKFromList(list, c.scheme)
93+
if err != nil {
94+
// The old fake client required GVK info in Raw.TypeMeta, so check there
95+
// before giving up
96+
if opts.Raw.TypeMeta.APIVersion == "" || opts.Raw.TypeMeta.Kind == "" {
97+
return err
98+
}
99+
gvk = opts.Raw.TypeMeta.GroupVersionKind()
100+
}
101+
82102
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
83103
o, err := c.tracker.List(gvr, gvk, opts.Namespace)
84104
if err != nil {
@@ -94,7 +114,7 @@ func (c *fakeClient) List(ctx context.Context, opts *client.ListOptions, list ru
94114
}
95115

96116
func (c *fakeClient) Create(ctx context.Context, obj runtime.Object) error {
97-
gvr, err := getGVRFromObject(obj)
117+
gvr, err := getGVRFromObject(obj, c.scheme)
98118
if err != nil {
99119
return err
100120
}
@@ -106,7 +126,7 @@ func (c *fakeClient) Create(ctx context.Context, obj runtime.Object) error {
106126
}
107127

108128
func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object, opts ...client.DeleteOptionFunc) error {
109-
gvr, err := getGVRFromObject(obj)
129+
gvr, err := getGVRFromObject(obj, c.scheme)
110130
if err != nil {
111131
return err
112132
}
@@ -119,7 +139,7 @@ func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object, opts ...cli
119139
}
120140

121141
func (c *fakeClient) Update(ctx context.Context, obj runtime.Object) error {
122-
gvr, err := getGVRFromObject(obj)
142+
gvr, err := getGVRFromObject(obj, c.scheme)
123143
if err != nil {
124144
return err
125145
}
@@ -134,15 +154,29 @@ func (c *fakeClient) Status() client.StatusWriter {
134154
return &fakeStatusWriter{client: c}
135155
}
136156

137-
func getGVRFromObject(obj runtime.Object) (schema.GroupVersionResource, error) {
138-
gvk, err := apiutil.GVKForObject(obj, scheme.Scheme)
157+
func getGVRFromObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionResource, error) {
158+
gvk, err := apiutil.GVKForObject(obj, scheme)
139159
if err != nil {
140160
return schema.GroupVersionResource{}, err
141161
}
142162
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
143163
return gvr, nil
144164
}
145165

166+
func getGVKFromList(list runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) {
167+
gvk, err := apiutil.GVKForObject(list, scheme)
168+
if err != nil {
169+
return schema.GroupVersionKind{}, err
170+
}
171+
172+
if !strings.HasSuffix(gvk.Kind, "List") {
173+
return schema.GroupVersionKind{}, fmt.Errorf("non-list type %T (kind %q) passed as output", list, gvk)
174+
}
175+
// we need the non-list GVK, so chop off the "List" from the end of the kind
176+
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
177+
return gvk, nil
178+
}
179+
146180
type fakeStatusWriter struct {
147181
client *fakeClient
148182
}

pkg/client/fake/client_test.go

+110-91
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var _ = Describe("Fake client", func() {
3535
var cm *corev1.ConfigMap
3636
var cl client.Client
3737

38-
BeforeEach(func(done Done) {
38+
BeforeEach(func() {
3939
dep = &appsv1.Deployment{
4040
ObjectMeta: metav1.ObjectMeta{
4141
Name: "test-deployment",
@@ -51,106 +51,125 @@ var _ = Describe("Fake client", func() {
5151
"test-key": "test-value",
5252
},
5353
}
54-
cl = NewFakeClient(dep, cm)
55-
close(done)
5654
})
5755

58-
It("should be able to Get", func() {
59-
By("Getting a deployment")
60-
namespacedName := types.NamespacedName{
61-
Name: "test-deployment",
62-
Namespace: "ns1",
63-
}
64-
obj := &appsv1.Deployment{}
65-
err := cl.Get(nil, namespacedName, obj)
66-
Expect(err).To(BeNil())
67-
Expect(obj).To(Equal(dep))
68-
})
69-
70-
It("should be able to List", func() {
71-
By("Listing all deployments in a namespace")
72-
list := &metav1.List{}
73-
err := cl.List(nil, &client.ListOptions{
74-
Namespace: "ns1",
75-
Raw: &metav1.ListOptions{
76-
TypeMeta: metav1.TypeMeta{
77-
APIVersion: "apps/v1",
78-
Kind: "Deployment",
56+
AssertClientBehavior := func() {
57+
It("should be able to Get", func() {
58+
By("Getting a deployment")
59+
namespacedName := types.NamespacedName{
60+
Name: "test-deployment",
61+
Namespace: "ns1",
62+
}
63+
obj := &appsv1.Deployment{}
64+
err := cl.Get(nil, namespacedName, obj)
65+
Expect(err).To(BeNil())
66+
Expect(obj).To(Equal(dep))
67+
})
68+
69+
It("should be able to List", func() {
70+
By("Listing all deployments in a namespace")
71+
list := &metav1.List{}
72+
err := cl.List(nil, &client.ListOptions{
73+
Namespace: "ns1",
74+
Raw: &metav1.ListOptions{
75+
TypeMeta: metav1.TypeMeta{
76+
APIVersion: "apps/v1",
77+
Kind: "Deployment",
78+
},
7979
},
80-
},
81-
}, list)
82-
Expect(err).To(BeNil())
83-
Expect(list.Items).To(HaveLen(1))
84-
j, err := json.Marshal(dep)
85-
Expect(err).To(BeNil())
86-
expectedDep := runtime.RawExtension{Raw: j}
87-
Expect(list.Items).To(ConsistOf(expectedDep))
88-
})
80+
}, list)
81+
Expect(err).To(BeNil())
82+
Expect(list.Items).To(HaveLen(1))
83+
j, err := json.Marshal(dep)
84+
Expect(err).To(BeNil())
85+
expectedDep := runtime.RawExtension{Raw: j}
86+
Expect(list.Items).To(ConsistOf(expectedDep))
87+
})
88+
89+
It("should be able to Create", func() {
90+
By("Creating a new configmap")
91+
newcm := &corev1.ConfigMap{
92+
ObjectMeta: metav1.ObjectMeta{
93+
Name: "new-test-cm",
94+
Namespace: "ns2",
95+
},
96+
}
97+
err := cl.Create(nil, newcm)
98+
Expect(err).To(BeNil())
8999

90-
It("should be able to Create", func() {
91-
By("Creating a new configmap")
92-
newcm := &corev1.ConfigMap{
93-
ObjectMeta: metav1.ObjectMeta{
100+
By("Getting the new configmap")
101+
namespacedName := types.NamespacedName{
94102
Name: "new-test-cm",
95103
Namespace: "ns2",
96-
},
97-
}
98-
err := cl.Create(nil, newcm)
99-
Expect(err).To(BeNil())
100-
101-
By("Getting the new configmap")
102-
namespacedName := types.NamespacedName{
103-
Name: "new-test-cm",
104-
Namespace: "ns2",
105-
}
106-
obj := &corev1.ConfigMap{}
107-
err = cl.Get(nil, namespacedName, obj)
108-
Expect(err).To(BeNil())
109-
Expect(obj).To(Equal(newcm))
110-
})
104+
}
105+
obj := &corev1.ConfigMap{}
106+
err = cl.Get(nil, namespacedName, obj)
107+
Expect(err).To(BeNil())
108+
Expect(obj).To(Equal(newcm))
109+
})
110+
111+
It("should be able to Update", func() {
112+
By("Updating a new configmap")
113+
newcm := &corev1.ConfigMap{
114+
ObjectMeta: metav1.ObjectMeta{
115+
Name: "test-cm",
116+
Namespace: "ns2",
117+
},
118+
Data: map[string]string{
119+
"test-key": "new-value",
120+
},
121+
}
122+
err := cl.Update(nil, newcm)
123+
Expect(err).To(BeNil())
111124

112-
It("should be able to Update", func() {
113-
By("Updating a new configmap")
114-
newcm := &corev1.ConfigMap{
115-
ObjectMeta: metav1.ObjectMeta{
125+
By("Getting the new configmap")
126+
namespacedName := types.NamespacedName{
116127
Name: "test-cm",
117128
Namespace: "ns2",
118-
},
119-
Data: map[string]string{
120-
"test-key": "new-value",
121-
},
122-
}
123-
err := cl.Update(nil, newcm)
124-
Expect(err).To(BeNil())
125-
126-
By("Getting the new configmap")
127-
namespacedName := types.NamespacedName{
128-
Name: "test-cm",
129-
Namespace: "ns2",
130-
}
131-
obj := &corev1.ConfigMap{}
132-
err = cl.Get(nil, namespacedName, obj)
133-
Expect(err).To(BeNil())
134-
Expect(obj).To(Equal(newcm))
129+
}
130+
obj := &corev1.ConfigMap{}
131+
err = cl.Get(nil, namespacedName, obj)
132+
Expect(err).To(BeNil())
133+
Expect(obj).To(Equal(newcm))
134+
})
135+
136+
It("should be able to Delete", func() {
137+
By("Deleting a deployment")
138+
err := cl.Delete(nil, dep)
139+
Expect(err).To(BeNil())
140+
141+
By("Listing all deployments in the namespace")
142+
list := &metav1.List{}
143+
err = cl.List(nil, &client.ListOptions{
144+
Namespace: "ns1",
145+
Raw: &metav1.ListOptions{
146+
TypeMeta: metav1.TypeMeta{
147+
APIVersion: "apps/v1",
148+
Kind: "Deployment",
149+
},
150+
},
151+
}, list)
152+
Expect(err).To(BeNil())
153+
Expect(list.Items).To(HaveLen(0))
154+
})
155+
}
156+
157+
Context("with default scheme.Scheme", func() {
158+
BeforeEach(func(done Done) {
159+
cl = NewFakeClient(dep, cm)
160+
close(done)
161+
})
162+
AssertClientBehavior()
135163
})
136164

137-
It("should be able to Delete", func() {
138-
By("Deleting a deployment")
139-
err := cl.Delete(nil, dep)
140-
Expect(err).To(BeNil())
141-
142-
By("Listing all deployments in the namespace")
143-
list := &metav1.List{}
144-
err = cl.List(nil, &client.ListOptions{
145-
Namespace: "ns1",
146-
Raw: &metav1.ListOptions{
147-
TypeMeta: metav1.TypeMeta{
148-
APIVersion: "apps/v1",
149-
Kind: "Deployment",
150-
},
151-
},
152-
}, list)
153-
Expect(err).To(BeNil())
154-
Expect(list.Items).To(HaveLen(0))
165+
Context("with given scheme", func() {
166+
BeforeEach(func(done Done) {
167+
scheme := runtime.NewScheme()
168+
corev1.AddToScheme(scheme)
169+
appsv1.AddToScheme(scheme)
170+
cl = NewFakeClientWithScheme(scheme, dep, cm)
171+
close(done)
172+
})
173+
AssertClientBehavior()
155174
})
156175
})

0 commit comments

Comments
 (0)