1/*
2Copyright 2018 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package fake
18
19import (
20	"context"
21	"fmt"
22	"strings"
23
24	"k8s.io/apimachinery/pkg/api/meta"
25	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26	"k8s.io/apimachinery/pkg/labels"
27	"k8s.io/apimachinery/pkg/runtime"
28	"k8s.io/apimachinery/pkg/runtime/schema"
29	"k8s.io/apimachinery/pkg/runtime/serializer"
30	"k8s.io/apimachinery/pkg/types"
31	"k8s.io/apimachinery/pkg/watch"
32	"k8s.io/client-go/metadata"
33	"k8s.io/client-go/testing"
34)
35
36// MetadataClient assists in creating fake objects for use when testing, since metadata.Getter
37// does not expose create
38type MetadataClient interface {
39	metadata.Getter
40	CreateFake(obj *metav1.PartialObjectMetadata, opts metav1.CreateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error)
41	UpdateFake(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error)
42}
43
44// NewSimpleMetadataClient creates a new client that will use the provided scheme and respond with the
45// provided objects when requests are made. It will track actions made to the client which can be checked
46// with GetActions().
47func NewSimpleMetadataClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeMetadataClient {
48	gvkFakeList := schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "List"}
49	if !scheme.Recognizes(gvkFakeList) {
50		// In order to use List with this client, you have to have the v1.List registered in your scheme, since this is a test
51		// type we modify the input scheme
52		scheme.AddKnownTypeWithName(gvkFakeList, &metav1.List{})
53	}
54
55	codecs := serializer.NewCodecFactory(scheme)
56	o := testing.NewObjectTracker(scheme, codecs.UniversalDeserializer())
57	for _, obj := range objects {
58		if err := o.Add(obj); err != nil {
59			panic(err)
60		}
61	}
62
63	cs := &FakeMetadataClient{scheme: scheme}
64	cs.AddReactor("*", "*", testing.ObjectReaction(o))
65	cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
66		gvr := action.GetResource()
67		ns := action.GetNamespace()
68		watch, err := o.Watch(gvr, ns)
69		if err != nil {
70			return false, nil, err
71		}
72		return true, watch, nil
73	})
74
75	return cs
76}
77
78// FakeMetadataClient implements clientset.Interface. Meant to be embedded into a
79// struct to get a default implementation. This makes faking out just the method
80// you want to test easier.
81type FakeMetadataClient struct {
82	testing.Fake
83	scheme *runtime.Scheme
84}
85
86type metadataResourceClient struct {
87	client    *FakeMetadataClient
88	namespace string
89	resource  schema.GroupVersionResource
90}
91
92var _ metadata.Interface = &FakeMetadataClient{}
93
94// Resource returns an interface for accessing the provided resource.
95func (c *FakeMetadataClient) Resource(resource schema.GroupVersionResource) metadata.Getter {
96	return &metadataResourceClient{client: c, resource: resource}
97}
98
99// Namespace returns an interface for accessing the current resource in the specified
100// namespace.
101func (c *metadataResourceClient) Namespace(ns string) metadata.ResourceInterface {
102	ret := *c
103	ret.namespace = ns
104	return &ret
105}
106
107// CreateFake records the object creation and processes it via the reactor.
108func (c *metadataResourceClient) CreateFake(obj *metav1.PartialObjectMetadata, opts metav1.CreateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
109	var uncastRet runtime.Object
110	var err error
111	switch {
112	case len(c.namespace) == 0 && len(subresources) == 0:
113		uncastRet, err = c.client.Fake.
114			Invokes(testing.NewRootCreateAction(c.resource, obj), obj)
115
116	case len(c.namespace) == 0 && len(subresources) > 0:
117		var accessor metav1.Object // avoid shadowing err
118		accessor, err = meta.Accessor(obj)
119		if err != nil {
120			return nil, err
121		}
122		name := accessor.GetName()
123		uncastRet, err = c.client.Fake.
124			Invokes(testing.NewRootCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), obj), obj)
125
126	case len(c.namespace) > 0 && len(subresources) == 0:
127		uncastRet, err = c.client.Fake.
128			Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj)
129
130	case len(c.namespace) > 0 && len(subresources) > 0:
131		var accessor metav1.Object // avoid shadowing err
132		accessor, err = meta.Accessor(obj)
133		if err != nil {
134			return nil, err
135		}
136		name := accessor.GetName()
137		uncastRet, err = c.client.Fake.
138			Invokes(testing.NewCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), c.namespace, obj), obj)
139
140	}
141
142	if err != nil {
143		return nil, err
144	}
145	if uncastRet == nil {
146		return nil, err
147	}
148	ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
149	if !ok {
150		return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
151	}
152	return ret, err
153}
154
155// UpdateFake records the object update and processes it via the reactor.
156func (c *metadataResourceClient) UpdateFake(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
157	var uncastRet runtime.Object
158	var err error
159	switch {
160	case len(c.namespace) == 0 && len(subresources) == 0:
161		uncastRet, err = c.client.Fake.
162			Invokes(testing.NewRootUpdateAction(c.resource, obj), obj)
163
164	case len(c.namespace) == 0 && len(subresources) > 0:
165		uncastRet, err = c.client.Fake.
166			Invokes(testing.NewRootUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), obj), obj)
167
168	case len(c.namespace) > 0 && len(subresources) == 0:
169		uncastRet, err = c.client.Fake.
170			Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj)
171
172	case len(c.namespace) > 0 && len(subresources) > 0:
173		uncastRet, err = c.client.Fake.
174			Invokes(testing.NewUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, obj), obj)
175
176	}
177
178	if err != nil {
179		return nil, err
180	}
181	if uncastRet == nil {
182		return nil, err
183	}
184	ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
185	if !ok {
186		return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
187	}
188	return ret, err
189}
190
191// UpdateStatus records the object status update and processes it via the reactor.
192func (c *metadataResourceClient) UpdateStatus(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions) (*metav1.PartialObjectMetadata, error) {
193	var uncastRet runtime.Object
194	var err error
195	switch {
196	case len(c.namespace) == 0:
197		uncastRet, err = c.client.Fake.
198			Invokes(testing.NewRootUpdateSubresourceAction(c.resource, "status", obj), obj)
199
200	case len(c.namespace) > 0:
201		uncastRet, err = c.client.Fake.
202			Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj)
203
204	}
205
206	if err != nil {
207		return nil, err
208	}
209	if uncastRet == nil {
210		return nil, err
211	}
212	ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
213	if !ok {
214		return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
215	}
216	return ret, err
217}
218
219// Delete records the object deletion and processes it via the reactor.
220func (c *metadataResourceClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error {
221	var err error
222	switch {
223	case len(c.namespace) == 0 && len(subresources) == 0:
224		_, err = c.client.Fake.
225			Invokes(testing.NewRootDeleteAction(c.resource, name), &metav1.Status{Status: "metadata delete fail"})
226
227	case len(c.namespace) == 0 && len(subresources) > 0:
228		_, err = c.client.Fake.
229			Invokes(testing.NewRootDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata delete fail"})
230
231	case len(c.namespace) > 0 && len(subresources) == 0:
232		_, err = c.client.Fake.
233			Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "metadata delete fail"})
234
235	case len(c.namespace) > 0 && len(subresources) > 0:
236		_, err = c.client.Fake.
237			Invokes(testing.NewDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, name), &metav1.Status{Status: "metadata delete fail"})
238	}
239
240	return err
241}
242
243// DeleteCollection records the object collection deletion and processes it via the reactor.
244func (c *metadataResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
245	var err error
246	switch {
247	case len(c.namespace) == 0:
248		action := testing.NewRootDeleteCollectionAction(c.resource, listOptions)
249		_, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "metadata deletecollection fail"})
250
251	case len(c.namespace) > 0:
252		action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions)
253		_, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "metadata deletecollection fail"})
254
255	}
256
257	return err
258}
259
260// Get records the object retrieval and processes it via the reactor.
261func (c *metadataResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
262	var uncastRet runtime.Object
263	var err error
264	switch {
265	case len(c.namespace) == 0 && len(subresources) == 0:
266		uncastRet, err = c.client.Fake.
267			Invokes(testing.NewRootGetAction(c.resource, name), &metav1.Status{Status: "metadata get fail"})
268
269	case len(c.namespace) == 0 && len(subresources) > 0:
270		uncastRet, err = c.client.Fake.
271			Invokes(testing.NewRootGetSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata get fail"})
272
273	case len(c.namespace) > 0 && len(subresources) == 0:
274		uncastRet, err = c.client.Fake.
275			Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "metadata get fail"})
276
277	case len(c.namespace) > 0 && len(subresources) > 0:
278		uncastRet, err = c.client.Fake.
279			Invokes(testing.NewGetSubresourceAction(c.resource, c.namespace, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata get fail"})
280	}
281
282	if err != nil {
283		return nil, err
284	}
285	if uncastRet == nil {
286		return nil, err
287	}
288	ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
289	if !ok {
290		return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
291	}
292	return ret, err
293}
294
295// List records the object deletion and processes it via the reactor.
296func (c *metadataResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) {
297	var obj runtime.Object
298	var err error
299	switch {
300	case len(c.namespace) == 0:
301		obj, err = c.client.Fake.
302			Invokes(testing.NewRootListAction(c.resource, schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, opts), &metav1.Status{Status: "metadata list fail"})
303
304	case len(c.namespace) > 0:
305		obj, err = c.client.Fake.
306			Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, c.namespace, opts), &metav1.Status{Status: "metadata list fail"})
307
308	}
309
310	if obj == nil {
311		return nil, err
312	}
313
314	label, _, _ := testing.ExtractFromListOptions(opts)
315	if label == nil {
316		label = labels.Everything()
317	}
318
319	inputList, ok := obj.(*metav1.List)
320	if !ok {
321		return nil, fmt.Errorf("incoming object is incorrect type %T", obj)
322	}
323
324	list := &metav1.PartialObjectMetadataList{
325		ListMeta: inputList.ListMeta,
326	}
327	for i := range inputList.Items {
328		item, ok := inputList.Items[i].Object.(*metav1.PartialObjectMetadata)
329		if !ok {
330			return nil, fmt.Errorf("item %d in list %T is %T", i, inputList, inputList.Items[i].Object)
331		}
332		metadata, err := meta.Accessor(item)
333		if err != nil {
334			return nil, err
335		}
336		if label.Matches(labels.Set(metadata.GetLabels())) {
337			list.Items = append(list.Items, *item)
338		}
339	}
340	return list, nil
341}
342
343func (c *metadataResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
344	switch {
345	case len(c.namespace) == 0:
346		return c.client.Fake.
347			InvokesWatch(testing.NewRootWatchAction(c.resource, opts))
348
349	case len(c.namespace) > 0:
350		return c.client.Fake.
351			InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts))
352
353	}
354
355	panic("math broke")
356}
357
358// Patch records the object patch and processes it via the reactor.
359func (c *metadataResourceClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
360	var uncastRet runtime.Object
361	var err error
362	switch {
363	case len(c.namespace) == 0 && len(subresources) == 0:
364		uncastRet, err = c.client.Fake.
365			Invokes(testing.NewRootPatchAction(c.resource, name, pt, data), &metav1.Status{Status: "metadata patch fail"})
366
367	case len(c.namespace) == 0 && len(subresources) > 0:
368		uncastRet, err = c.client.Fake.
369			Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, pt, data, subresources...), &metav1.Status{Status: "metadata patch fail"})
370
371	case len(c.namespace) > 0 && len(subresources) == 0:
372		uncastRet, err = c.client.Fake.
373			Invokes(testing.NewPatchAction(c.resource, c.namespace, name, pt, data), &metav1.Status{Status: "metadata patch fail"})
374
375	case len(c.namespace) > 0 && len(subresources) > 0:
376		uncastRet, err = c.client.Fake.
377			Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, pt, data, subresources...), &metav1.Status{Status: "metadata patch fail"})
378
379	}
380
381	if err != nil {
382		return nil, err
383	}
384	if uncastRet == nil {
385		return nil, err
386	}
387	ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
388	if !ok {
389		return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
390	}
391	return ret, err
392}
393