sigs.k8s.io/controller-runtime@v0.18.2/pkg/client/fake/client_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package fake 18 19 import ( 20 "context" 21 "encoding/json" 22 "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 // It is likely starting a new goroutine is slower than progressing 893 // in the outer routine, sleep to make sure this is always true 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 // update using unstructured 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 // only the spec mutation should persist 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 // status update using unstructured 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 // only the status mutation should persist 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 // We need any realistic GroupVersionKind, the choice of apps/v1 Deployment is arbitrary. 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 })