1
16
17 package fake
18
19 import (
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/apis/meta/v1/unstructured"
27 "k8s.io/apimachinery/pkg/labels"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/runtime/schema"
30 "k8s.io/apimachinery/pkg/runtime/serializer"
31 "k8s.io/apimachinery/pkg/types"
32 "k8s.io/apimachinery/pkg/watch"
33 "k8s.io/client-go/dynamic"
34 "k8s.io/client-go/testing"
35 )
36
37 func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient {
38 unstructuredScheme := runtime.NewScheme()
39 for gvk := range scheme.AllKnownTypes() {
40 if unstructuredScheme.Recognizes(gvk) {
41 continue
42 }
43 if strings.HasSuffix(gvk.Kind, "List") {
44 unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.UnstructuredList{})
45 continue
46 }
47 unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
48 }
49
50 objects, err := convertObjectsToUnstructured(scheme, objects)
51 if err != nil {
52 panic(err)
53 }
54
55 for _, obj := range objects {
56 gvk := obj.GetObjectKind().GroupVersionKind()
57 if !unstructuredScheme.Recognizes(gvk) {
58 unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
59 }
60 gvk.Kind += "List"
61 if !unstructuredScheme.Recognizes(gvk) {
62 unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.UnstructuredList{})
63 }
64 }
65
66 return NewSimpleDynamicClientWithCustomListKinds(unstructuredScheme, nil, objects...)
67 }
68
69
70
71 func NewSimpleDynamicClientWithCustomListKinds(scheme *runtime.Scheme, gvrToListKind map[schema.GroupVersionResource]string, objects ...runtime.Object) *FakeDynamicClient {
72
73
74
75
76
77
78
79
80 completeGVRToListKind := map[schema.GroupVersionResource]string{}
81 for listGVK := range scheme.AllKnownTypes() {
82 if !strings.HasSuffix(listGVK.Kind, "List") {
83 continue
84 }
85 nonListGVK := listGVK.GroupVersion().WithKind(listGVK.Kind[:len(listGVK.Kind)-4])
86 plural, _ := meta.UnsafeGuessKindToResource(nonListGVK)
87 completeGVRToListKind[plural] = listGVK.Kind
88 }
89
90 for gvr, listKind := range gvrToListKind {
91 if !strings.HasSuffix(listKind, "List") {
92 panic("coding error, listGVK must end in List or this fake client doesn't work right")
93 }
94 listGVK := gvr.GroupVersion().WithKind(listKind)
95
96
97 if _, err := scheme.New(listGVK); err == nil {
98 completeGVRToListKind[gvr] = listKind
99 continue
100 }
101
102 scheme.AddKnownTypeWithName(listGVK, &unstructured.UnstructuredList{})
103 completeGVRToListKind[gvr] = listKind
104 }
105
106 codecs := serializer.NewCodecFactory(scheme)
107 o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
108 for _, obj := range objects {
109 if err := o.Add(obj); err != nil {
110 panic(err)
111 }
112 }
113
114 cs := &FakeDynamicClient{scheme: scheme, gvrToListKind: completeGVRToListKind, tracker: o}
115 cs.AddReactor("*", "*", testing.ObjectReaction(o))
116 cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
117 gvr := action.GetResource()
118 ns := action.GetNamespace()
119 watch, err := o.Watch(gvr, ns)
120 if err != nil {
121 return false, nil, err
122 }
123 return true, watch, nil
124 })
125
126 return cs
127 }
128
129
130
131
132 type FakeDynamicClient struct {
133 testing.Fake
134 scheme *runtime.Scheme
135 gvrToListKind map[schema.GroupVersionResource]string
136 tracker testing.ObjectTracker
137 }
138
139 type dynamicResourceClient struct {
140 client *FakeDynamicClient
141 namespace string
142 resource schema.GroupVersionResource
143 listKind string
144 }
145
146 var (
147 _ dynamic.Interface = &FakeDynamicClient{}
148 _ testing.FakeClient = &FakeDynamicClient{}
149 )
150
151 func (c *FakeDynamicClient) Tracker() testing.ObjectTracker {
152 return c.tracker
153 }
154
155 func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
156 return &dynamicResourceClient{client: c, resource: resource, listKind: c.gvrToListKind[resource]}
157 }
158
159 func (c *dynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface {
160 ret := *c
161 ret.namespace = ns
162 return &ret
163 }
164
165 func (c *dynamicResourceClient) Create(ctx context.Context, obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
166 var uncastRet runtime.Object
167 var err error
168 switch {
169 case len(c.namespace) == 0 && len(subresources) == 0:
170 uncastRet, err = c.client.Fake.
171 Invokes(testing.NewRootCreateAction(c.resource, obj), obj)
172
173 case len(c.namespace) == 0 && len(subresources) > 0:
174 var accessor metav1.Object
175 accessor, err = meta.Accessor(obj)
176 if err != nil {
177 return nil, err
178 }
179 name := accessor.GetName()
180 uncastRet, err = c.client.Fake.
181 Invokes(testing.NewRootCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), obj), obj)
182
183 case len(c.namespace) > 0 && len(subresources) == 0:
184 uncastRet, err = c.client.Fake.
185 Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj)
186
187 case len(c.namespace) > 0 && len(subresources) > 0:
188 var accessor metav1.Object
189 accessor, err = meta.Accessor(obj)
190 if err != nil {
191 return nil, err
192 }
193 name := accessor.GetName()
194 uncastRet, err = c.client.Fake.
195 Invokes(testing.NewCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), c.namespace, obj), obj)
196
197 }
198
199 if err != nil {
200 return nil, err
201 }
202 if uncastRet == nil {
203 return nil, err
204 }
205
206 ret := &unstructured.Unstructured{}
207 if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
208 return nil, err
209 }
210 return ret, err
211 }
212
213 func (c *dynamicResourceClient) Update(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
214 var uncastRet runtime.Object
215 var err error
216 switch {
217 case len(c.namespace) == 0 && len(subresources) == 0:
218 uncastRet, err = c.client.Fake.
219 Invokes(testing.NewRootUpdateAction(c.resource, obj), obj)
220
221 case len(c.namespace) == 0 && len(subresources) > 0:
222 uncastRet, err = c.client.Fake.
223 Invokes(testing.NewRootUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), obj), obj)
224
225 case len(c.namespace) > 0 && len(subresources) == 0:
226 uncastRet, err = c.client.Fake.
227 Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj)
228
229 case len(c.namespace) > 0 && len(subresources) > 0:
230 uncastRet, err = c.client.Fake.
231 Invokes(testing.NewUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, obj), obj)
232
233 }
234
235 if err != nil {
236 return nil, err
237 }
238 if uncastRet == nil {
239 return nil, err
240 }
241
242 ret := &unstructured.Unstructured{}
243 if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
244 return nil, err
245 }
246 return ret, err
247 }
248
249 func (c *dynamicResourceClient) UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) {
250 var uncastRet runtime.Object
251 var err error
252 switch {
253 case len(c.namespace) == 0:
254 uncastRet, err = c.client.Fake.
255 Invokes(testing.NewRootUpdateSubresourceAction(c.resource, "status", obj), obj)
256
257 case len(c.namespace) > 0:
258 uncastRet, err = c.client.Fake.
259 Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj)
260
261 }
262
263 if err != nil {
264 return nil, err
265 }
266 if uncastRet == nil {
267 return nil, err
268 }
269
270 ret := &unstructured.Unstructured{}
271 if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
272 return nil, err
273 }
274 return ret, err
275 }
276
277 func (c *dynamicResourceClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error {
278 var err error
279 switch {
280 case len(c.namespace) == 0 && len(subresources) == 0:
281 _, err = c.client.Fake.
282 Invokes(testing.NewRootDeleteAction(c.resource, name), &metav1.Status{Status: "dynamic delete fail"})
283
284 case len(c.namespace) == 0 && len(subresources) > 0:
285 _, err = c.client.Fake.
286 Invokes(testing.NewRootDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic delete fail"})
287
288 case len(c.namespace) > 0 && len(subresources) == 0:
289 _, err = c.client.Fake.
290 Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic delete fail"})
291
292 case len(c.namespace) > 0 && len(subresources) > 0:
293 _, err = c.client.Fake.
294 Invokes(testing.NewDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, name), &metav1.Status{Status: "dynamic delete fail"})
295 }
296
297 return err
298 }
299
300 func (c *dynamicResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
301 var err error
302 switch {
303 case len(c.namespace) == 0:
304 action := testing.NewRootDeleteCollectionAction(c.resource, listOptions)
305 _, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"})
306
307 case len(c.namespace) > 0:
308 action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions)
309 _, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"})
310
311 }
312
313 return err
314 }
315
316 func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
317 var uncastRet runtime.Object
318 var err error
319 switch {
320 case len(c.namespace) == 0 && len(subresources) == 0:
321 uncastRet, err = c.client.Fake.
322 Invokes(testing.NewRootGetAction(c.resource, name), &metav1.Status{Status: "dynamic get fail"})
323
324 case len(c.namespace) == 0 && len(subresources) > 0:
325 uncastRet, err = c.client.Fake.
326 Invokes(testing.NewRootGetSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"})
327
328 case len(c.namespace) > 0 && len(subresources) == 0:
329 uncastRet, err = c.client.Fake.
330 Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic get fail"})
331
332 case len(c.namespace) > 0 && len(subresources) > 0:
333 uncastRet, err = c.client.Fake.
334 Invokes(testing.NewGetSubresourceAction(c.resource, c.namespace, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"})
335 }
336
337 if err != nil {
338 return nil, err
339 }
340 if uncastRet == nil {
341 return nil, err
342 }
343
344 ret := &unstructured.Unstructured{}
345 if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
346 return nil, err
347 }
348 return ret, err
349 }
350
351 func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
352 if len(c.listKind) == 0 {
353 panic(fmt.Sprintf("coding error: you must register resource to list kind for every resource you're going to LIST when creating the client. See NewSimpleDynamicClientWithCustomListKinds or register the list into the scheme: %v out of %v", c.resource, c.client.gvrToListKind))
354 }
355 listGVK := c.resource.GroupVersion().WithKind(c.listKind)
356 listForFakeClientGVK := c.resource.GroupVersion().WithKind(c.listKind[:len(c.listKind)-4])
357
358 var obj runtime.Object
359 var err error
360 switch {
361 case len(c.namespace) == 0:
362 obj, err = c.client.Fake.
363 Invokes(testing.NewRootListAction(c.resource, listForFakeClientGVK, opts), &metav1.Status{Status: "dynamic list fail"})
364
365 case len(c.namespace) > 0:
366 obj, err = c.client.Fake.
367 Invokes(testing.NewListAction(c.resource, listForFakeClientGVK, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"})
368
369 }
370
371 if obj == nil {
372 return nil, err
373 }
374
375 label, _, _ := testing.ExtractFromListOptions(opts)
376 if label == nil {
377 label = labels.Everything()
378 }
379
380 retUnstructured := &unstructured.Unstructured{}
381 if err := c.client.scheme.Convert(obj, retUnstructured, nil); err != nil {
382 return nil, err
383 }
384 entireList, err := retUnstructured.ToList()
385 if err != nil {
386 return nil, err
387 }
388
389 list := &unstructured.UnstructuredList{}
390 list.SetRemainingItemCount(entireList.GetRemainingItemCount())
391 list.SetResourceVersion(entireList.GetResourceVersion())
392 list.SetContinue(entireList.GetContinue())
393 list.GetObjectKind().SetGroupVersionKind(listGVK)
394 for i := range entireList.Items {
395 item := &entireList.Items[i]
396 metadata, err := meta.Accessor(item)
397 if err != nil {
398 return nil, err
399 }
400 if label.Matches(labels.Set(metadata.GetLabels())) {
401 list.Items = append(list.Items, *item)
402 }
403 }
404 return list, nil
405 }
406
407 func (c *dynamicResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
408 switch {
409 case len(c.namespace) == 0:
410 return c.client.Fake.
411 InvokesWatch(testing.NewRootWatchAction(c.resource, opts))
412
413 case len(c.namespace) > 0:
414 return c.client.Fake.
415 InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts))
416
417 }
418
419 panic("math broke")
420 }
421
422
423 func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) {
424 var uncastRet runtime.Object
425 var err error
426 switch {
427 case len(c.namespace) == 0 && len(subresources) == 0:
428 uncastRet, err = c.client.Fake.
429 Invokes(testing.NewRootPatchAction(c.resource, name, pt, data), &metav1.Status{Status: "dynamic patch fail"})
430
431 case len(c.namespace) == 0 && len(subresources) > 0:
432 uncastRet, err = c.client.Fake.
433 Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
434
435 case len(c.namespace) > 0 && len(subresources) == 0:
436 uncastRet, err = c.client.Fake.
437 Invokes(testing.NewPatchAction(c.resource, c.namespace, name, pt, data), &metav1.Status{Status: "dynamic patch fail"})
438
439 case len(c.namespace) > 0 && len(subresources) > 0:
440 uncastRet, err = c.client.Fake.
441 Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
442
443 }
444
445 if err != nil {
446 return nil, err
447 }
448 if uncastRet == nil {
449 return nil, err
450 }
451
452 ret := &unstructured.Unstructured{}
453 if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
454 return nil, err
455 }
456 return ret, err
457 }
458
459
460 func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) {
461 outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
462 if err != nil {
463 return nil, err
464 }
465 var uncastRet runtime.Object
466 switch {
467 case len(c.namespace) == 0 && len(subresources) == 0:
468 uncastRet, err = c.client.Fake.
469 Invokes(testing.NewRootPatchAction(c.resource, name, types.ApplyPatchType, outBytes), &metav1.Status{Status: "dynamic patch fail"})
470
471 case len(c.namespace) == 0 && len(subresources) > 0:
472 uncastRet, err = c.client.Fake.
473 Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, types.ApplyPatchType, outBytes, subresources...), &metav1.Status{Status: "dynamic patch fail"})
474
475 case len(c.namespace) > 0 && len(subresources) == 0:
476 uncastRet, err = c.client.Fake.
477 Invokes(testing.NewPatchAction(c.resource, c.namespace, name, types.ApplyPatchType, outBytes), &metav1.Status{Status: "dynamic patch fail"})
478
479 case len(c.namespace) > 0 && len(subresources) > 0:
480 uncastRet, err = c.client.Fake.
481 Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, types.ApplyPatchType, outBytes, subresources...), &metav1.Status{Status: "dynamic patch fail"})
482
483 }
484
485 if err != nil {
486 return nil, err
487 }
488 if uncastRet == nil {
489 return nil, err
490 }
491
492 ret := &unstructured.Unstructured{}
493 if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
494 return nil, err
495 }
496 return ret, nil
497 }
498
499 func (c *dynamicResourceClient) ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions) (*unstructured.Unstructured, error) {
500 return c.Apply(ctx, name, obj, options, "status")
501 }
502
503 func convertObjectsToUnstructured(s *runtime.Scheme, objs []runtime.Object) ([]runtime.Object, error) {
504 ul := make([]runtime.Object, 0, len(objs))
505
506 for _, obj := range objs {
507 u, err := convertToUnstructured(s, obj)
508 if err != nil {
509 return nil, err
510 }
511
512 ul = append(ul, u)
513 }
514 return ul, nil
515 }
516
517 func convertToUnstructured(s *runtime.Scheme, obj runtime.Object) (runtime.Object, error) {
518 var (
519 err error
520 u unstructured.Unstructured
521 )
522
523 u.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
524 if err != nil {
525 return nil, fmt.Errorf("failed to convert to unstructured: %w", err)
526 }
527
528 gvk := u.GroupVersionKind()
529 if gvk.Group == "" || gvk.Kind == "" {
530 gvks, _, err := s.ObjectKinds(obj)
531 if err != nil {
532 return nil, fmt.Errorf("failed to convert to unstructured - unable to get GVK %w", err)
533 }
534 apiv, k := gvks[0].ToAPIVersionAndKind()
535 u.SetAPIVersion(apiv)
536 u.SetKind(k)
537 }
538 return &u, nil
539 }
540
View as plain text