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  }