2 Copyright 2018 The Kubernetes Authors.
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
8 http://www.apache.org/licenses/LICENSE-2.0
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.
26 "k8s.io/apimachinery/pkg/api/meta"
27 "k8s.io/apimachinery/pkg/labels"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/runtime/schema"
30 "k8s.io/client-go/kubernetes/scheme"
31 "k8s.io/client-go/testing"
33 "sigs.k8s.io/controller-runtime/pkg/client"
34 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
35 "sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
36 logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
40 log = logf.KBLog.WithName("fake-client")
43 type fakeClient struct {
44 tracker testing.ObjectTracker
45 scheme *runtime.Scheme
48 var _ client.Client = &fakeClient{}
50 // NewFakeClient creates a new fake client for testing.
51 // You can choose to initialize it with a slice of runtime.Object.
52 func NewFakeClient(initObjs ...runtime.Object) client.Client {
53 return NewFakeClientWithScheme(scheme.Scheme, initObjs...)
56 // NewFakeClientWithScheme creates a new fake client with the given scheme
58 // You can choose to initialize it with a slice of runtime.Object.
59 func NewFakeClientWithScheme(clientScheme *runtime.Scheme, initObjs ...runtime.Object) client.Client {
60 tracker := testing.NewObjectTracker(clientScheme, scheme.Codecs.UniversalDecoder())
61 for _, obj := range initObjs {
62 err := tracker.Add(obj)
64 log.Error(err, "failed to add object to fake client", "object", obj)
75 func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
76 gvr, err := getGVRFromObject(obj, c.scheme)
80 o, err := c.tracker.Get(gvr, key.Namespace, key.Name)
84 j, err := json.Marshal(o)
88 decoder := scheme.Codecs.UniversalDecoder()
89 _, _, err = decoder.Decode(j, nil, obj)
93 func (c *fakeClient) List(ctx context.Context, opts *client.ListOptions, list runtime.Object) error {
94 gvk, err := getGVKFromList(list, c.scheme)
96 // The old fake client required GVK info in Raw.TypeMeta, so check there
98 if opts.Raw == nil || opts.Raw.TypeMeta.APIVersion == "" || opts.Raw.TypeMeta.Kind == "" {
101 gvk = opts.Raw.TypeMeta.GroupVersionKind()
104 gvr, _ := meta.UnsafeGuessKindToResource(gvk)
105 o, err := c.tracker.List(gvr, gvk, opts.Namespace)
109 j, err := json.Marshal(o)
113 decoder := scheme.Codecs.UniversalDecoder()
114 _, _, err = decoder.Decode(j, nil, list)
119 if opts.LabelSelector != nil {
120 return filterListItems(list, opts.LabelSelector)
125 func filterListItems(list runtime.Object, labSel labels.Selector) error {
126 objs, err := meta.ExtractList(list)
130 filteredObjs, err := objectutil.FilterWithLabels(objs, labSel)
134 err = meta.SetList(list, filteredObjs)
141 func (c *fakeClient) Create(ctx context.Context, obj runtime.Object) error {
142 gvr, err := getGVRFromObject(obj, c.scheme)
146 accessor, err := meta.Accessor(obj)
150 return c.tracker.Create(gvr, obj, accessor.GetNamespace())
153 func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object, opts ...client.DeleteOptionFunc) error {
154 gvr, err := getGVRFromObject(obj, c.scheme)
158 accessor, err := meta.Accessor(obj)
162 //TODO: implement propagation
163 return c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
166 func (c *fakeClient) Update(ctx context.Context, obj runtime.Object) error {
167 gvr, err := getGVRFromObject(obj, c.scheme)
171 accessor, err := meta.Accessor(obj)
175 return c.tracker.Update(gvr, obj, accessor.GetNamespace())
178 func (c *fakeClient) Status() client.StatusWriter {
179 return &fakeStatusWriter{client: c}
182 func getGVRFromObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionResource, error) {
183 gvk, err := apiutil.GVKForObject(obj, scheme)
185 return schema.GroupVersionResource{}, err
187 gvr, _ := meta.UnsafeGuessKindToResource(gvk)
191 func getGVKFromList(list runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) {
192 gvk, err := apiutil.GVKForObject(list, scheme)
194 return schema.GroupVersionKind{}, err
197 if gvk.Kind == "List" {
198 return schema.GroupVersionKind{}, fmt.Errorf("cannot derive GVK for generic List type %T (kind %q)", list, gvk)
201 if !strings.HasSuffix(gvk.Kind, "List") {
202 return schema.GroupVersionKind{}, fmt.Errorf("non-list type %T (kind %q) passed as output", list, gvk)
204 // we need the non-list GVK, so chop off the "List" from the end of the kind
205 gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
209 type fakeStatusWriter struct {
213 func (sw *fakeStatusWriter) Update(ctx context.Context, obj runtime.Object) error {
214 // TODO(droot): This results in full update of the obj (spec + status). Need
215 // a way to update status field only.
216 return sw.client.Update(ctx, obj)