Skip to content

Commit a1ff5a8

Browse files
author
Mengqi Yu
committed
implement a fake client
1 parent 6c84c31 commit a1ff5a8

File tree

4 files changed

+353
-0
lines changed

4 files changed

+353
-0
lines changed

pkg/client/fake/client.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package fake
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"log"
23+
24+
"k8s.io/apimachinery/pkg/api/meta"
25+
"k8s.io/apimachinery/pkg/runtime"
26+
"k8s.io/apimachinery/pkg/runtime/schema"
27+
"k8s.io/client-go/kubernetes/scheme"
28+
"k8s.io/client-go/testing"
29+
30+
"sigs.k8s.io/controller-runtime/pkg/client"
31+
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
32+
)
33+
34+
type fakeClient struct {
35+
tracker testing.ObjectTracker
36+
}
37+
38+
var _ client.Client = &fakeClient{}
39+
40+
// NewFakeClient creates a new fake client for testing.
41+
// You can choose to initialize it with a slice of runtime.Object.
42+
func NewFakeClient(initObjs ...runtime.Object) client.Client {
43+
tracker := testing.NewObjectTracker(scheme.Scheme, scheme.Codecs.UniversalDecoder())
44+
for _, obj := range initObjs {
45+
err := tracker.Add(obj)
46+
if err != nil {
47+
log.Fatalf("failed to add object: %#v, error: %v", obj, err)
48+
return nil
49+
}
50+
}
51+
return &fakeClient{
52+
tracker: tracker,
53+
}
54+
}
55+
56+
func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
57+
gvr, err := getGVRFromObject(obj)
58+
if err != nil {
59+
return err
60+
}
61+
o, err := c.tracker.Get(gvr, key.Namespace, key.Name)
62+
if err != nil {
63+
return err
64+
}
65+
j, err := json.Marshal(o)
66+
if err != nil {
67+
return err
68+
}
69+
decoder := scheme.Codecs.UniversalDecoder()
70+
_, _, err = decoder.Decode(j, nil, obj)
71+
return err
72+
}
73+
74+
func (c *fakeClient) List(ctx context.Context, opts *client.ListOptions, list runtime.Object) error {
75+
gvk := opts.Raw.TypeMeta.GroupVersionKind()
76+
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
77+
o, err := c.tracker.List(gvr, gvk, opts.Namespace)
78+
if err != nil {
79+
return err
80+
}
81+
j, err := json.Marshal(o)
82+
if err != nil {
83+
return err
84+
}
85+
decoder := scheme.Codecs.UniversalDecoder()
86+
_, _, err = decoder.Decode(j, nil, list)
87+
return err
88+
}
89+
90+
func (c *fakeClient) Create(ctx context.Context, obj runtime.Object) error {
91+
gvr, err := getGVRFromObject(obj)
92+
if err != nil {
93+
return err
94+
}
95+
accessor, err := meta.Accessor(obj)
96+
if err != nil {
97+
return err
98+
}
99+
return c.tracker.Create(gvr, obj, accessor.GetNamespace())
100+
}
101+
102+
func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object) error {
103+
gvr, err := getGVRFromObject(obj)
104+
if err != nil {
105+
return err
106+
}
107+
accessor, err := meta.Accessor(obj)
108+
if err != nil {
109+
return err
110+
}
111+
return c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
112+
}
113+
114+
func (c *fakeClient) Update(ctx context.Context, obj runtime.Object) error {
115+
gvr, err := getGVRFromObject(obj)
116+
if err != nil {
117+
return err
118+
}
119+
accessor, err := meta.Accessor(obj)
120+
if err != nil {
121+
return err
122+
}
123+
return c.tracker.Update(gvr, obj, accessor.GetNamespace())
124+
}
125+
126+
func getGVRFromObject(obj runtime.Object) (schema.GroupVersionResource, error) {
127+
gvk, err := apiutil.GVKForObject(obj, scheme.Scheme)
128+
if err != nil {
129+
return schema.GroupVersionResource{}, err
130+
}
131+
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
132+
return gvr, nil
133+
}

pkg/client/fake/client_suite_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package fake
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/ginkgo"
23+
. "github.com/onsi/gomega"
24+
"sigs.k8s.io/controller-runtime/pkg/test"
25+
26+
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
27+
)
28+
29+
func TestSource(t *testing.T) {
30+
RegisterFailHandler(Fail)
31+
RunSpecsWithDefaultAndCustomReporters(t, "Controller Integration Suite", []Reporter{test.NewlineReporter{}})
32+
}
33+
34+
var _ = BeforeSuite(func(done Done) {
35+
logf.SetLogger(logf.ZapLogger(false))
36+
close(done)
37+
}, 60)

pkg/client/fake/client_test.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package fake
18+
19+
import (
20+
"encoding/json"
21+
22+
. "github.com/onsi/ginkgo"
23+
. "github.com/onsi/gomega"
24+
25+
appsv1 "k8s.io/api/apps/v1"
26+
corev1 "k8s.io/api/core/v1"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/apimachinery/pkg/types"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
31+
)
32+
33+
var _ = Describe("Fake client", func() {
34+
var dep *appsv1.Deployment
35+
var cm *corev1.ConfigMap
36+
var cl client.Client
37+
38+
BeforeEach(func(done Done) {
39+
dep = &appsv1.Deployment{
40+
ObjectMeta: metav1.ObjectMeta{
41+
Name: "test-deployment",
42+
Namespace: "ns1",
43+
},
44+
}
45+
cm = &corev1.ConfigMap{
46+
ObjectMeta: metav1.ObjectMeta{
47+
Name: "test-cm",
48+
Namespace: "ns2",
49+
},
50+
Data: map[string]string{
51+
"test-key": "test-value",
52+
},
53+
}
54+
cl = NewFakeClient(dep, cm)
55+
close(done)
56+
})
57+
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",
79+
},
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+
})
89+
90+
It("should be able to Create", func() {
91+
By("Creating a new configmap")
92+
newcm := &corev1.ConfigMap{
93+
ObjectMeta: metav1.ObjectMeta{
94+
Name: "new-test-cm",
95+
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+
})
111+
112+
It("should be able to Update", func() {
113+
By("Updating a new configmap")
114+
newcm := &corev1.ConfigMap{
115+
ObjectMeta: metav1.ObjectMeta{
116+
Name: "test-cm",
117+
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))
135+
})
136+
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))
155+
})
156+
})

pkg/client/fake/doc.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
/*
18+
Package fake provides a fake client for testing.
19+
20+
An fake client is backed by its simple object store indexed by GroupVersionResource.
21+
You can create a fake client with optional objects.
22+
23+
client := NewFakeClient(initObjs...) // initObjs is a slice of runtime.Object
24+
25+
You can invoke the methods defined in the Client interface.
26+
*/
27+
package fake

0 commit comments

Comments
 (0)