1
16
17 package fake
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "strconv"
24 "time"
25
26 "github.com/google/go-cmp/cmp"
27 . "github.com/onsi/ginkgo/v2"
28 . "github.com/onsi/gomega"
29 appsv1 "k8s.io/api/apps/v1"
30 coordinationv1 "k8s.io/api/coordination/v1"
31 corev1 "k8s.io/api/core/v1"
32 policyv1 "k8s.io/api/policy/v1"
33 policyv1beta1 "k8s.io/api/policy/v1beta1"
34 apierrors "k8s.io/apimachinery/pkg/api/errors"
35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
37 "k8s.io/apimachinery/pkg/fields"
38 "k8s.io/apimachinery/pkg/labels"
39 "k8s.io/apimachinery/pkg/runtime"
40 "k8s.io/apimachinery/pkg/runtime/schema"
41 "k8s.io/apimachinery/pkg/types"
42 "k8s.io/apimachinery/pkg/watch"
43 "k8s.io/client-go/kubernetes/fake"
44 "k8s.io/utils/ptr"
45
46 "sigs.k8s.io/controller-runtime/pkg/client"
47 "sigs.k8s.io/controller-runtime/pkg/client/interceptor"
48 "sigs.k8s.io/controller-runtime/pkg/scheme"
49 )
50
51 const (
52 machineIDFromStatusUpdate = "machine-id-from-status-update"
53 cidrFromStatusUpdate = "cidr-from-status-update"
54 )
55
56 var _ = Describe("Fake client", func() {
57 var dep *appsv1.Deployment
58 var dep2 *appsv1.Deployment
59 var cm *corev1.ConfigMap
60 var cl client.WithWatch
61
62 BeforeEach(func() {
63 replicas := int32(1)
64 dep = &appsv1.Deployment{
65 TypeMeta: metav1.TypeMeta{
66 APIVersion: "apps/v1",
67 Kind: "Deployment",
68 },
69 ObjectMeta: metav1.ObjectMeta{
70 Name: "test-deployment",
71 Namespace: "ns1",
72 ResourceVersion: trackerAddResourceVersion,
73 },
74 Spec: appsv1.DeploymentSpec{
75 Replicas: &replicas,
76 Strategy: appsv1.DeploymentStrategy{
77 Type: appsv1.RecreateDeploymentStrategyType,
78 },
79 },
80 }
81 dep2 = &appsv1.Deployment{
82 TypeMeta: metav1.TypeMeta{
83 APIVersion: "apps/v1",
84 Kind: "Deployment",
85 },
86 ObjectMeta: metav1.ObjectMeta{
87 Name: "test-deployment-2",
88 Namespace: "ns1",
89 Labels: map[string]string{
90 "test-label": "label-value",
91 },
92 ResourceVersion: trackerAddResourceVersion,
93 },
94 Spec: appsv1.DeploymentSpec{
95 Replicas: &replicas,
96 },
97 }
98 cm = &corev1.ConfigMap{
99 TypeMeta: metav1.TypeMeta{
100 APIVersion: "v1",
101 Kind: "ConfigMap",
102 },
103 ObjectMeta: metav1.ObjectMeta{
104 Name: "test-cm",
105 Namespace: "ns2",
106 ResourceVersion: trackerAddResourceVersion,
107 },
108 Data: map[string]string{
109 "test-key": "test-value",
110 },
111 }
112 })
113
114 AssertClientWithoutIndexBehavior := func() {
115 It("should be able to Get", func() {
116 By("Getting a deployment")
117 namespacedName := types.NamespacedName{
118 Name: "test-deployment",
119 Namespace: "ns1",
120 }
121 obj := &appsv1.Deployment{}
122 err := cl.Get(context.Background(), namespacedName, obj)
123 Expect(err).ToNot(HaveOccurred())
124 Expect(obj).To(Equal(dep))
125 })
126
127 It("should be able to Get using unstructured", func() {
128 By("Getting a deployment")
129 namespacedName := types.NamespacedName{
130 Name: "test-deployment",
131 Namespace: "ns1",
132 }
133 obj := &unstructured.Unstructured{}
134 obj.SetAPIVersion("apps/v1")
135 obj.SetKind("Deployment")
136 err := cl.Get(context.Background(), namespacedName, obj)
137 Expect(err).ToNot(HaveOccurred())
138 })
139
140 It("should be able to List", func() {
141 By("Listing all deployments in a namespace")
142 list := &appsv1.DeploymentList{}
143 err := cl.List(context.Background(), list, client.InNamespace("ns1"))
144 Expect(err).ToNot(HaveOccurred())
145 Expect(list.Items).To(HaveLen(2))
146 Expect(list.Items).To(ConsistOf(*dep, *dep2))
147 })
148
149 It("should be able to List using unstructured list", func() {
150 By("Listing all deployments in a namespace")
151 list := &unstructured.UnstructuredList{}
152 list.SetAPIVersion("apps/v1")
153 list.SetKind("DeploymentList")
154 err := cl.List(context.Background(), list, client.InNamespace("ns1"))
155 Expect(err).ToNot(HaveOccurred())
156 Expect(list.Items).To(HaveLen(2))
157 })
158
159 It("should be able to List using unstructured list when setting a non-list kind", func() {
160 By("Listing all deployments in a namespace")
161 list := &unstructured.UnstructuredList{}
162 list.SetAPIVersion("apps/v1")
163 list.SetKind("Deployment")
164 err := cl.List(context.Background(), list, client.InNamespace("ns1"))
165 Expect(err).ToNot(HaveOccurred())
166 Expect(list.Items).To(HaveLen(2))
167 })
168
169 It("should be able to retrieve registered objects that got manipulated as unstructured", func() {
170 list := func() {
171 By("Listing all endpoints in a namespace")
172 list := &unstructured.UnstructuredList{}
173 list.SetAPIVersion("v1")
174 list.SetKind("EndpointsList")
175 err := cl.List(context.Background(), list, client.InNamespace("ns1"))
176 Expect(err).ToNot(HaveOccurred())
177 Expect(list.Items).To(HaveLen(1))
178 }
179
180 unstructuredEndpoint := func() *unstructured.Unstructured {
181 item := &unstructured.Unstructured{}
182 item.SetAPIVersion("v1")
183 item.SetKind("Endpoints")
184 item.SetName("test-endpoint")
185 item.SetNamespace("ns1")
186 return item
187 }
188
189 By("Adding the object during client initialization")
190 cl = NewClientBuilder().WithRuntimeObjects(unstructuredEndpoint()).Build()
191 list()
192 Expect(cl.Delete(context.Background(), unstructuredEndpoint())).To(Succeed())
193
194 By("Creating an object")
195 item := unstructuredEndpoint()
196 err := cl.Create(context.Background(), item)
197 Expect(err).ToNot(HaveOccurred())
198 list()
199
200 By("Updating the object")
201 item.SetAnnotations(map[string]string{"foo": "bar"})
202 err = cl.Update(context.Background(), item)
203 Expect(err).ToNot(HaveOccurred())
204 list()
205
206 By("Patching the object")
207 old := item.DeepCopy()
208 item.SetAnnotations(map[string]string{"bar": "baz"})
209 err = cl.Patch(context.Background(), item, client.MergeFrom(old))
210 Expect(err).ToNot(HaveOccurred())
211 list()
212 })
213
214 It("should be able to Create an unregistered type using unstructured", func() {
215 item := &unstructured.Unstructured{}
216 item.SetAPIVersion("custom/v1")
217 item.SetKind("Image")
218 item.SetName("my-item")
219 err := cl.Create(context.Background(), item)
220 Expect(err).ToNot(HaveOccurred())
221 })
222
223 It("should be able to Get an unregisted type using unstructured", func() {
224 By("Creating an object of an unregistered type")
225 item := &unstructured.Unstructured{}
226 item.SetAPIVersion("custom/v2")
227 item.SetKind("Image")
228 item.SetName("my-item")
229 err := cl.Create(context.Background(), item)
230 Expect(err).ToNot(HaveOccurred())
231
232 By("Getting and the object")
233 item = &unstructured.Unstructured{}
234 item.SetAPIVersion("custom/v2")
235 item.SetKind("Image")
236 item.SetName("my-item")
237 err = cl.Get(context.Background(), client.ObjectKeyFromObject(item), item)
238 Expect(err).ToNot(HaveOccurred())
239 })
240
241 It("should be able to List an unregistered type using unstructured", func() {
242 list := &unstructured.UnstructuredList{}
243 list.SetAPIVersion("custom/v3")
244 list.SetKind("ImageList")
245 err := cl.List(context.Background(), list)
246 Expect(err).ToNot(HaveOccurred())
247 })
248
249 It("should be able to List an unregistered type using unstructured", func() {
250 list := &unstructured.UnstructuredList{}
251 list.SetAPIVersion("custom/v4")
252 list.SetKind("Image")
253 err := cl.List(context.Background(), list)
254 Expect(err).ToNot(HaveOccurred())
255 })
256
257 It("should be able to Update an unregistered type using unstructured", func() {
258 By("Creating an object of an unregistered type")
259 item := &unstructured.Unstructured{}
260 item.SetAPIVersion("custom/v5")
261 item.SetKind("Image")
262 item.SetName("my-item")
263 err := cl.Create(context.Background(), item)
264 Expect(err).ToNot(HaveOccurred())
265
266 By("Updating the object")
267 err = unstructured.SetNestedField(item.Object, int64(2), "spec", "replicas")
268 Expect(err).ToNot(HaveOccurred())
269 err = cl.Update(context.Background(), item)
270 Expect(err).ToNot(HaveOccurred())
271
272 By("Getting the object")
273 item = &unstructured.Unstructured{}
274 item.SetAPIVersion("custom/v5")
275 item.SetKind("Image")
276 item.SetName("my-item")
277 err = cl.Get(context.Background(), client.ObjectKeyFromObject(item), item)
278 Expect(err).ToNot(HaveOccurred())
279
280 By("Inspecting the object")
281 value, found, err := unstructured.NestedInt64(item.Object, "spec", "replicas")
282 Expect(err).ToNot(HaveOccurred())
283 Expect(found).To(BeTrue())
284 Expect(value).To(Equal(int64(2)))
285 })
286
287 It("should be able to Patch an unregistered type using unstructured", func() {
288 By("Creating an object of an unregistered type")
289 item := &unstructured.Unstructured{}
290 item.SetAPIVersion("custom/v6")
291 item.SetKind("Image")
292 item.SetName("my-item")
293 err := cl.Create(context.Background(), item)
294 Expect(err).ToNot(HaveOccurred())
295
296 By("Updating the object")
297 original := item.DeepCopy()
298 err = unstructured.SetNestedField(item.Object, int64(2), "spec", "replicas")
299 Expect(err).ToNot(HaveOccurred())
300 err = cl.Patch(context.Background(), item, client.MergeFrom(original))
301 Expect(err).ToNot(HaveOccurred())
302
303 By("Getting the object")
304 item = &unstructured.Unstructured{}
305 item.SetAPIVersion("custom/v6")
306 item.SetKind("Image")
307 item.SetName("my-item")
308 err = cl.Get(context.Background(), client.ObjectKeyFromObject(item), item)
309 Expect(err).ToNot(HaveOccurred())
310
311 By("Inspecting the object")
312 value, found, err := unstructured.NestedInt64(item.Object, "spec", "replicas")
313 Expect(err).ToNot(HaveOccurred())
314 Expect(found).To(BeTrue())
315 Expect(value).To(Equal(int64(2)))
316 })
317
318 It("should be able to Delete an unregistered type using unstructured", func() {
319 By("Creating an object of an unregistered type")
320 item := &unstructured.Unstructured{}
321 item.SetAPIVersion("custom/v7")
322 item.SetKind("Image")
323 item.SetName("my-item")
324 err := cl.Create(context.Background(), item)
325 Expect(err).ToNot(HaveOccurred())
326
327 By("Deleting the object")
328 err = cl.Delete(context.Background(), item)
329 Expect(err).ToNot(HaveOccurred())
330
331 By("Getting the object")
332 item = &unstructured.Unstructured{}
333 item.SetAPIVersion("custom/v7")
334 item.SetKind("Image")
335 item.SetName("my-item")
336 err = cl.Get(context.Background(), client.ObjectKeyFromObject(item), item)
337 Expect(apierrors.IsNotFound(err)).To(BeTrue())
338 })
339
340 It("should support filtering by labels and their values", func() {
341 By("Listing deployments with a particular label and value")
342 list := &appsv1.DeploymentList{}
343 err := cl.List(context.Background(), list, client.InNamespace("ns1"),
344 client.MatchingLabels(map[string]string{
345 "test-label": "label-value",
346 }))
347 Expect(err).ToNot(HaveOccurred())
348 Expect(list.Items).To(HaveLen(1))
349 Expect(list.Items).To(ConsistOf(*dep2))
350 })
351
352 It("should support filtering by label existence", func() {
353 By("Listing deployments with a particular label")
354 list := &appsv1.DeploymentList{}
355 err := cl.List(context.Background(), list, client.InNamespace("ns1"),
356 client.HasLabels{"test-label"})
357 Expect(err).ToNot(HaveOccurred())
358 Expect(list.Items).To(HaveLen(1))
359 Expect(list.Items).To(ConsistOf(*dep2))
360 })
361
362 It("should reject apply patches, they are not supported in the fake client", func() {
363 By("Creating a new configmap")
364 cm := &corev1.ConfigMap{
365 TypeMeta: metav1.TypeMeta{
366 APIVersion: "v1",
367 Kind: "ConfigMap",
368 },
369 ObjectMeta: metav1.ObjectMeta{
370 Name: "new-test-cm",
371 Namespace: "ns2",
372 },
373 }
374 err := cl.Create(context.Background(), cm)
375 Expect(err).ToNot(HaveOccurred())
376
377 cm.Data = map[string]string{"foo": "bar"}
378 err = cl.Patch(context.Background(), cm, client.Apply, client.ForceOwnership)
379 Expect(err).To(MatchError(ContainSubstring("apply patches are not supported in the fake client")))
380 })
381
382 It("should be able to Create", func() {
383 By("Creating a new configmap")
384 newcm := &corev1.ConfigMap{
385 TypeMeta: metav1.TypeMeta{
386 APIVersion: "v1",
387 Kind: "ConfigMap",
388 },
389 ObjectMeta: metav1.ObjectMeta{
390 Name: "new-test-cm",
391 Namespace: "ns2",
392 },
393 }
394 err := cl.Create(context.Background(), newcm)
395 Expect(err).ToNot(HaveOccurred())
396
397 By("Getting the new configmap")
398 namespacedName := types.NamespacedName{
399 Name: "new-test-cm",
400 Namespace: "ns2",
401 }
402 obj := &corev1.ConfigMap{}
403 err = cl.Get(context.Background(), namespacedName, obj)
404 Expect(err).ToNot(HaveOccurred())
405 Expect(obj).To(Equal(newcm))
406 Expect(obj.ObjectMeta.ResourceVersion).To(Equal("1"))
407 })
408
409 It("should error on create with set resourceVersion", func() {
410 By("Creating a new configmap")
411 newcm := &corev1.ConfigMap{
412 ObjectMeta: metav1.ObjectMeta{
413 Name: "new-test-cm",
414 Namespace: "ns2",
415 ResourceVersion: "1",
416 },
417 }
418 err := cl.Create(context.Background(), newcm)
419 Expect(apierrors.IsBadRequest(err)).To(BeTrue())
420 })
421
422 It("should not change the submitted object if Create failed", func() {
423 By("Trying to create an existing configmap")
424 submitted := cm.DeepCopy()
425 submitted.ResourceVersion = ""
426 submittedReference := submitted.DeepCopy()
427 err := cl.Create(context.Background(), submitted)
428 Expect(err).To(HaveOccurred())
429 Expect(apierrors.IsAlreadyExists(err)).To(BeTrue())
430 Expect(submitted).To(Equal(submittedReference))
431 })
432
433 It("should error on Create with empty Name", func() {
434 By("Creating a new configmap")
435 newcm := &corev1.ConfigMap{
436 TypeMeta: metav1.TypeMeta{
437 APIVersion: "v1",
438 Kind: "ConfigMap",
439 },
440 ObjectMeta: metav1.ObjectMeta{
441 Namespace: "ns2",
442 },
443 }
444 err := cl.Create(context.Background(), newcm)
445 Expect(err.Error()).To(Equal("ConfigMap \"\" is invalid: metadata.name: Required value: name is required"))
446 })
447
448 It("should error on Update with empty Name", func() {
449 By("Creating a new configmap")
450 newcm := &corev1.ConfigMap{
451 TypeMeta: metav1.TypeMeta{
452 APIVersion: "v1",
453 Kind: "ConfigMap",
454 },
455 ObjectMeta: metav1.ObjectMeta{
456 Namespace: "ns2",
457 },
458 }
459 err := cl.Update(context.Background(), newcm)
460 Expect(err.Error()).To(Equal("ConfigMap \"\" is invalid: metadata.name: Required value: name is required"))
461 })
462
463 It("should be able to Create with GenerateName", func() {
464 By("Creating a new configmap")
465 newcm := &corev1.ConfigMap{
466 TypeMeta: metav1.TypeMeta{
467 APIVersion: "v1",
468 Kind: "ConfigMap",
469 },
470 ObjectMeta: metav1.ObjectMeta{
471 GenerateName: "new-test-cm",
472 Namespace: "ns2",
473 Labels: map[string]string{
474 "test-label": "label-value",
475 },
476 },
477 }
478 err := cl.Create(context.Background(), newcm)
479 Expect(err).ToNot(HaveOccurred())
480
481 By("Listing configmaps with a particular label")
482 list := &corev1.ConfigMapList{}
483 err = cl.List(context.Background(), list, client.InNamespace("ns2"),
484 client.MatchingLabels(map[string]string{
485 "test-label": "label-value",
486 }))
487 Expect(err).ToNot(HaveOccurred())
488 Expect(list.Items).To(HaveLen(1))
489 Expect(list.Items[0].Name).NotTo(BeEmpty())
490 })
491
492 It("should be able to Update", func() {
493 By("Updating a new configmap")
494 newcm := &corev1.ConfigMap{
495 TypeMeta: metav1.TypeMeta{
496 APIVersion: "v1",
497 Kind: "ConfigMap",
498 },
499 ObjectMeta: metav1.ObjectMeta{
500 Name: "test-cm",
501 Namespace: "ns2",
502 ResourceVersion: "",
503 },
504 Data: map[string]string{
505 "test-key": "new-value",
506 },
507 }
508 err := cl.Update(context.Background(), newcm)
509 Expect(err).ToNot(HaveOccurred())
510
511 By("Getting the new configmap")
512 namespacedName := types.NamespacedName{
513 Name: "test-cm",
514 Namespace: "ns2",
515 }
516 obj := &corev1.ConfigMap{}
517 err = cl.Get(context.Background(), namespacedName, obj)
518 Expect(err).ToNot(HaveOccurred())
519 Expect(obj).To(Equal(newcm))
520 Expect(obj.ObjectMeta.ResourceVersion).To(Equal("1000"))
521 })
522
523 It("should allow updates with non-set ResourceVersion for a resource that allows unconditional updates", func() {
524 By("Updating a new configmap")
525 newcm := &corev1.ConfigMap{
526 TypeMeta: metav1.TypeMeta{
527 APIVersion: "v1",
528 Kind: "ConfigMap",
529 },
530 ObjectMeta: metav1.ObjectMeta{
531 Name: "test-cm",
532 Namespace: "ns2",
533 },
534 Data: map[string]string{
535 "test-key": "new-value",
536 },
537 }
538 err := cl.Update(context.Background(), newcm)
539 Expect(err).ToNot(HaveOccurred())
540
541 By("Getting the configmap")
542 namespacedName := types.NamespacedName{
543 Name: "test-cm",
544 Namespace: "ns2",
545 }
546 obj := &corev1.ConfigMap{}
547 err = cl.Get(context.Background(), namespacedName, obj)
548 Expect(err).ToNot(HaveOccurred())
549 Expect(obj).To(Equal(newcm))
550 Expect(obj.ObjectMeta.ResourceVersion).To(Equal("1000"))
551 })
552
553 It("should allow patch with non-set ResourceVersion for a resource that doesn't allow unconditional updates", func() {
554 schemeBuilder := &scheme.Builder{GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"}}
555 schemeBuilder.Register(&WithPointerMeta{}, &WithPointerMetaList{})
556
557 scheme := runtime.NewScheme()
558 Expect(schemeBuilder.AddToScheme(scheme)).NotTo(HaveOccurred())
559
560 cl := NewClientBuilder().WithScheme(scheme).Build()
561 original := &WithPointerMeta{
562 ObjectMeta: &metav1.ObjectMeta{
563 Name: "obj",
564 Namespace: "ns2",
565 }}
566
567 err := cl.Create(context.Background(), original)
568 Expect(err).ToNot(HaveOccurred())
569
570 newObj := &WithPointerMeta{
571 ObjectMeta: &metav1.ObjectMeta{
572 Name: original.Name,
573 Namespace: original.Namespace,
574 Annotations: map[string]string{
575 "foo": "bar",
576 },
577 }}
578 Expect(cl.Patch(context.Background(), newObj, client.MergeFrom(original))).To(Succeed())
579
580 patched := &WithPointerMeta{}
581 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(original), patched)).To(Succeed())
582 Expect(patched.Annotations).To(Equal(map[string]string{"foo": "bar"}))
583 })
584
585 It("should reject updates with non-set ResourceVersion for a resource that doesn't allow unconditional updates", func() {
586 By("Creating a new binding")
587 binding := &corev1.Binding{
588 TypeMeta: metav1.TypeMeta{
589 APIVersion: "v1",
590 Kind: "Binding",
591 },
592 ObjectMeta: metav1.ObjectMeta{
593 Name: "test-binding",
594 Namespace: "ns2",
595 },
596 Target: corev1.ObjectReference{
597 Kind: "ConfigMap",
598 APIVersion: "v1",
599 Namespace: cm.Namespace,
600 Name: cm.Name,
601 },
602 }
603 Expect(cl.Create(context.Background(), binding)).To(Succeed())
604
605 By("Updating the binding with a new resource lacking resource version")
606 newBinding := &corev1.Binding{
607 TypeMeta: metav1.TypeMeta{
608 APIVersion: "v1",
609 Kind: "Binding",
610 },
611 ObjectMeta: metav1.ObjectMeta{
612 Name: binding.Name,
613 Namespace: binding.Namespace,
614 },
615 Target: corev1.ObjectReference{
616 Namespace: binding.Namespace,
617 Name: "blue",
618 },
619 }
620 Expect(cl.Update(context.Background(), newBinding)).NotTo(Succeed())
621 })
622
623 It("should allow create on update for a resource that allows create on update", func() {
624 By("Creating a new lease with update")
625 lease := &coordinationv1.Lease{
626 TypeMeta: metav1.TypeMeta{
627 APIVersion: "coordination.k8s.io/v1",
628 Kind: "Lease",
629 },
630 ObjectMeta: metav1.ObjectMeta{
631 Name: "test-lease",
632 Namespace: "ns2",
633 },
634 Spec: coordinationv1.LeaseSpec{},
635 }
636 Expect(cl.Create(context.Background(), lease)).To(Succeed())
637
638 By("Getting the lease")
639 namespacedName := types.NamespacedName{
640 Name: lease.Name,
641 Namespace: lease.Namespace,
642 }
643 obj := &coordinationv1.Lease{}
644 Expect(cl.Get(context.Background(), namespacedName, obj)).To(Succeed())
645 Expect(obj).To(Equal(lease))
646 Expect(obj.ObjectMeta.ResourceVersion).To(Equal("1"))
647 })
648
649 It("should reject create on update for a resource that does not allow create on update", func() {
650 By("Attemping to create a new configmap with update")
651 newcm := &corev1.ConfigMap{
652 TypeMeta: metav1.TypeMeta{
653 APIVersion: "v1",
654 Kind: "ConfigMap",
655 },
656 ObjectMeta: metav1.ObjectMeta{
657 Name: "different-test-cm",
658 Namespace: "ns2",
659 },
660 Data: map[string]string{
661 "test-key": "new-value",
662 },
663 }
664 Expect(cl.Update(context.Background(), newcm)).NotTo(Succeed())
665 })
666
667 It("should reject updates with non-matching ResourceVersion", func() {
668 By("Updating a new configmap")
669 newcm := &corev1.ConfigMap{
670 ObjectMeta: metav1.ObjectMeta{
671 Name: "test-cm",
672 Namespace: "ns2",
673 ResourceVersion: "1",
674 },
675 Data: map[string]string{
676 "test-key": "new-value",
677 },
678 }
679 err := cl.Update(context.Background(), newcm)
680 Expect(apierrors.IsConflict(err)).To(BeTrue())
681
682 By("Getting the configmap")
683 namespacedName := types.NamespacedName{
684 Name: "test-cm",
685 Namespace: "ns2",
686 }
687 obj := &corev1.ConfigMap{}
688 err = cl.Get(context.Background(), namespacedName, obj)
689 Expect(err).ToNot(HaveOccurred())
690 Expect(obj).To(Equal(cm))
691 Expect(obj.ObjectMeta.ResourceVersion).To(Equal(trackerAddResourceVersion))
692 })
693
694 It("should reject Delete with a mismatched ResourceVersion", func() {
695 bogusRV := "bogus"
696 By("Deleting with a mismatched ResourceVersion Precondition")
697 err := cl.Delete(context.Background(), dep, client.Preconditions{ResourceVersion: &bogusRV})
698 Expect(apierrors.IsConflict(err)).To(BeTrue())
699
700 list := &appsv1.DeploymentList{}
701 err = cl.List(context.Background(), list, client.InNamespace("ns1"))
702 Expect(err).ToNot(HaveOccurred())
703 Expect(list.Items).To(HaveLen(2))
704 Expect(list.Items).To(ConsistOf(*dep, *dep2))
705 })
706
707 It("should successfully Delete with a matching ResourceVersion", func() {
708 goodRV := trackerAddResourceVersion
709 By("Deleting with a matching ResourceVersion Precondition")
710 err := cl.Delete(context.Background(), dep, client.Preconditions{ResourceVersion: &goodRV})
711 Expect(err).ToNot(HaveOccurred())
712
713 list := &appsv1.DeploymentList{}
714 err = cl.List(context.Background(), list, client.InNamespace("ns1"))
715 Expect(err).ToNot(HaveOccurred())
716 Expect(list.Items).To(HaveLen(1))
717 Expect(list.Items).To(ConsistOf(*dep2))
718 })
719
720 It("should be able to Delete with no ResourceVersion Precondition", func() {
721 By("Deleting a deployment")
722 err := cl.Delete(context.Background(), dep)
723 Expect(err).ToNot(HaveOccurred())
724
725 By("Listing all deployments in the namespace")
726 list := &appsv1.DeploymentList{}
727 err = cl.List(context.Background(), list, client.InNamespace("ns1"))
728 Expect(err).ToNot(HaveOccurred())
729 Expect(list.Items).To(HaveLen(1))
730 Expect(list.Items).To(ConsistOf(*dep2))
731 })
732
733 It("should be able to Delete with no opts even if object's ResourceVersion doesn't match server", func() {
734 By("Deleting a deployment")
735 depCopy := dep.DeepCopy()
736 depCopy.ResourceVersion = "bogus"
737 err := cl.Delete(context.Background(), depCopy)
738 Expect(err).ToNot(HaveOccurred())
739
740 By("Listing all deployments in the namespace")
741 list := &appsv1.DeploymentList{}
742 err = cl.List(context.Background(), list, client.InNamespace("ns1"))
743 Expect(err).ToNot(HaveOccurred())
744 Expect(list.Items).To(HaveLen(1))
745 Expect(list.Items).To(ConsistOf(*dep2))
746 })
747
748 It("should handle finalizers on Update", func() {
749 namespacedName := types.NamespacedName{
750 Name: "test-cm",
751 Namespace: "delete-with-finalizers",
752 }
753 By("Updating a new object")
754 newObj := &corev1.ConfigMap{
755 ObjectMeta: metav1.ObjectMeta{
756 Name: namespacedName.Name,
757 Namespace: namespacedName.Namespace,
758 Finalizers: []string{"finalizers.sigs.k8s.io/test"},
759 },
760 Data: map[string]string{
761 "test-key": "new-value",
762 },
763 }
764 err := cl.Create(context.Background(), newObj)
765 Expect(err).ToNot(HaveOccurred())
766
767 By("Deleting the object")
768 err = cl.Delete(context.Background(), newObj)
769 Expect(err).ToNot(HaveOccurred())
770
771 By("Getting the object")
772 obj := &corev1.ConfigMap{}
773 err = cl.Get(context.Background(), namespacedName, obj)
774 Expect(err).ToNot(HaveOccurred())
775 Expect(obj.DeletionTimestamp).NotTo(BeNil())
776
777 By("Removing the finalizer")
778 obj.Finalizers = []string{}
779 err = cl.Update(context.Background(), obj)
780 Expect(err).ToNot(HaveOccurred())
781
782 By("Getting the object")
783 obj = &corev1.ConfigMap{}
784 err = cl.Get(context.Background(), namespacedName, obj)
785 Expect(apierrors.IsNotFound(err)).To(BeTrue())
786 })
787
788 It("should reject changes to deletionTimestamp on Update", func() {
789 namespacedName := types.NamespacedName{
790 Name: "test-cm",
791 Namespace: "reject-with-deletiontimestamp",
792 }
793 By("Updating a new object")
794 newObj := &corev1.ConfigMap{
795 ObjectMeta: metav1.ObjectMeta{
796 Name: namespacedName.Name,
797 Namespace: namespacedName.Namespace,
798 },
799 Data: map[string]string{
800 "test-key": "new-value",
801 },
802 }
803 err := cl.Create(context.Background(), newObj)
804 Expect(err).ToNot(HaveOccurred())
805
806 By("Getting the object")
807 obj := &corev1.ConfigMap{}
808 err = cl.Get(context.Background(), namespacedName, obj)
809 Expect(err).ToNot(HaveOccurred())
810 Expect(obj.DeletionTimestamp).To(BeNil())
811
812 By("Adding deletionTimestamp")
813 now := metav1.Now()
814 obj.DeletionTimestamp = &now
815 err = cl.Update(context.Background(), obj)
816 Expect(err).To(HaveOccurred())
817
818 By("Deleting the object")
819 err = cl.Delete(context.Background(), newObj)
820 Expect(err).ToNot(HaveOccurred())
821
822 By("Changing the deletionTimestamp to new value")
823 obj = &corev1.ConfigMap{}
824 t := metav1.NewTime(time.Now().Add(time.Second))
825 obj.DeletionTimestamp = &t
826 err = cl.Update(context.Background(), obj)
827 Expect(err).To(HaveOccurred())
828
829 By("Removing deletionTimestamp")
830 obj.DeletionTimestamp = nil
831 err = cl.Update(context.Background(), obj)
832 Expect(err).To(HaveOccurred())
833
834 })
835
836 It("should be able to Delete a Collection", func() {
837 By("Deleting a deploymentList")
838 err := cl.DeleteAllOf(context.Background(), &appsv1.Deployment{}, client.InNamespace("ns1"))
839 Expect(err).ToNot(HaveOccurred())
840
841 By("Listing all deployments in the namespace")
842 list := &appsv1.DeploymentList{}
843 err = cl.List(context.Background(), list, client.InNamespace("ns1"))
844 Expect(err).ToNot(HaveOccurred())
845 Expect(list.Items).To(BeEmpty())
846 })
847
848 It("should handle finalizers deleting a collection", func() {
849 for i := 0; i < 5; i++ {
850 namespacedName := types.NamespacedName{
851 Name: fmt.Sprintf("test-cm-%d", i),
852 Namespace: "delete-collection-with-finalizers",
853 }
854 By("Creating a new object")
855 newObj := &corev1.ConfigMap{
856 ObjectMeta: metav1.ObjectMeta{
857 Name: namespacedName.Name,
858 Namespace: namespacedName.Namespace,
859 Finalizers: []string{"finalizers.sigs.k8s.io/test"},
860 },
861 Data: map[string]string{
862 "test-key": "new-value",
863 },
864 }
865 err := cl.Create(context.Background(), newObj)
866 Expect(err).ToNot(HaveOccurred())
867 }
868
869 By("Deleting the object")
870 err := cl.DeleteAllOf(context.Background(), &corev1.ConfigMap{}, client.InNamespace("delete-collection-with-finalizers"))
871 Expect(err).ToNot(HaveOccurred())
872
873 configmaps := corev1.ConfigMapList{}
874 err = cl.List(context.Background(), &configmaps, client.InNamespace("delete-collection-with-finalizers"))
875 Expect(err).ToNot(HaveOccurred())
876
877 Expect(configmaps.Items).To(HaveLen(5))
878 for _, cm := range configmaps.Items {
879 Expect(cm.DeletionTimestamp).NotTo(BeNil())
880 }
881 })
882
883 It("should be able to watch", func() {
884 By("Creating a watch")
885 objWatch, err := cl.Watch(context.Background(), &corev1.ServiceList{})
886 Expect(err).NotTo(HaveOccurred())
887
888 defer objWatch.Stop()
889
890 go func() {
891 defer GinkgoRecover()
892
893
894 time.Sleep(100 * time.Millisecond)
895
896 err := cl.Create(context.Background(), &corev1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "for-watch"}})
897 Expect(err).ToNot(HaveOccurred())
898 }()
899
900 event, ok := <-objWatch.ResultChan()
901 Expect(ok).To(BeTrue())
902 Expect(event.Type).To(Equal(watch.Added))
903
904 service, ok := event.Object.(*corev1.Service)
905 Expect(ok).To(BeTrue())
906 Expect(service.Name).To(Equal("for-watch"))
907 })
908
909 Context("with the DryRun option", func() {
910 It("should not create a new object", func() {
911 By("Creating a new configmap with DryRun")
912 newcm := &corev1.ConfigMap{
913 ObjectMeta: metav1.ObjectMeta{
914 Name: "new-test-cm",
915 Namespace: "ns2",
916 },
917 }
918 err := cl.Create(context.Background(), newcm, client.DryRunAll)
919 Expect(err).ToNot(HaveOccurred())
920
921 By("Getting the new configmap")
922 namespacedName := types.NamespacedName{
923 Name: "new-test-cm",
924 Namespace: "ns2",
925 }
926 obj := &corev1.ConfigMap{}
927 err = cl.Get(context.Background(), namespacedName, obj)
928 Expect(err).To(HaveOccurred())
929 Expect(apierrors.IsNotFound(err)).To(BeTrue())
930 Expect(obj).NotTo(Equal(newcm))
931 })
932
933 It("should not Update the object", func() {
934 By("Updating a new configmap with DryRun")
935 newcm := &corev1.ConfigMap{
936 ObjectMeta: metav1.ObjectMeta{
937 Name: "test-cm",
938 Namespace: "ns2",
939 ResourceVersion: "1",
940 },
941 Data: map[string]string{
942 "test-key": "new-value",
943 },
944 }
945 err := cl.Update(context.Background(), newcm, client.DryRunAll)
946 Expect(err).ToNot(HaveOccurred())
947
948 By("Getting the new configmap")
949 namespacedName := types.NamespacedName{
950 Name: "test-cm",
951 Namespace: "ns2",
952 }
953 obj := &corev1.ConfigMap{}
954 err = cl.Get(context.Background(), namespacedName, obj)
955 Expect(err).ToNot(HaveOccurred())
956 Expect(obj).To(Equal(cm))
957 Expect(obj.ObjectMeta.ResourceVersion).To(Equal(trackerAddResourceVersion))
958 })
959
960 It("Should not Delete the object", func() {
961 By("Deleting a configmap with DryRun with Delete()")
962 err := cl.Delete(context.Background(), cm, client.DryRunAll)
963 Expect(err).ToNot(HaveOccurred())
964
965 By("Deleting a configmap with DryRun with DeleteAllOf()")
966 err = cl.DeleteAllOf(context.Background(), cm, client.DryRunAll)
967 Expect(err).ToNot(HaveOccurred())
968
969 By("Getting the configmap")
970 namespacedName := types.NamespacedName{
971 Name: "test-cm",
972 Namespace: "ns2",
973 }
974 obj := &corev1.ConfigMap{}
975 err = cl.Get(context.Background(), namespacedName, obj)
976 Expect(err).ToNot(HaveOccurred())
977 Expect(obj).To(Equal(cm))
978 Expect(obj.ObjectMeta.ResourceVersion).To(Equal(trackerAddResourceVersion))
979 })
980 })
981
982 It("should be able to Patch", func() {
983 By("Patching a deployment")
984 mergePatch, err := json.Marshal(map[string]interface{}{
985 "metadata": map[string]interface{}{
986 "annotations": map[string]interface{}{
987 "foo": "bar",
988 },
989 },
990 })
991 Expect(err).NotTo(HaveOccurred())
992 err = cl.Patch(context.Background(), dep, client.RawPatch(types.StrategicMergePatchType, mergePatch))
993 Expect(err).NotTo(HaveOccurred())
994
995 By("Getting the patched deployment")
996 namespacedName := types.NamespacedName{
997 Name: "test-deployment",
998 Namespace: "ns1",
999 }
1000 obj := &appsv1.Deployment{}
1001 err = cl.Get(context.Background(), namespacedName, obj)
1002 Expect(err).NotTo(HaveOccurred())
1003 Expect(obj.Annotations["foo"]).To(Equal("bar"))
1004 Expect(obj.ObjectMeta.ResourceVersion).To(Equal("1000"))
1005 })
1006
1007 It("should ignore deletionTimestamp without finalizer on Create", func() {
1008 namespacedName := types.NamespacedName{
1009 Name: "test-cm",
1010 Namespace: "ignore-deletiontimestamp",
1011 }
1012 By("Creating a new object")
1013 now := metav1.Now()
1014 newObj := &corev1.ConfigMap{
1015 ObjectMeta: metav1.ObjectMeta{
1016 Name: namespacedName.Name,
1017 Namespace: namespacedName.Namespace,
1018 Finalizers: []string{"finalizers.sigs.k8s.io/test"},
1019 DeletionTimestamp: &now,
1020 },
1021 Data: map[string]string{
1022 "test-key": "new-value",
1023 },
1024 }
1025
1026 err := cl.Create(context.Background(), newObj)
1027 Expect(err).ToNot(HaveOccurred())
1028
1029 By("Getting the object")
1030 obj := &corev1.ConfigMap{}
1031 err = cl.Get(context.Background(), namespacedName, obj)
1032 Expect(err).ToNot(HaveOccurred())
1033 Expect(obj.DeletionTimestamp).To(BeNil())
1034
1035 })
1036
1037 It("should reject deletionTimestamp without finalizers on Build", func() {
1038 namespacedName := types.NamespacedName{
1039 Name: "test-cm",
1040 Namespace: "reject-deletiontimestamp-no-finalizers",
1041 }
1042 By("Build with a new object without finalizer")
1043 now := metav1.Now()
1044 obj := &corev1.ConfigMap{
1045 ObjectMeta: metav1.ObjectMeta{
1046 Name: namespacedName.Name,
1047 Namespace: namespacedName.Namespace,
1048 DeletionTimestamp: &now,
1049 },
1050 Data: map[string]string{
1051 "test-key": "new-value",
1052 },
1053 }
1054
1055 Expect(func() { NewClientBuilder().WithObjects(obj).Build() }).To(Panic())
1056
1057 By("Build with a new object with finalizer")
1058 newObj := &corev1.ConfigMap{
1059 ObjectMeta: metav1.ObjectMeta{
1060 Name: namespacedName.Name,
1061 Namespace: namespacedName.Namespace,
1062 Finalizers: []string{"finalizers.sigs.k8s.io/test"},
1063 DeletionTimestamp: &now,
1064 },
1065 Data: map[string]string{
1066 "test-key": "new-value",
1067 },
1068 }
1069
1070 cl := NewClientBuilder().WithObjects(newObj).Build()
1071
1072 By("Getting the object")
1073 obj = &corev1.ConfigMap{}
1074 err := cl.Get(context.Background(), namespacedName, obj)
1075 Expect(err).ToNot(HaveOccurred())
1076
1077 })
1078
1079 It("should reject changes to deletionTimestamp on Patch", func() {
1080 namespacedName := types.NamespacedName{
1081 Name: "test-cm",
1082 Namespace: "reject-deletiontimestamp",
1083 }
1084 By("Creating a new object")
1085 now := metav1.Now()
1086 newObj := &corev1.ConfigMap{
1087 ObjectMeta: metav1.ObjectMeta{
1088 Name: namespacedName.Name,
1089 Namespace: namespacedName.Namespace,
1090 Finalizers: []string{"finalizers.sigs.k8s.io/test"},
1091 },
1092 Data: map[string]string{
1093 "test-key": "new-value",
1094 },
1095 }
1096 err := cl.Create(context.Background(), newObj)
1097 Expect(err).ToNot(HaveOccurred())
1098
1099 By("Add a deletionTimestamp")
1100 obj := &corev1.ConfigMap{
1101 ObjectMeta: metav1.ObjectMeta{
1102 Name: namespacedName.Name,
1103 Namespace: namespacedName.Namespace,
1104 Finalizers: []string{},
1105 DeletionTimestamp: &now,
1106 },
1107 }
1108 err = cl.Patch(context.Background(), obj, client.MergeFrom(newObj))
1109 Expect(err).To(HaveOccurred())
1110
1111 By("Deleting the object")
1112 err = cl.Delete(context.Background(), newObj)
1113 Expect(err).ToNot(HaveOccurred())
1114
1115 By("Getting the object")
1116 obj = &corev1.ConfigMap{}
1117 err = cl.Get(context.Background(), namespacedName, obj)
1118 Expect(err).ToNot(HaveOccurred())
1119 Expect(obj.DeletionTimestamp).NotTo(BeNil())
1120
1121 By("Changing the deletionTimestamp to new value")
1122 newObj = &corev1.ConfigMap{}
1123 t := metav1.NewTime(time.Now().Add(time.Second))
1124 newObj.DeletionTimestamp = &t
1125 err = cl.Patch(context.Background(), newObj, client.MergeFrom(obj))
1126 Expect(err).To(HaveOccurred())
1127
1128 By("Removing deletionTimestamp")
1129 newObj = &corev1.ConfigMap{
1130 ObjectMeta: metav1.ObjectMeta{
1131 Name: namespacedName.Name,
1132 Namespace: namespacedName.Namespace,
1133 DeletionTimestamp: nil,
1134 },
1135 }
1136 err = cl.Patch(context.Background(), newObj, client.MergeFrom(obj))
1137 Expect(err).To(HaveOccurred())
1138
1139 })
1140
1141 It("should handle finalizers on Patch", func() {
1142 namespacedName := types.NamespacedName{
1143 Name: "test-cm",
1144 Namespace: "delete-with-finalizers",
1145 }
1146 By("Creating a new object")
1147 newObj := &corev1.ConfigMap{
1148 ObjectMeta: metav1.ObjectMeta{
1149 Name: namespacedName.Name,
1150 Namespace: namespacedName.Namespace,
1151 Finalizers: []string{"finalizers.sigs.k8s.io/test"},
1152 },
1153 Data: map[string]string{
1154 "test-key": "new-value",
1155 },
1156 }
1157 err := cl.Create(context.Background(), newObj)
1158 Expect(err).ToNot(HaveOccurred())
1159
1160 By("Deleting the object")
1161 err = cl.Delete(context.Background(), newObj)
1162 Expect(err).ToNot(HaveOccurred())
1163
1164 By("Removing the finalizer")
1165 obj := &corev1.ConfigMap{
1166 ObjectMeta: metav1.ObjectMeta{
1167 Name: namespacedName.Name,
1168 Namespace: namespacedName.Namespace,
1169 Finalizers: []string{},
1170 },
1171 }
1172 err = cl.Patch(context.Background(), obj, client.MergeFrom(newObj))
1173 Expect(err).ToNot(HaveOccurred())
1174
1175 By("Getting the object")
1176 obj = &corev1.ConfigMap{}
1177 err = cl.Get(context.Background(), namespacedName, obj)
1178 Expect(apierrors.IsNotFound(err)).To(BeTrue())
1179 })
1180
1181 It("should remove finalizers of the object on Patch", func() {
1182 namespacedName := types.NamespacedName{
1183 Name: "test-cm",
1184 Namespace: "patch-finalizers-in-obj",
1185 }
1186 By("Creating a new object")
1187 obj := &corev1.ConfigMap{
1188 ObjectMeta: metav1.ObjectMeta{
1189 Name: namespacedName.Name,
1190 Namespace: namespacedName.Namespace,
1191 Finalizers: []string{"finalizers.sigs.k8s.io/test"},
1192 },
1193 Data: map[string]string{
1194 "test-key": "new-value",
1195 },
1196 }
1197 err := cl.Create(context.Background(), obj)
1198 Expect(err).ToNot(HaveOccurred())
1199
1200 By("Removing the finalizer")
1201 mergePatch, err := json.Marshal(map[string]interface{}{
1202 "metadata": map[string]interface{}{
1203 "$deleteFromPrimitiveList/finalizers": []string{
1204 "finalizers.sigs.k8s.io/test",
1205 },
1206 },
1207 })
1208 Expect(err).ToNot(HaveOccurred())
1209 err = cl.Patch(context.Background(), obj, client.RawPatch(types.StrategicMergePatchType, mergePatch))
1210 Expect(err).ToNot(HaveOccurred())
1211
1212 By("Check the finalizer has been removed in the object")
1213 Expect(obj.Finalizers).To(BeEmpty())
1214
1215 By("Check the finalizer has been removed in client")
1216 newObj := &corev1.ConfigMap{}
1217 err = cl.Get(context.Background(), namespacedName, newObj)
1218 Expect(err).ToNot(HaveOccurred())
1219 Expect(newObj.Finalizers).To(BeEmpty())
1220 })
1221
1222 }
1223
1224 Context("with default scheme.Scheme", func() {
1225 BeforeEach(func() {
1226 cl = NewClientBuilder().
1227 WithObjects(dep, dep2, cm).
1228 Build()
1229 })
1230 AssertClientWithoutIndexBehavior()
1231 })
1232
1233 Context("with given scheme", func() {
1234 BeforeEach(func() {
1235 scheme := runtime.NewScheme()
1236 Expect(corev1.AddToScheme(scheme)).To(Succeed())
1237 Expect(appsv1.AddToScheme(scheme)).To(Succeed())
1238 Expect(coordinationv1.AddToScheme(scheme)).To(Succeed())
1239 cl = NewClientBuilder().
1240 WithScheme(scheme).
1241 WithObjects(cm).
1242 WithLists(&appsv1.DeploymentList{Items: []appsv1.Deployment{*dep, *dep2}}).
1243 Build()
1244 })
1245 AssertClientWithoutIndexBehavior()
1246 })
1247
1248 Context("with Indexes", func() {
1249 depReplicasIndexer := func(obj client.Object) []string {
1250 dep, ok := obj.(*appsv1.Deployment)
1251 if !ok {
1252 panic(fmt.Errorf("indexer function for type %T's spec.replicas field received"+
1253 " object of type %T, this should never happen", appsv1.Deployment{}, obj))
1254 }
1255 indexVal := ""
1256 if dep.Spec.Replicas != nil {
1257 indexVal = strconv.Itoa(int(*dep.Spec.Replicas))
1258 }
1259 return []string{indexVal}
1260 }
1261
1262 depStrategyTypeIndexer := func(obj client.Object) []string {
1263 dep, ok := obj.(*appsv1.Deployment)
1264 if !ok {
1265 panic(fmt.Errorf("indexer function for type %T's spec.strategy.type field received"+
1266 " object of type %T, this should never happen", appsv1.Deployment{}, obj))
1267 }
1268 return []string{string(dep.Spec.Strategy.Type)}
1269 }
1270
1271 var cb *ClientBuilder
1272 BeforeEach(func() {
1273 cb = NewClientBuilder().
1274 WithObjects(dep, dep2, cm).
1275 WithIndex(&appsv1.Deployment{}, "spec.replicas", depReplicasIndexer)
1276 })
1277
1278 Context("client has just one Index", func() {
1279 BeforeEach(func() { cl = cb.Build() })
1280
1281 Context("behavior that doesn't use an Index", func() {
1282 AssertClientWithoutIndexBehavior()
1283 })
1284
1285 Context("filtered List using field selector", func() {
1286 It("errors when there's no Index for the GroupVersionResource", func() {
1287 listOpts := &client.ListOptions{
1288 FieldSelector: fields.OneTermEqualSelector("key", "val"),
1289 }
1290 err := cl.List(context.Background(), &corev1.ConfigMapList{}, listOpts)
1291 Expect(err).To(HaveOccurred())
1292 })
1293
1294 It("errors when there's no Index matching the field name", func() {
1295 listOpts := &client.ListOptions{
1296 FieldSelector: fields.OneTermEqualSelector("spec.paused", "false"),
1297 }
1298 err := cl.List(context.Background(), &appsv1.DeploymentList{}, listOpts)
1299 Expect(err).To(HaveOccurred())
1300 })
1301
1302 It("errors when field selector uses two requirements", func() {
1303 listOpts := &client.ListOptions{
1304 FieldSelector: fields.AndSelectors(
1305 fields.OneTermEqualSelector("spec.replicas", "1"),
1306 fields.OneTermEqualSelector("spec.strategy.type", string(appsv1.RecreateDeploymentStrategyType)),
1307 )}
1308 err := cl.List(context.Background(), &appsv1.DeploymentList{}, listOpts)
1309 Expect(err).To(HaveOccurred())
1310 })
1311
1312 It("returns two deployments that match the only field selector requirement", func() {
1313 listOpts := &client.ListOptions{
1314 FieldSelector: fields.OneTermEqualSelector("spec.replicas", "1"),
1315 }
1316 list := &appsv1.DeploymentList{}
1317 Expect(cl.List(context.Background(), list, listOpts)).To(Succeed())
1318 Expect(list.Items).To(ConsistOf(*dep, *dep2))
1319 })
1320
1321 It("returns no object because no object matches the only field selector requirement", func() {
1322 listOpts := &client.ListOptions{
1323 FieldSelector: fields.OneTermEqualSelector("spec.replicas", "2"),
1324 }
1325 list := &appsv1.DeploymentList{}
1326 Expect(cl.List(context.Background(), list, listOpts)).To(Succeed())
1327 Expect(list.Items).To(BeEmpty())
1328 })
1329
1330 It("returns deployment that matches both the field and label selectors", func() {
1331 listOpts := &client.ListOptions{
1332 FieldSelector: fields.OneTermEqualSelector("spec.replicas", "1"),
1333 LabelSelector: labels.SelectorFromSet(dep2.Labels),
1334 }
1335 list := &appsv1.DeploymentList{}
1336 Expect(cl.List(context.Background(), list, listOpts)).To(Succeed())
1337 Expect(list.Items).To(ConsistOf(*dep2))
1338 })
1339
1340 It("returns no object even if field selector matches because label selector doesn't", func() {
1341 listOpts := &client.ListOptions{
1342 FieldSelector: fields.OneTermEqualSelector("spec.replicas", "1"),
1343 LabelSelector: labels.Nothing(),
1344 }
1345 list := &appsv1.DeploymentList{}
1346 Expect(cl.List(context.Background(), list, listOpts)).To(Succeed())
1347 Expect(list.Items).To(BeEmpty())
1348 })
1349
1350 It("returns no object even if label selector matches because field selector doesn't", func() {
1351 listOpts := &client.ListOptions{
1352 FieldSelector: fields.OneTermEqualSelector("spec.replicas", "2"),
1353 LabelSelector: labels.Everything(),
1354 }
1355 list := &appsv1.DeploymentList{}
1356 Expect(cl.List(context.Background(), list, listOpts)).To(Succeed())
1357 Expect(list.Items).To(BeEmpty())
1358 })
1359 })
1360 })
1361
1362 Context("client has two Indexes", func() {
1363 BeforeEach(func() {
1364 cl = cb.WithIndex(&appsv1.Deployment{}, "spec.strategy.type", depStrategyTypeIndexer).Build()
1365 })
1366
1367 Context("behavior that doesn't use an Index", func() {
1368 AssertClientWithoutIndexBehavior()
1369 })
1370
1371 Context("filtered List using field selector", func() {
1372 It("uses the second index to retrieve the indexed objects when there are matches", func() {
1373 listOpts := &client.ListOptions{
1374 FieldSelector: fields.OneTermEqualSelector("spec.strategy.type", string(appsv1.RecreateDeploymentStrategyType)),
1375 }
1376 list := &appsv1.DeploymentList{}
1377 Expect(cl.List(context.Background(), list, listOpts)).To(Succeed())
1378 Expect(list.Items).To(ConsistOf(*dep))
1379 })
1380
1381 It("uses the second index to retrieve the indexed objects when there are no matches", func() {
1382 listOpts := &client.ListOptions{
1383 FieldSelector: fields.OneTermEqualSelector("spec.strategy.type", string(appsv1.RollingUpdateDeploymentStrategyType)),
1384 }
1385 list := &appsv1.DeploymentList{}
1386 Expect(cl.List(context.Background(), list, listOpts)).To(Succeed())
1387 Expect(list.Items).To(BeEmpty())
1388 })
1389
1390 It("no error when field selector uses two requirements", func() {
1391 listOpts := &client.ListOptions{
1392 FieldSelector: fields.AndSelectors(
1393 fields.OneTermEqualSelector("spec.replicas", "1"),
1394 fields.OneTermEqualSelector("spec.strategy.type", string(appsv1.RecreateDeploymentStrategyType)),
1395 )}
1396 list := &appsv1.DeploymentList{}
1397 Expect(cl.List(context.Background(), list, listOpts)).To(Succeed())
1398 Expect(list.Items).To(ConsistOf(*dep))
1399 })
1400 })
1401 })
1402 })
1403
1404 It("should set the ResourceVersion to 999 when adding an object to the tracker", func() {
1405 cl := NewClientBuilder().WithObjects(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "cm"}}).Build()
1406
1407 retrieved := &corev1.Secret{}
1408 Expect(cl.Get(context.Background(), types.NamespacedName{Name: "cm"}, retrieved)).To(Succeed())
1409
1410 reference := &corev1.Secret{
1411 ObjectMeta: metav1.ObjectMeta{
1412 Name: "cm",
1413 ResourceVersion: "999",
1414 },
1415 }
1416 Expect(retrieved).To(Equal(reference))
1417 })
1418
1419 It("should be able to build with given tracker and get resource", func() {
1420 clientSet := fake.NewSimpleClientset(dep)
1421 cl := NewClientBuilder().WithRuntimeObjects(dep2).WithObjectTracker(clientSet.Tracker()).Build()
1422
1423 By("Getting a deployment")
1424 namespacedName := types.NamespacedName{
1425 Name: "test-deployment",
1426 Namespace: "ns1",
1427 }
1428 obj := &appsv1.Deployment{}
1429 err := cl.Get(context.Background(), namespacedName, obj)
1430 Expect(err).ToNot(HaveOccurred())
1431 Expect(obj).To(Equal(dep))
1432
1433 By("Getting a deployment from clientSet")
1434 csDep2, err := clientSet.AppsV1().Deployments("ns1").Get(context.Background(), "test-deployment-2", metav1.GetOptions{})
1435 Expect(err).ToNot(HaveOccurred())
1436 Expect(csDep2).To(Equal(dep2))
1437
1438 By("Getting a new deployment")
1439 namespacedName3 := types.NamespacedName{
1440 Name: "test-deployment-3",
1441 Namespace: "ns1",
1442 }
1443
1444 dep3 := &appsv1.Deployment{
1445 TypeMeta: metav1.TypeMeta{
1446 APIVersion: "apps/v1",
1447 Kind: "Deployment",
1448 },
1449 ObjectMeta: metav1.ObjectMeta{
1450 Name: "test-deployment-3",
1451 Namespace: "ns1",
1452 Labels: map[string]string{
1453 "test-label": "label-value",
1454 },
1455 ResourceVersion: trackerAddResourceVersion,
1456 },
1457 }
1458
1459 _, err = clientSet.AppsV1().Deployments("ns1").Create(context.Background(), dep3, metav1.CreateOptions{})
1460 Expect(err).ToNot(HaveOccurred())
1461
1462 obj = &appsv1.Deployment{}
1463 err = cl.Get(context.Background(), namespacedName3, obj)
1464 Expect(err).ToNot(HaveOccurred())
1465 Expect(obj).To(Equal(dep3))
1466 })
1467
1468 It("should not change the status of typed objects that have a status subresource on update", func() {
1469 obj := &corev1.Pod{
1470 ObjectMeta: metav1.ObjectMeta{
1471 Name: "pod",
1472 },
1473 }
1474 cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1475
1476 obj.Status.Phase = "Running"
1477 Expect(cl.Update(context.Background(), obj)).To(Succeed())
1478
1479 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(obj), obj)).To(Succeed())
1480
1481 Expect(obj.Status).To(BeEquivalentTo(corev1.PodStatus{}))
1482 })
1483
1484 It("should return a conflict error when an incorrect RV is used on status update", func() {
1485 obj := &corev1.Node{
1486 ObjectMeta: metav1.ObjectMeta{
1487 Name: "node",
1488 ResourceVersion: trackerAddResourceVersion,
1489 },
1490 }
1491 cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1492
1493 obj.Status.Phase = corev1.NodeRunning
1494 obj.ResourceVersion = "invalid"
1495 err := cl.Status().Update(context.Background(), obj)
1496 Expect(apierrors.IsConflict(err)).To(BeTrue())
1497 })
1498
1499 It("should not change non-status field of typed objects that have a status subresource on status update", func() {
1500 obj := &corev1.Node{
1501 ObjectMeta: metav1.ObjectMeta{
1502 Name: "node",
1503 },
1504 Spec: corev1.NodeSpec{
1505 PodCIDR: "old-cidr",
1506 },
1507 Status: corev1.NodeStatus{
1508 NodeInfo: corev1.NodeSystemInfo{
1509 MachineID: "machine-id",
1510 },
1511 },
1512 }
1513 cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1514 objOriginal := obj.DeepCopy()
1515
1516 obj.Spec.PodCIDR = cidrFromStatusUpdate
1517 obj.Annotations = map[string]string{
1518 "some-annotation-key": "some-annotation-value",
1519 }
1520 obj.Labels = map[string]string{
1521 "some-label-key": "some-label-value",
1522 }
1523
1524 obj.Status.NodeInfo.MachineID = machineIDFromStatusUpdate
1525 Expect(cl.Status().Update(context.Background(), obj)).NotTo(HaveOccurred())
1526
1527 actual := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: obj.Name}}
1528 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(actual), actual)).NotTo(HaveOccurred())
1529
1530 objOriginal.APIVersion = actual.APIVersion
1531 objOriginal.Kind = actual.Kind
1532 objOriginal.ResourceVersion = actual.ResourceVersion
1533 objOriginal.Status.NodeInfo.MachineID = machineIDFromStatusUpdate
1534 Expect(cmp.Diff(objOriginal, actual)).To(BeEmpty())
1535 })
1536
1537 It("should be able to update an object after updating an object's status", func() {
1538 obj := &corev1.Node{
1539 ObjectMeta: metav1.ObjectMeta{
1540 Name: "node",
1541 },
1542 Spec: corev1.NodeSpec{
1543 PodCIDR: "old-cidr",
1544 },
1545 Status: corev1.NodeStatus{
1546 NodeInfo: corev1.NodeSystemInfo{
1547 MachineID: "machine-id",
1548 },
1549 },
1550 }
1551 cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1552 expectedObj := obj.DeepCopy()
1553
1554 obj.Status.NodeInfo.MachineID = machineIDFromStatusUpdate
1555 Expect(cl.Status().Update(context.Background(), obj)).NotTo(HaveOccurred())
1556
1557 obj.Annotations = map[string]string{
1558 "some-annotation-key": "some",
1559 }
1560 expectedObj.Annotations = map[string]string{
1561 "some-annotation-key": "some",
1562 }
1563 Expect(cl.Update(context.Background(), obj)).NotTo(HaveOccurred())
1564
1565 actual := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: obj.Name}}
1566 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(actual), actual)).NotTo(HaveOccurred())
1567
1568 expectedObj.APIVersion = actual.APIVersion
1569 expectedObj.Kind = actual.Kind
1570 expectedObj.ResourceVersion = actual.ResourceVersion
1571 expectedObj.Status.NodeInfo.MachineID = machineIDFromStatusUpdate
1572 Expect(cmp.Diff(expectedObj, actual)).To(BeEmpty())
1573 })
1574
1575 It("should be able to update an object's status after updating an object", func() {
1576 obj := &corev1.Node{
1577 ObjectMeta: metav1.ObjectMeta{
1578 Name: "node",
1579 },
1580 Spec: corev1.NodeSpec{
1581 PodCIDR: "old-cidr",
1582 },
1583 Status: corev1.NodeStatus{
1584 NodeInfo: corev1.NodeSystemInfo{
1585 MachineID: "machine-id",
1586 },
1587 },
1588 }
1589 cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1590 expectedObj := obj.DeepCopy()
1591
1592 obj.Annotations = map[string]string{
1593 "some-annotation-key": "some",
1594 }
1595 expectedObj.Annotations = map[string]string{
1596 "some-annotation-key": "some",
1597 }
1598 Expect(cl.Update(context.Background(), obj)).NotTo(HaveOccurred())
1599
1600 obj.Spec.PodCIDR = cidrFromStatusUpdate
1601 obj.Status.NodeInfo.MachineID = machineIDFromStatusUpdate
1602 Expect(cl.Status().Update(context.Background(), obj)).NotTo(HaveOccurred())
1603
1604 actual := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: obj.Name}}
1605 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(actual), actual)).NotTo(HaveOccurred())
1606
1607 expectedObj.APIVersion = actual.APIVersion
1608 expectedObj.Kind = actual.Kind
1609 expectedObj.ResourceVersion = actual.ResourceVersion
1610 expectedObj.Status.NodeInfo.MachineID = machineIDFromStatusUpdate
1611 Expect(cmp.Diff(expectedObj, actual)).To(BeEmpty())
1612 })
1613
1614 It("Should only override status fields of typed objects that have a status subresource on status update", func() {
1615 obj := &corev1.Node{
1616 ObjectMeta: metav1.ObjectMeta{
1617 Name: "node",
1618 },
1619 Spec: corev1.NodeSpec{
1620 PodCIDR: "old-cidr",
1621 },
1622 Status: corev1.NodeStatus{
1623 NodeInfo: corev1.NodeSystemInfo{
1624 MachineID: "machine-id",
1625 },
1626 },
1627 }
1628 cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1629 objOriginal := obj.DeepCopy()
1630
1631 obj.Status.Phase = corev1.NodeRunning
1632 Expect(cl.Status().Update(context.Background(), obj)).NotTo(HaveOccurred())
1633
1634 actual := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: obj.Name}}
1635 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(actual), actual)).NotTo(HaveOccurred())
1636
1637 objOriginal.APIVersion = actual.APIVersion
1638 objOriginal.Kind = actual.Kind
1639 objOriginal.ResourceVersion = actual.ResourceVersion
1640 Expect(cmp.Diff(objOriginal, actual)).ToNot(BeEmpty())
1641 Expect(objOriginal.Status.NodeInfo.MachineID).To(Equal(actual.Status.NodeInfo.MachineID))
1642 Expect(objOriginal.Status.Phase).ToNot(Equal(actual.Status.Phase))
1643 })
1644
1645 It("should be able to change typed objects that have a scale subresource on patch", func() {
1646 obj := &appsv1.Deployment{
1647 ObjectMeta: metav1.ObjectMeta{
1648 Name: "deploy",
1649 },
1650 }
1651 cl := NewClientBuilder().WithObjects(obj).Build()
1652 objOriginal := obj.DeepCopy()
1653
1654 patch := []byte(fmt.Sprintf(`{"spec":{"replicas":%d}}`, 2))
1655 Expect(cl.SubResource("scale").Patch(context.Background(), obj, client.RawPatch(types.MergePatchType, patch))).NotTo(HaveOccurred())
1656
1657 actual := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: obj.Name}}
1658 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(actual), actual)).To(Succeed())
1659
1660 objOriginal.APIVersion = actual.APIVersion
1661 objOriginal.Kind = actual.Kind
1662 objOriginal.ResourceVersion = actual.ResourceVersion
1663 objOriginal.Spec.Replicas = ptr.To(int32(2))
1664 Expect(cmp.Diff(objOriginal, actual)).To(BeEmpty())
1665 })
1666
1667 It("should not change the status of typed objects that have a status subresource on patch", func() {
1668 obj := &corev1.Pod{
1669 ObjectMeta: metav1.ObjectMeta{
1670 Name: "node",
1671 },
1672 }
1673 Expect(cl.Create(context.Background(), obj)).To(Succeed())
1674 original := obj.DeepCopy()
1675
1676 obj.Status.Phase = "Running"
1677 Expect(cl.Patch(context.Background(), obj, client.MergeFrom(original))).To(Succeed())
1678
1679 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(obj), obj)).To(Succeed())
1680
1681 Expect(obj.Status).To(BeEquivalentTo(corev1.PodStatus{}))
1682 })
1683
1684 It("should not change non-status field of typed objects that have a status subresource on status patch", func() {
1685 obj := &corev1.Node{
1686 ObjectMeta: metav1.ObjectMeta{
1687 Name: "node",
1688 },
1689 Spec: corev1.NodeSpec{
1690 PodCIDR: "old-cidr",
1691 },
1692 }
1693 cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1694 objOriginal := obj.DeepCopy()
1695
1696 obj.Spec.PodCIDR = cidrFromStatusUpdate
1697 obj.Status.NodeInfo.MachineID = "machine-id"
1698 Expect(cl.Status().Patch(context.Background(), obj, client.MergeFrom(objOriginal))).NotTo(HaveOccurred())
1699
1700 actual := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: obj.Name}}
1701 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(actual), actual)).NotTo(HaveOccurred())
1702
1703 objOriginal.APIVersion = actual.APIVersion
1704 objOriginal.Kind = actual.Kind
1705 objOriginal.ResourceVersion = actual.ResourceVersion
1706 objOriginal.Status.NodeInfo.MachineID = "machine-id"
1707 Expect(cmp.Diff(objOriginal, actual)).To(BeEmpty())
1708 })
1709
1710 It("should not change the status of unstructured objects that are configured to have a status subresource on update", func() {
1711 obj := &unstructured.Unstructured{}
1712 obj.SetAPIVersion("foo/v1")
1713 obj.SetKind("Foo")
1714 obj.SetName("a-foo")
1715
1716 err := unstructured.SetNestedField(obj.Object, map[string]any{"state": "old"}, "status")
1717 Expect(err).NotTo(HaveOccurred())
1718
1719 cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1720
1721 err = unstructured.SetNestedField(obj.Object, map[string]any{"state": "new"}, "status")
1722 Expect(err).ToNot(HaveOccurred())
1723
1724 Expect(cl.Update(context.Background(), obj)).To(Succeed())
1725
1726 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(obj), obj)).To(Succeed())
1727
1728 Expect(obj.Object["status"]).To(BeEquivalentTo(map[string]any{"state": "old"}))
1729 })
1730
1731 It("should not change non-status fields of unstructured objects that are configured to have a status subresource on status update", func() {
1732 obj := &unstructured.Unstructured{}
1733 obj.SetAPIVersion("foo/v1")
1734 obj.SetKind("Foo")
1735 obj.SetName("a-foo")
1736
1737 err := unstructured.SetNestedField(obj.Object, "original", "spec")
1738 Expect(err).NotTo(HaveOccurred())
1739
1740 cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1741
1742 err = unstructured.SetNestedField(obj.Object, "from-status-update", "spec")
1743 Expect(err).NotTo(HaveOccurred())
1744 err = unstructured.SetNestedField(obj.Object, map[string]any{"state": "new"}, "status")
1745 Expect(err).ToNot(HaveOccurred())
1746
1747 Expect(cl.Status().Update(context.Background(), obj)).To(Succeed())
1748 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(obj), obj)).To(Succeed())
1749
1750 Expect(obj.Object["status"]).To(BeEquivalentTo(map[string]any{"state": "new"}))
1751 Expect(obj.Object["spec"]).To(BeEquivalentTo("original"))
1752 })
1753
1754 It("should not change the status of known unstructured objects that have a status subresource on update", func() {
1755 obj := &corev1.Pod{
1756 ObjectMeta: metav1.ObjectMeta{
1757 Name: "pod",
1758 },
1759 Spec: corev1.PodSpec{
1760 RestartPolicy: corev1.RestartPolicyAlways,
1761 },
1762 Status: corev1.PodStatus{
1763 Phase: corev1.PodPending,
1764 },
1765 }
1766 cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1767
1768
1769 u := &unstructured.Unstructured{}
1770 u.SetAPIVersion("v1")
1771 u.SetKind("Pod")
1772 u.SetName(obj.Name)
1773 err := cl.Get(context.Background(), client.ObjectKeyFromObject(u), u)
1774 Expect(err).NotTo(HaveOccurred())
1775
1776 err = unstructured.SetNestedField(u.Object, string(corev1.RestartPolicyNever), "spec", "restartPolicy")
1777 Expect(err).NotTo(HaveOccurred())
1778 err = unstructured.SetNestedField(u.Object, string(corev1.PodRunning), "status", "phase")
1779 Expect(err).NotTo(HaveOccurred())
1780
1781 Expect(cl.Update(context.Background(), u)).To(Succeed())
1782
1783 actual := &corev1.Pod{}
1784 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(obj), actual)).To(Succeed())
1785 obj.APIVersion = u.GetAPIVersion()
1786 obj.Kind = u.GetKind()
1787 obj.ResourceVersion = actual.ResourceVersion
1788
1789 obj.Spec.RestartPolicy = corev1.RestartPolicyNever
1790 Expect(cmp.Diff(obj, actual)).To(BeEmpty())
1791 })
1792
1793 It("should not change non-status field of known unstructured objects that have a status subresource on status update", func() {
1794 obj := &corev1.Pod{
1795 ObjectMeta: metav1.ObjectMeta{
1796 Name: "pod",
1797 },
1798 Spec: corev1.PodSpec{
1799 RestartPolicy: corev1.RestartPolicyAlways,
1800 },
1801 Status: corev1.PodStatus{
1802 Phase: corev1.PodPending,
1803 },
1804 }
1805 cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1806
1807
1808 u := &unstructured.Unstructured{}
1809 u.SetAPIVersion("v1")
1810 u.SetKind("Pod")
1811 u.SetName(obj.Name)
1812 err := cl.Get(context.Background(), client.ObjectKeyFromObject(u), u)
1813 Expect(err).NotTo(HaveOccurred())
1814
1815 err = unstructured.SetNestedField(u.Object, string(corev1.RestartPolicyNever), "spec", "restartPolicy")
1816 Expect(err).NotTo(HaveOccurred())
1817 err = unstructured.SetNestedField(u.Object, string(corev1.PodRunning), "status", "phase")
1818 Expect(err).NotTo(HaveOccurred())
1819
1820 Expect(cl.Status().Update(context.Background(), u)).To(Succeed())
1821
1822 actual := &corev1.Pod{}
1823 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(obj), actual)).To(Succeed())
1824 obj.ResourceVersion = actual.ResourceVersion
1825
1826 obj.Status.Phase = corev1.PodRunning
1827 Expect(cmp.Diff(obj, actual)).To(BeEmpty())
1828 })
1829
1830 It("should not change the status of unstructured objects that are configured to have a status subresource on patch", func() {
1831 obj := &unstructured.Unstructured{}
1832 obj.SetAPIVersion("foo/v1")
1833 obj.SetKind("Foo")
1834 obj.SetName("a-foo")
1835 cl := NewClientBuilder().WithStatusSubresource(obj).Build()
1836
1837 Expect(cl.Create(context.Background(), obj)).To(Succeed())
1838 original := obj.DeepCopy()
1839
1840 err := unstructured.SetNestedField(obj.Object, map[string]interface{}{"count": int64(2)}, "status")
1841 Expect(err).ToNot(HaveOccurred())
1842 Expect(cl.Patch(context.Background(), obj, client.MergeFrom(original))).To(Succeed())
1843
1844 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(obj), obj)).To(Succeed())
1845
1846 Expect(obj.Object["status"]).To(BeNil())
1847
1848 })
1849
1850 It("should not change non-status fields of unstructured objects that are configured to have a status subresource on status patch", func() {
1851 obj := &unstructured.Unstructured{}
1852 obj.SetAPIVersion("foo/v1")
1853 obj.SetKind("Foo")
1854 obj.SetName("a-foo")
1855
1856 err := unstructured.SetNestedField(obj.Object, "original", "spec")
1857 Expect(err).NotTo(HaveOccurred())
1858
1859 cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1860 original := obj.DeepCopy()
1861
1862 err = unstructured.SetNestedField(obj.Object, "from-status-update", "spec")
1863 Expect(err).NotTo(HaveOccurred())
1864 err = unstructured.SetNestedField(obj.Object, map[string]any{"state": "new"}, "status")
1865 Expect(err).ToNot(HaveOccurred())
1866
1867 Expect(cl.Status().Patch(context.Background(), obj, client.MergeFrom(original))).To(Succeed())
1868 Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(obj), obj)).To(Succeed())
1869
1870 Expect(obj.Object["status"]).To(BeEquivalentTo(map[string]any{"state": "new"}))
1871 Expect(obj.Object["spec"]).To(BeEquivalentTo("original"))
1872 })
1873
1874 It("should return not found on status update of resources that don't have a status subresource", func() {
1875 obj := &unstructured.Unstructured{}
1876 obj.SetAPIVersion("foo/v1")
1877 obj.SetKind("Foo")
1878 obj.SetName("a-foo")
1879
1880 cl := NewClientBuilder().WithObjects(obj).Build()
1881
1882 err := cl.Status().Update(context.Background(), obj)
1883 Expect(apierrors.IsNotFound(err)).To(BeTrue())
1884 })
1885
1886 evictionTypes := []client.Object{
1887 &policyv1beta1.Eviction{},
1888 &policyv1.Eviction{},
1889 }
1890 for _, tp := range evictionTypes {
1891 It("should delete a pod through the eviction subresource", func() {
1892 pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
1893
1894 cl := NewClientBuilder().WithObjects(pod).Build()
1895
1896 err := cl.SubResource("eviction").Create(context.Background(), pod, tp)
1897 Expect(err).NotTo(HaveOccurred())
1898
1899 err = cl.Get(context.Background(), client.ObjectKeyFromObject(pod), pod)
1900 Expect(apierrors.IsNotFound(err)).To(BeTrue())
1901 })
1902
1903 It("should return not found when attempting to evict a pod that doesn't exist", func() {
1904 cl := NewClientBuilder().Build()
1905
1906 pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
1907 err := cl.SubResource("eviction").Create(context.Background(), pod, tp)
1908 Expect(apierrors.IsNotFound(err)).To(BeTrue())
1909 })
1910
1911 It("should return not found when attempting to evict something other than a pod", func() {
1912 ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
1913 cl := NewClientBuilder().WithObjects(ns).Build()
1914
1915 err := cl.SubResource("eviction").Create(context.Background(), ns, tp)
1916 Expect(apierrors.IsNotFound(err)).To(BeTrue())
1917 })
1918
1919 It("should return an error when using the wrong subresource", func() {
1920 cl := NewClientBuilder().Build()
1921
1922 err := cl.SubResource("eviction-subresource").Create(context.Background(), &corev1.Namespace{}, tp)
1923 Expect(err).To(HaveOccurred())
1924 })
1925 }
1926
1927 It("should error when creating an eviction with the wrong type", func() {
1928 cl := NewClientBuilder().Build()
1929 err := cl.SubResource("eviction").Create(context.Background(), &corev1.Pod{}, &corev1.Namespace{})
1930 Expect(apierrors.IsBadRequest(err)).To(BeTrue())
1931 })
1932
1933 It("should leave typemeta empty on typed get", func() {
1934 cl := NewClientBuilder().WithObjects(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{
1935 Namespace: "default",
1936 Name: "foo",
1937 }}).Build()
1938
1939 var pod corev1.Pod
1940 Expect(cl.Get(context.Background(), client.ObjectKey{Namespace: "default", Name: "foo"}, &pod)).NotTo(HaveOccurred())
1941
1942 Expect(pod.TypeMeta).To(Equal(metav1.TypeMeta{}))
1943 })
1944
1945 It("should leave typemeta empty on typed list", func() {
1946 cl := NewClientBuilder().WithObjects(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{
1947 Namespace: "default",
1948 Name: "foo",
1949 }}).Build()
1950
1951 var podList corev1.PodList
1952 Expect(cl.List(context.Background(), &podList)).NotTo(HaveOccurred())
1953 Expect(podList.ListMeta).To(Equal(metav1.ListMeta{}))
1954 Expect(podList.Items[0].TypeMeta).To(Equal(metav1.TypeMeta{}))
1955 })
1956
1957 It("should be able to Get an object that has pointer fields for metadata", func() {
1958 schemeBuilder := &scheme.Builder{GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"}}
1959 schemeBuilder.Register(&WithPointerMeta{}, &WithPointerMetaList{})
1960 scheme := runtime.NewScheme()
1961 Expect(schemeBuilder.AddToScheme(scheme)).NotTo(HaveOccurred())
1962
1963 cl := NewClientBuilder().
1964 WithScheme(scheme).
1965 WithObjects(&WithPointerMeta{ObjectMeta: &metav1.ObjectMeta{
1966 Name: "foo",
1967 }}).
1968 Build()
1969
1970 var object WithPointerMeta
1971 Expect(cl.Get(context.Background(), client.ObjectKey{Name: "foo"}, &object)).NotTo(HaveOccurred())
1972 })
1973
1974 It("should be able to List an object type that has pointer fields for metadata", func() {
1975 schemeBuilder := &scheme.Builder{GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"}}
1976 schemeBuilder.Register(&WithPointerMeta{}, &WithPointerMetaList{})
1977 scheme := runtime.NewScheme()
1978 Expect(schemeBuilder.AddToScheme(scheme)).NotTo(HaveOccurred())
1979
1980 cl := NewClientBuilder().
1981 WithScheme(scheme).
1982 WithObjects(&WithPointerMeta{ObjectMeta: &metav1.ObjectMeta{
1983 Name: "foo",
1984 }}).
1985 Build()
1986
1987 var objectList WithPointerMetaList
1988 Expect(cl.List(context.Background(), &objectList)).NotTo(HaveOccurred())
1989 Expect(objectList.Items).To(HaveLen(1))
1990 })
1991
1992 It("should be able to List an object type that has pointer fields for metadata with no results", func() {
1993 schemeBuilder := &scheme.Builder{GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"}}
1994 schemeBuilder.Register(&WithPointerMeta{}, &WithPointerMetaList{})
1995 scheme := runtime.NewScheme()
1996 Expect(schemeBuilder.AddToScheme(scheme)).NotTo(HaveOccurred())
1997
1998 cl := NewClientBuilder().
1999 WithScheme(scheme).
2000 Build()
2001
2002 var objectList WithPointerMetaList
2003 Expect(cl.List(context.Background(), &objectList)).NotTo(HaveOccurred())
2004 Expect(objectList.Items).To(BeEmpty())
2005 })
2006
2007 It("should be able to Patch an object type that has pointer fields for metadata", func() {
2008 schemeBuilder := &scheme.Builder{GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"}}
2009 schemeBuilder.Register(&WithPointerMeta{}, &WithPointerMetaList{})
2010 scheme := runtime.NewScheme()
2011 Expect(schemeBuilder.AddToScheme(scheme)).NotTo(HaveOccurred())
2012
2013 obj := &WithPointerMeta{ObjectMeta: &metav1.ObjectMeta{
2014 Name: "foo",
2015 }}
2016 cl := NewClientBuilder().
2017 WithScheme(scheme).
2018 WithObjects(obj).
2019 Build()
2020
2021 original := obj.DeepCopy()
2022 obj.Labels = map[string]string{"foo": "bar"}
2023 Expect(cl.Patch(context.Background(), obj, client.MergeFrom(original))).NotTo(HaveOccurred())
2024
2025 Expect(cl.Get(context.Background(), client.ObjectKey{Name: "foo"}, obj)).NotTo(HaveOccurred())
2026 Expect(obj.Labels).To(Equal(map[string]string{"foo": "bar"}))
2027 })
2028
2029 It("should be able to Update an object type that has pointer fields for metadata", func() {
2030 schemeBuilder := &scheme.Builder{GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"}}
2031 schemeBuilder.Register(&WithPointerMeta{}, &WithPointerMetaList{})
2032 scheme := runtime.NewScheme()
2033 Expect(schemeBuilder.AddToScheme(scheme)).NotTo(HaveOccurred())
2034
2035 obj := &WithPointerMeta{ObjectMeta: &metav1.ObjectMeta{
2036 Name: "foo",
2037 }}
2038 cl := NewClientBuilder().
2039 WithScheme(scheme).
2040 WithObjects(obj).
2041 Build()
2042
2043 Expect(cl.Get(context.Background(), client.ObjectKey{Name: "foo"}, obj)).NotTo(HaveOccurred())
2044
2045 obj.Labels = map[string]string{"foo": "bar"}
2046 Expect(cl.Update(context.Background(), obj)).NotTo(HaveOccurred())
2047
2048 Expect(cl.Get(context.Background(), client.ObjectKey{Name: "foo"}, obj)).NotTo(HaveOccurred())
2049 Expect(obj.Labels).To(Equal(map[string]string{"foo": "bar"}))
2050 })
2051
2052 It("should be able to Delete an object type that has pointer fields for metadata", func() {
2053 schemeBuilder := &scheme.Builder{GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"}}
2054 schemeBuilder.Register(&WithPointerMeta{}, &WithPointerMetaList{})
2055 scheme := runtime.NewScheme()
2056 Expect(schemeBuilder.AddToScheme(scheme)).NotTo(HaveOccurred())
2057
2058 obj := &WithPointerMeta{ObjectMeta: &metav1.ObjectMeta{
2059 Name: "foo",
2060 }}
2061 cl := NewClientBuilder().
2062 WithScheme(scheme).
2063 WithObjects(obj).
2064 Build()
2065
2066 Expect(cl.Delete(context.Background(), obj)).NotTo(HaveOccurred())
2067
2068 err := cl.Get(context.Background(), client.ObjectKey{Name: "foo"}, obj)
2069 Expect(apierrors.IsNotFound(err)).To(BeTrue())
2070 })
2071 })
2072
2073 type WithPointerMetaList struct {
2074 *metav1.ListMeta
2075 *metav1.TypeMeta
2076 Items []*WithPointerMeta
2077 }
2078
2079 func (t *WithPointerMetaList) DeepCopy() *WithPointerMetaList {
2080 l := &WithPointerMetaList{
2081 ListMeta: t.ListMeta.DeepCopy(),
2082 }
2083 if t.TypeMeta != nil {
2084 l.TypeMeta = &metav1.TypeMeta{
2085 APIVersion: t.APIVersion,
2086 Kind: t.Kind,
2087 }
2088 }
2089 for _, item := range t.Items {
2090 l.Items = append(l.Items, item.DeepCopy())
2091 }
2092
2093 return l
2094 }
2095
2096 func (t *WithPointerMetaList) DeepCopyObject() runtime.Object {
2097 return t.DeepCopy()
2098 }
2099
2100 type WithPointerMeta struct {
2101 *metav1.TypeMeta
2102 *metav1.ObjectMeta
2103 }
2104
2105 func (t *WithPointerMeta) DeepCopy() *WithPointerMeta {
2106 w := &WithPointerMeta{
2107 ObjectMeta: t.ObjectMeta.DeepCopy(),
2108 }
2109 if t.TypeMeta != nil {
2110 w.TypeMeta = &metav1.TypeMeta{
2111 APIVersion: t.APIVersion,
2112 Kind: t.Kind,
2113 }
2114 }
2115
2116 return w
2117 }
2118
2119 func (t *WithPointerMeta) DeepCopyObject() runtime.Object {
2120 return t.DeepCopy()
2121 }
2122
2123 var _ = Describe("Fake client builder", func() {
2124 It("panics when an index with the same name and GroupVersionKind is registered twice", func() {
2125
2126 cb := NewClientBuilder().WithIndex(&appsv1.Deployment{},
2127 "test-name",
2128 func(client.Object) []string { return nil })
2129
2130 Expect(func() {
2131 cb.WithIndex(&appsv1.Deployment{},
2132 "test-name",
2133 func(client.Object) []string { return []string{"foo"} })
2134 }).To(Panic())
2135 })
2136
2137 It("should wrap the fake client with an interceptor when WithInterceptorFuncs is called", func() {
2138 var called bool
2139 cli := NewClientBuilder().WithInterceptorFuncs(interceptor.Funcs{
2140 Get: func(ctx context.Context, client client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
2141 called = true
2142 return nil
2143 },
2144 }).Build()
2145 err := cli.Get(context.Background(), client.ObjectKey{}, &corev1.Pod{})
2146 Expect(err).NotTo(HaveOccurred())
2147 Expect(called).To(BeTrue())
2148 })
2149 })
2150
View as plain text