sigs.k8s.io/cluster-api@v1.7.1/util/patch/patch_test.go (about) 1 /* 2 Copyright 2017 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 patch 18 19 import ( 20 "reflect" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 . "github.com/onsi/gomega" 25 appsv1 "k8s.io/api/apps/v1" 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/types" 30 "k8s.io/utils/ptr" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 33 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 34 "sigs.k8s.io/cluster-api/controllers/external" 35 "sigs.k8s.io/cluster-api/util/conditions" 36 ) 37 38 func TestPatchHelper(t *testing.T) { 39 ns, err := env.CreateNamespace(ctx, "test-patch-helper") 40 if err != nil { 41 t.Fatal(err) 42 } 43 defer func() { 44 if err := env.Delete(ctx, ns); err != nil { 45 t.Fatal(err) 46 } 47 }() 48 49 t.Run("should patch an unstructured object", func(t *testing.T) { 50 obj := &unstructured.Unstructured{ 51 Object: map[string]interface{}{ 52 "kind": "GenericBootstrapConfig", 53 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 54 "metadata": map[string]interface{}{ 55 "generateName": "test-bootstrap-", 56 "namespace": ns.Name, 57 }, 58 }, 59 } 60 61 t.Run("adding an owner reference, preserving its status", func(t *testing.T) { 62 g := NewWithT(t) 63 64 t.Log("Creating the unstructured object") 65 g.Expect(env.Create(ctx, obj)).To(Succeed()) 66 defer func() { 67 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 68 }() 69 key := client.ObjectKey{Name: obj.GetName(), Namespace: obj.GetNamespace()} 70 71 t.Log("Checking that the object has been created") 72 g.Eventually(func() error { 73 obj := obj.DeepCopy() 74 return env.Get(ctx, key, obj) 75 }).Should(Succeed()) 76 77 obj.Object["status"] = map[string]interface{}{ 78 "ready": true, 79 } 80 g.Expect(env.Status().Update(ctx, obj)).To(Succeed()) 81 82 t.Log("Creating a new patch helper") 83 patcher, err := NewHelper(obj, env) 84 g.Expect(err).ToNot(HaveOccurred()) 85 86 t.Log("Modifying the OwnerReferences") 87 refs := []metav1.OwnerReference{ 88 { 89 APIVersion: "cluster.x-k8s.io/v1beta1", 90 Kind: "Cluster", 91 Name: "test", 92 UID: types.UID("fake-uid"), 93 }, 94 } 95 obj.SetOwnerReferences(refs) 96 97 t.Log("Patching the unstructured object") 98 g.Expect(patcher.Patch(ctx, obj)).To(Succeed()) 99 100 t.Log("Validating that the status has been preserved") 101 ready, err := external.IsReady(obj) 102 g.Expect(err).ToNot(HaveOccurred()) 103 g.Expect(ready).To(BeTrue()) 104 105 t.Log("Validating the object has been updated") 106 g.Eventually(func() bool { 107 objAfter := obj.DeepCopy() 108 if err := env.Get(ctx, key, objAfter); err != nil { 109 return false 110 } 111 return reflect.DeepEqual(obj.GetOwnerReferences(), objAfter.GetOwnerReferences()) 112 }, timeout).Should(BeTrue()) 113 }) 114 }) 115 116 t.Run("Should patch conditions", func(t *testing.T) { 117 t.Run("on a corev1.Node object", func(t *testing.T) { 118 g := NewWithT(t) 119 120 conditionTime := metav1.Date(2015, 1, 1, 12, 0, 0, 0, metav1.Now().Location()) 121 obj := &corev1.Node{ 122 ObjectMeta: metav1.ObjectMeta{ 123 GenerateName: "node-patch-test-", 124 Namespace: ns.Name, 125 Annotations: map[string]string{ 126 "test": "1", 127 }, 128 }, 129 } 130 131 t.Log("Creating a Node object") 132 g.Expect(env.Create(ctx, obj)).To(Succeed()) 133 defer func() { 134 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 135 }() 136 key := client.ObjectKey{Name: obj.GetName()} 137 138 t.Log("Checking that the object has been created") 139 g.Eventually(func() error { 140 obj := obj.DeepCopy() 141 return env.Get(ctx, key, obj) 142 }).Should(Succeed()) 143 144 t.Log("Creating a new patch helper") 145 patcher, err := NewHelper(obj, env) 146 g.Expect(err).ToNot(HaveOccurred()) 147 148 t.Log("Appending a new condition") 149 condition := corev1.NodeCondition{ 150 Type: "CustomCondition", 151 Status: corev1.ConditionTrue, 152 LastHeartbeatTime: conditionTime, 153 LastTransitionTime: conditionTime, 154 Reason: "reason", 155 Message: "message", 156 } 157 obj.Status.Conditions = append(obj.Status.Conditions, condition) 158 159 t.Log("Patching the Node") 160 g.Expect(patcher.Patch(ctx, obj)).To(Succeed()) 161 162 t.Log("Validating the object has been updated") 163 g.Eventually(func() bool { 164 objAfter := obj.DeepCopy() 165 g.Expect(env.Get(ctx, key, objAfter)).To(Succeed()) 166 167 ok, _ := ContainElement(condition).Match(objAfter.Status.Conditions) 168 return ok 169 }, timeout).Should(BeTrue()) 170 }) 171 172 t.Run("on a clusterv1.Cluster object", func(t *testing.T) { 173 obj := &clusterv1.Cluster{ 174 ObjectMeta: metav1.ObjectMeta{ 175 GenerateName: "test-", 176 Namespace: ns.Name, 177 }, 178 } 179 180 t.Run("should mark it ready", func(t *testing.T) { 181 g := NewWithT(t) 182 183 obj := obj.DeepCopy() 184 185 t.Log("Creating the object") 186 g.Expect(env.Create(ctx, obj)).To(Succeed()) 187 defer func() { 188 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 189 }() 190 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 191 192 t.Log("Checking that the object has been created") 193 g.Eventually(func() error { 194 obj := obj.DeepCopy() 195 return env.Get(ctx, key, obj) 196 }).Should(Succeed()) 197 198 t.Log("Creating a new patch helper") 199 patcher, err := NewHelper(obj, env) 200 g.Expect(err).ToNot(HaveOccurred()) 201 202 t.Log("Marking Ready=True") 203 conditions.MarkTrue(obj, clusterv1.ReadyCondition) 204 205 t.Log("Patching the object") 206 g.Expect(patcher.Patch(ctx, obj)).To(Succeed()) 207 208 t.Log("Validating the object has been updated") 209 g.Eventually(func() clusterv1.Conditions { 210 objAfter := obj.DeepCopy() 211 if err := env.Get(ctx, key, objAfter); err != nil { 212 return clusterv1.Conditions{} 213 } 214 return objAfter.Status.Conditions 215 }, timeout).Should(conditions.MatchConditions(obj.Status.Conditions)) 216 }) 217 218 t.Run("should recover if there is a resolvable conflict", func(t *testing.T) { 219 g := NewWithT(t) 220 221 obj := obj.DeepCopy() 222 223 t.Log("Creating the object") 224 g.Expect(env.Create(ctx, obj)).To(Succeed()) 225 defer func() { 226 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 227 }() 228 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 229 230 t.Log("Checking that the object has been created") 231 g.Eventually(func() error { 232 obj := obj.DeepCopy() 233 return env.Get(ctx, key, obj) 234 }).Should(Succeed()) 235 236 objCopy := obj.DeepCopy() 237 238 t.Log("Marking a custom condition to be false") 239 conditions.MarkFalse(objCopy, clusterv1.ConditionType("TestCondition"), "reason", clusterv1.ConditionSeverityInfo, "message") 240 g.Expect(env.Status().Update(ctx, objCopy)).To(Succeed()) 241 242 t.Log("Validating that the local object's resource version is behind") 243 g.Expect(obj.ResourceVersion).NotTo(Equal(objCopy.ResourceVersion)) 244 245 t.Log("Creating a new patch helper") 246 patcher, err := NewHelper(obj, env) 247 g.Expect(err).ToNot(HaveOccurred()) 248 249 t.Log("Marking Ready=True") 250 conditions.MarkTrue(obj, clusterv1.ReadyCondition) 251 252 t.Log("Patching the object") 253 g.Expect(patcher.Patch(ctx, obj)).To(Succeed()) 254 255 t.Log("Validating the object has been updated") 256 g.Eventually(func() bool { 257 objAfter := obj.DeepCopy() 258 if err := env.Get(ctx, key, objAfter); err != nil { 259 return false 260 } 261 262 testConditionCopy := conditions.Get(objCopy, "TestCondition") 263 testConditionAfter := conditions.Get(objAfter, "TestCondition") 264 ok, err := conditions.MatchCondition(*testConditionCopy).Match(*testConditionAfter) 265 if err != nil || !ok { 266 return false 267 } 268 269 readyBefore := conditions.Get(obj, clusterv1.ReadyCondition) 270 readyAfter := conditions.Get(objAfter, clusterv1.ReadyCondition) 271 ok, err = conditions.MatchCondition(*readyBefore).Match(*readyAfter) 272 if err != nil || !ok { 273 return false 274 } 275 276 return true 277 }, timeout).Should(BeTrue()) 278 }) 279 280 t.Run("should recover if there is a resolvable conflict, incl. patch spec and status", func(t *testing.T) { 281 g := NewWithT(t) 282 283 obj := obj.DeepCopy() 284 285 t.Log("Creating the object") 286 g.Expect(env.Create(ctx, obj)).To(Succeed()) 287 defer func() { 288 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 289 }() 290 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 291 292 t.Log("Checking that the object has been created") 293 g.Eventually(func() error { 294 obj := obj.DeepCopy() 295 return env.Get(ctx, key, obj) 296 }).Should(Succeed()) 297 298 objCopy := obj.DeepCopy() 299 300 t.Log("Marking a custom condition to be false") 301 conditions.MarkFalse(objCopy, clusterv1.ConditionType("TestCondition"), "reason", clusterv1.ConditionSeverityInfo, "message") 302 g.Expect(env.Status().Update(ctx, objCopy)).To(Succeed()) 303 304 t.Log("Validating that the local object's resource version is behind") 305 g.Expect(obj.ResourceVersion).NotTo(Equal(objCopy.ResourceVersion)) 306 307 t.Log("Creating a new patch helper") 308 patcher, err := NewHelper(obj, env) 309 g.Expect(err).ToNot(HaveOccurred()) 310 311 t.Log("Changing the object spec, status, and adding Ready=True condition") 312 obj.Spec.Paused = true 313 obj.Spec.ControlPlaneEndpoint.Host = "test://endpoint" 314 obj.Spec.ControlPlaneEndpoint.Port = 8443 315 obj.Status.Phase = "custom-phase" 316 conditions.MarkTrue(obj, clusterv1.ReadyCondition) 317 318 t.Log("Patching the object") 319 g.Expect(patcher.Patch(ctx, obj)).To(Succeed()) 320 321 t.Log("Validating the object has been updated") 322 objAfter := obj.DeepCopy() 323 g.Eventually(func() bool { 324 if err := env.Get(ctx, key, objAfter); err != nil { 325 return false 326 } 327 328 testConditionCopy := conditions.Get(objCopy, "TestCondition") 329 testConditionAfter := conditions.Get(objAfter, "TestCondition") 330 ok, err := conditions.MatchCondition(*testConditionCopy).Match(*testConditionAfter) 331 if err != nil || !ok { 332 return false 333 } 334 335 readyBefore := conditions.Get(obj, clusterv1.ReadyCondition) 336 readyAfter := conditions.Get(objAfter, clusterv1.ReadyCondition) 337 ok, err = conditions.MatchCondition(*readyBefore).Match(*readyAfter) 338 if err != nil || !ok { 339 return false 340 } 341 342 return obj.Spec.Paused == objAfter.Spec.Paused && 343 obj.Spec.ControlPlaneEndpoint == objAfter.Spec.ControlPlaneEndpoint && 344 obj.Status.Phase == objAfter.Status.Phase 345 }, timeout).Should(BeTrue(), cmp.Diff(obj, objAfter)) 346 }) 347 348 t.Run("should return an error if there is an unresolvable conflict", func(t *testing.T) { 349 g := NewWithT(t) 350 351 obj := obj.DeepCopy() 352 353 t.Log("Creating the object") 354 g.Expect(env.Create(ctx, obj)).To(Succeed()) 355 defer func() { 356 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 357 }() 358 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 359 360 t.Log("Checking that the object has been created") 361 g.Eventually(func() error { 362 obj := obj.DeepCopy() 363 return env.Get(ctx, key, obj) 364 }).Should(Succeed()) 365 366 objCopy := obj.DeepCopy() 367 368 t.Log("Marking a custom condition to be false") 369 conditions.MarkFalse(objCopy, clusterv1.ReadyCondition, "reason", clusterv1.ConditionSeverityInfo, "message") 370 g.Expect(env.Status().Update(ctx, objCopy)).To(Succeed()) 371 372 t.Log("Validating that the local object's resource version is behind") 373 g.Expect(obj.ResourceVersion).NotTo(Equal(objCopy.ResourceVersion)) 374 375 t.Log("Creating a new patch helper") 376 patcher, err := NewHelper(obj, env) 377 g.Expect(err).ToNot(HaveOccurred()) 378 379 t.Log("Marking Ready=True") 380 conditions.MarkTrue(obj, clusterv1.ReadyCondition) 381 382 t.Log("Patching the object") 383 g.Expect(patcher.Patch(ctx, obj)).NotTo(Succeed()) 384 385 t.Log("Validating the object has not been updated") 386 g.Eventually(func() bool { 387 objAfter := obj.DeepCopy() 388 if err := env.Get(ctx, key, objAfter); err != nil { 389 return false 390 } 391 392 for _, afterCondition := range objAfter.Status.Conditions { 393 ok, err := conditions.MatchCondition(objCopy.Status.Conditions[0]).Match(afterCondition) 394 if err == nil && ok { 395 return true 396 } 397 } 398 399 return false 400 }, timeout).Should(BeTrue()) 401 }) 402 403 t.Run("should not return an error if there is an unresolvable conflict but the conditions is owned by the controller", func(t *testing.T) { 404 g := NewWithT(t) 405 406 obj := obj.DeepCopy() 407 408 t.Log("Creating the object") 409 g.Expect(env.Create(ctx, obj)).To(Succeed()) 410 defer func() { 411 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 412 }() 413 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 414 415 t.Log("Checking that the object has been created") 416 g.Eventually(func() error { 417 obj := obj.DeepCopy() 418 return env.Get(ctx, key, obj) 419 }).Should(Succeed()) 420 421 objCopy := obj.DeepCopy() 422 423 t.Log("Marking a custom condition to be false") 424 conditions.MarkFalse(objCopy, clusterv1.ReadyCondition, "reason", clusterv1.ConditionSeverityInfo, "message") 425 g.Expect(env.Status().Update(ctx, objCopy)).To(Succeed()) 426 427 t.Log("Validating that the local object's resource version is behind") 428 g.Expect(obj.ResourceVersion).NotTo(Equal(objCopy.ResourceVersion)) 429 430 t.Log("Creating a new patch helper") 431 patcher, err := NewHelper(obj, env) 432 g.Expect(err).ToNot(HaveOccurred()) 433 434 t.Log("Marking Ready=True") 435 conditions.MarkTrue(obj, clusterv1.ReadyCondition) 436 437 t.Log("Patching the object") 438 g.Expect(patcher.Patch(ctx, obj, WithOwnedConditions{Conditions: []clusterv1.ConditionType{clusterv1.ReadyCondition}})).To(Succeed()) 439 440 t.Log("Validating the object has been updated") 441 readyBefore := conditions.Get(obj, clusterv1.ReadyCondition) 442 g.Eventually(func() clusterv1.Condition { 443 objAfter := obj.DeepCopy() 444 if err := env.Get(ctx, key, objAfter); err != nil { 445 return clusterv1.Condition{} 446 } 447 448 return *conditions.Get(objAfter, clusterv1.ReadyCondition) 449 }, timeout).Should(conditions.MatchCondition(*readyBefore)) 450 }) 451 452 t.Run("should not return an error if there is an unresolvable conflict when force overwrite is enabled", func(t *testing.T) { 453 g := NewWithT(t) 454 455 obj := obj.DeepCopy() 456 457 t.Log("Creating the object") 458 g.Expect(env.Create(ctx, obj)).To(Succeed()) 459 defer func() { 460 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 461 }() 462 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 463 464 t.Log("Checking that the object has been created") 465 g.Eventually(func() error { 466 obj := obj.DeepCopy() 467 return env.Get(ctx, key, obj) 468 }).Should(Succeed()) 469 470 objCopy := obj.DeepCopy() 471 472 t.Log("Marking a custom condition to be false") 473 conditions.MarkFalse(objCopy, clusterv1.ReadyCondition, "reason", clusterv1.ConditionSeverityInfo, "message") 474 g.Expect(env.Status().Update(ctx, objCopy)).To(Succeed()) 475 476 t.Log("Validating that the local object's resource version is behind") 477 g.Expect(obj.ResourceVersion).NotTo(Equal(objCopy.ResourceVersion)) 478 479 t.Log("Creating a new patch helper") 480 patcher, err := NewHelper(obj, env) 481 g.Expect(err).ToNot(HaveOccurred()) 482 483 t.Log("Marking Ready=True") 484 conditions.MarkTrue(obj, clusterv1.ReadyCondition) 485 486 t.Log("Patching the object") 487 g.Expect(patcher.Patch(ctx, obj, WithForceOverwriteConditions{})).To(Succeed()) 488 489 t.Log("Validating the object has been updated") 490 readyBefore := conditions.Get(obj, clusterv1.ReadyCondition) 491 g.Eventually(func() clusterv1.Condition { 492 objAfter := obj.DeepCopy() 493 if err := env.Get(ctx, key, objAfter); err != nil { 494 return clusterv1.Condition{} 495 } 496 497 return *conditions.Get(objAfter, clusterv1.ReadyCondition) 498 }, timeout).Should(conditions.MatchCondition(*readyBefore)) 499 }) 500 }) 501 }) 502 503 t.Run("Should patch a clusterv1.Cluster", func(t *testing.T) { 504 obj := &clusterv1.Cluster{ 505 ObjectMeta: metav1.ObjectMeta{ 506 GenerateName: "test-", 507 Namespace: ns.Name, 508 }, 509 } 510 511 t.Run("add a finalizer", func(t *testing.T) { 512 g := NewWithT(t) 513 514 obj := obj.DeepCopy() 515 516 t.Log("Creating the object") 517 g.Expect(env.Create(ctx, obj)).To(Succeed()) 518 defer func() { 519 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 520 }() 521 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 522 523 t.Log("Checking that the object has been created") 524 g.Eventually(func() error { 525 obj := obj.DeepCopy() 526 return env.Get(ctx, key, obj) 527 }).Should(Succeed()) 528 529 t.Log("Creating a new patch helper") 530 patcher, err := NewHelper(obj, env) 531 g.Expect(err).ToNot(HaveOccurred()) 532 533 t.Log("Adding a finalizer") 534 obj.Finalizers = append(obj.Finalizers, clusterv1.ClusterFinalizer) 535 536 t.Log("Patching the object") 537 g.Expect(patcher.Patch(ctx, obj)).To(Succeed()) 538 539 t.Log("Validating the object has been updated") 540 g.Eventually(func() bool { 541 objAfter := obj.DeepCopy() 542 if err := env.Get(ctx, key, objAfter); err != nil { 543 return false 544 } 545 546 return reflect.DeepEqual(obj.Finalizers, objAfter.Finalizers) 547 }, timeout).Should(BeTrue()) 548 }) 549 550 t.Run("removing finalizers", func(t *testing.T) { 551 g := NewWithT(t) 552 553 obj := obj.DeepCopy() 554 obj.Finalizers = append(obj.Finalizers, clusterv1.ClusterFinalizer) 555 556 t.Log("Creating the object") 557 g.Expect(env.Create(ctx, obj)).To(Succeed()) 558 defer func() { 559 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 560 }() 561 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 562 563 t.Log("Checking that the object has been created") 564 g.Eventually(func() error { 565 obj := obj.DeepCopy() 566 return env.Get(ctx, key, obj) 567 }).Should(Succeed()) 568 569 t.Log("Creating a new patch helper") 570 patcher, err := NewHelper(obj, env) 571 g.Expect(err).ToNot(HaveOccurred()) 572 573 t.Log("Removing the finalizers") 574 obj.SetFinalizers(nil) 575 576 t.Log("Patching the object") 577 g.Expect(patcher.Patch(ctx, obj)).To(Succeed()) 578 579 t.Log("Validating the object has been updated") 580 g.Eventually(func() bool { 581 objAfter := obj.DeepCopy() 582 if err := env.Get(ctx, key, objAfter); err != nil { 583 return false 584 } 585 586 return len(objAfter.Finalizers) == 0 587 }, timeout).Should(BeTrue()) 588 }) 589 590 t.Run("updating spec", func(t *testing.T) { 591 g := NewWithT(t) 592 593 obj := obj.DeepCopy() 594 595 t.Log("Creating the object") 596 g.Expect(env.Create(ctx, obj)).To(Succeed()) 597 defer func() { 598 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 599 }() 600 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 601 602 t.Log("Checking that the object has been created") 603 g.Eventually(func() error { 604 obj := obj.DeepCopy() 605 return env.Get(ctx, key, obj) 606 }).Should(Succeed()) 607 608 t.Log("Creating a new patch helper") 609 patcher, err := NewHelper(obj, env) 610 g.Expect(err).ToNot(HaveOccurred()) 611 612 t.Log("Updating the object spec") 613 obj.Spec.Paused = true 614 obj.Spec.InfrastructureRef = &corev1.ObjectReference{ 615 Kind: "test-kind", 616 Name: "test-ref", 617 Namespace: ns.Name, 618 } 619 620 t.Log("Patching the object") 621 g.Expect(patcher.Patch(ctx, obj)).To(Succeed()) 622 623 t.Log("Validating the object has been updated") 624 g.Eventually(func() bool { 625 objAfter := obj.DeepCopy() 626 if err := env.Get(ctx, key, objAfter); err != nil { 627 return false 628 } 629 630 return objAfter.Spec.Paused && 631 reflect.DeepEqual(obj.Spec.InfrastructureRef, objAfter.Spec.InfrastructureRef) 632 }, timeout).Should(BeTrue()) 633 }) 634 635 t.Run("updating status", func(t *testing.T) { 636 g := NewWithT(t) 637 638 obj := obj.DeepCopy() 639 640 t.Log("Creating the object") 641 g.Expect(env.Create(ctx, obj)).To(Succeed()) 642 defer func() { 643 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 644 }() 645 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 646 647 t.Log("Checking that the object has been created") 648 g.Eventually(func() error { 649 obj := obj.DeepCopy() 650 return env.Get(ctx, key, obj) 651 }).Should(Succeed()) 652 653 t.Log("Creating a new patch helper") 654 patcher, err := NewHelper(obj, env) 655 g.Expect(err).ToNot(HaveOccurred()) 656 657 t.Log("Updating the object status") 658 obj.Status.InfrastructureReady = true 659 660 t.Log("Patching the object") 661 g.Expect(patcher.Patch(ctx, obj)).To(Succeed()) 662 663 t.Log("Validating the object has been updated") 664 g.Eventually(func() bool { 665 objAfter := obj.DeepCopy() 666 if err := env.Get(ctx, key, objAfter); err != nil { 667 return false 668 } 669 return reflect.DeepEqual(objAfter.Status, obj.Status) 670 }, timeout).Should(BeTrue()) 671 }) 672 673 t.Run("updating both spec, status, and adding a condition", func(t *testing.T) { 674 g := NewWithT(t) 675 676 obj := obj.DeepCopy() 677 678 t.Log("Creating the object") 679 g.Expect(env.Create(ctx, obj)).To(Succeed()) 680 defer func() { 681 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 682 }() 683 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 684 685 t.Log("Checking that the object has been created") 686 g.Eventually(func() error { 687 obj := obj.DeepCopy() 688 return env.Get(ctx, key, obj) 689 }).Should(Succeed()) 690 691 t.Log("Creating a new patch helper") 692 patcher, err := NewHelper(obj, env) 693 g.Expect(err).ToNot(HaveOccurred()) 694 695 t.Log("Updating the object spec") 696 obj.Spec.Paused = true 697 obj.Spec.InfrastructureRef = &corev1.ObjectReference{ 698 Kind: "test-kind", 699 Name: "test-ref", 700 Namespace: ns.Name, 701 } 702 703 t.Log("Updating the object status") 704 obj.Status.InfrastructureReady = true 705 706 t.Log("Setting Ready condition") 707 conditions.MarkTrue(obj, clusterv1.ReadyCondition) 708 709 t.Log("Patching the object") 710 g.Expect(patcher.Patch(ctx, obj)).To(Succeed()) 711 712 t.Log("Validating the object has been updated") 713 g.Eventually(func() bool { 714 objAfter := obj.DeepCopy() 715 if err := env.Get(ctx, key, objAfter); err != nil { 716 return false 717 } 718 719 return obj.Status.InfrastructureReady == objAfter.Status.InfrastructureReady && 720 conditions.IsTrue(objAfter, clusterv1.ReadyCondition) && 721 reflect.DeepEqual(obj.Spec, objAfter.Spec) 722 }, timeout).Should(BeTrue()) 723 }) 724 }) 725 726 t.Run("Should update Status.ObservedGeneration when using WithStatusObservedGeneration option", func(t *testing.T) { 727 obj := &clusterv1.MachineSet{ 728 ObjectMeta: metav1.ObjectMeta{ 729 GenerateName: "test-ms", 730 Namespace: ns.Name, 731 }, 732 Spec: clusterv1.MachineSetSpec{ 733 ClusterName: "test1", 734 Template: clusterv1.MachineTemplateSpec{ 735 Spec: clusterv1.MachineSpec{ 736 ClusterName: "test1", 737 }, 738 }, 739 }, 740 } 741 742 t.Run("when updating spec", func(t *testing.T) { 743 g := NewWithT(t) 744 745 obj := obj.DeepCopy() 746 747 t.Log("Creating the MachineSet object") 748 g.Expect(env.Create(ctx, obj)).To(Succeed()) 749 defer func() { 750 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 751 }() 752 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 753 754 t.Log("Checking that the object has been created") 755 g.Eventually(func() error { 756 obj := obj.DeepCopy() 757 return env.Get(ctx, key, obj) 758 }).Should(Succeed()) 759 760 t.Log("Creating a new patch helper") 761 patcher, err := NewHelper(obj, env) 762 g.Expect(err).ToNot(HaveOccurred()) 763 764 t.Log("Updating the object spec") 765 obj.Spec.Replicas = ptr.To[int32](10) 766 767 t.Log("Patching the object") 768 g.Expect(patcher.Patch(ctx, obj, WithStatusObservedGeneration{})).To(Succeed()) 769 770 t.Log("Validating the object has been updated") 771 g.Eventually(func() bool { 772 objAfter := obj.DeepCopy() 773 if err := env.Get(ctx, key, objAfter); err != nil { 774 return false 775 } 776 777 return reflect.DeepEqual(obj.Spec, objAfter.Spec) && 778 obj.GetGeneration() == objAfter.Status.ObservedGeneration 779 }, timeout).Should(BeTrue()) 780 }) 781 782 t.Run("when updating spec, status, and metadata", func(t *testing.T) { 783 g := NewWithT(t) 784 785 obj := obj.DeepCopy() 786 787 t.Log("Creating the MachineSet object") 788 g.Expect(env.Create(ctx, obj)).To(Succeed()) 789 defer func() { 790 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 791 }() 792 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 793 794 t.Log("Checking that the object has been created") 795 g.Eventually(func() error { 796 obj := obj.DeepCopy() 797 return env.Get(ctx, key, obj) 798 }).Should(Succeed()) 799 800 t.Log("Creating a new patch helper") 801 patcher, err := NewHelper(obj, env) 802 g.Expect(err).ToNot(HaveOccurred()) 803 804 t.Log("Updating the object spec") 805 obj.Spec.Replicas = ptr.To[int32](10) 806 807 t.Log("Updating the object status") 808 obj.Status.AvailableReplicas = 6 809 obj.Status.ReadyReplicas = 6 810 811 t.Log("Updating the object metadata") 812 obj.ObjectMeta.Annotations = map[string]string{ 813 "test1": "annotation", 814 } 815 816 t.Log("Patching the object") 817 g.Expect(patcher.Patch(ctx, obj, WithStatusObservedGeneration{})).To(Succeed()) 818 819 t.Log("Validating the object has been updated") 820 g.Eventually(func() bool { 821 objAfter := obj.DeepCopy() 822 if err := env.Get(ctx, key, objAfter); err != nil { 823 return false 824 } 825 826 return reflect.DeepEqual(obj.Spec, objAfter.Spec) && 827 reflect.DeepEqual(obj.Status, objAfter.Status) && 828 obj.GetGeneration() == objAfter.Status.ObservedGeneration 829 }, timeout).Should(BeTrue()) 830 }) 831 832 t.Run("without any changes", func(t *testing.T) { 833 g := NewWithT(t) 834 835 obj := obj.DeepCopy() 836 837 t.Log("Creating the MachineSet object") 838 g.Expect(env.Create(ctx, obj)).To(Succeed()) 839 defer func() { 840 g.Expect(env.Delete(ctx, obj)).To(Succeed()) 841 }() 842 key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} 843 844 t.Log("Checking that the object has been created") 845 g.Eventually(func() error { 846 obj := obj.DeepCopy() 847 return env.Get(ctx, key, obj) 848 }).Should(Succeed()) 849 850 obj.Status.ObservedGeneration = obj.GetGeneration() 851 lastGeneration := obj.GetGeneration() 852 g.Expect(env.Status().Update(ctx, obj)).To(Succeed()) 853 854 t.Log("Creating a new patch helper") 855 patcher, err := NewHelper(obj, env) 856 g.Expect(err).ToNot(HaveOccurred()) 857 858 t.Log("Patching the object") 859 g.Expect(patcher.Patch(ctx, obj, WithStatusObservedGeneration{})).To(Succeed()) 860 861 t.Log("Validating the object has been updated") 862 g.Eventually(func() bool { 863 objAfter := obj.DeepCopy() 864 if err := env.Get(ctx, key, objAfter); err != nil { 865 return false 866 } 867 return lastGeneration == objAfter.Status.ObservedGeneration 868 }, timeout).Should(BeTrue()) 869 }) 870 }) 871 872 t.Run("Should error if the object isn't the same", func(t *testing.T) { 873 g := NewWithT(t) 874 875 cluster := &clusterv1.Cluster{ 876 ObjectMeta: metav1.ObjectMeta{ 877 GenerateName: "test-", 878 Namespace: ns.Name, 879 }, 880 } 881 882 machineSet := &clusterv1.MachineSet{ 883 ObjectMeta: metav1.ObjectMeta{ 884 GenerateName: "test-ms", 885 Namespace: ns.Name, 886 }, 887 Spec: clusterv1.MachineSetSpec{ 888 ClusterName: "test1", 889 Template: clusterv1.MachineTemplateSpec{ 890 Spec: clusterv1.MachineSpec{ 891 ClusterName: "test1", 892 }, 893 }, 894 }, 895 } 896 897 g.Expect(env.Create(ctx, cluster)).To(Succeed()) 898 defer func() { 899 g.Expect(env.Delete(ctx, cluster)).To(Succeed()) 900 }() 901 g.Expect(env.Create(ctx, machineSet)).To(Succeed()) 902 defer func() { 903 g.Expect(env.Delete(ctx, machineSet)).To(Succeed()) 904 }() 905 906 patcher, err := NewHelper(cluster, env) 907 g.Expect(err).ToNot(HaveOccurred()) 908 909 g.Expect(patcher.Patch(ctx, machineSet)).NotTo(Succeed()) 910 }) 911 } 912 913 func TestNewHelperNil(t *testing.T) { 914 var x *appsv1.Deployment 915 g := NewWithT(t) 916 _, err := NewHelper(x, nil) 917 g.Expect(err).To(HaveOccurred()) 918 _, err = NewHelper(nil, nil) 919 g.Expect(err).To(HaveOccurred()) 920 }