sigs.k8s.io/cluster-api@v1.7.1/controlplane/kubeadm/internal/filters_test.go (about)

     1  /*
     2  Copyright 2020 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 internal
    18  
    19  import (
    20  	"testing"
    21  
    22  	. "github.com/onsi/gomega"
    23  	corev1 "k8s.io/api/core/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    26  
    27  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    28  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    29  	controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
    30  )
    31  
    32  func TestMatchClusterConfiguration(t *testing.T) {
    33  	t.Run("machine without the ClusterConfiguration annotation should match (not enough information to make a decision)", func(t *testing.T) {
    34  		g := NewWithT(t)
    35  		kcp := &controlplanev1.KubeadmControlPlane{}
    36  		m := &clusterv1.Machine{}
    37  		g.Expect(matchClusterConfiguration(kcp, m)).To(BeTrue())
    38  	})
    39  	t.Run("machine without an invalid ClusterConfiguration annotation should not match (only solution is to rollout)", func(t *testing.T) {
    40  		g := NewWithT(t)
    41  		kcp := &controlplanev1.KubeadmControlPlane{}
    42  		m := &clusterv1.Machine{
    43  			ObjectMeta: metav1.ObjectMeta{
    44  				Annotations: map[string]string{
    45  					controlplanev1.KubeadmClusterConfigurationAnnotation: "$|^^_",
    46  				},
    47  			},
    48  		}
    49  		g.Expect(matchClusterConfiguration(kcp, m)).To(BeFalse())
    50  	})
    51  	t.Run("Return true if cluster configuration matches", func(t *testing.T) {
    52  		g := NewWithT(t)
    53  		kcp := &controlplanev1.KubeadmControlPlane{
    54  			Spec: controlplanev1.KubeadmControlPlaneSpec{
    55  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
    56  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
    57  						ClusterName: "foo",
    58  					},
    59  				},
    60  			},
    61  		}
    62  		m := &clusterv1.Machine{
    63  			ObjectMeta: metav1.ObjectMeta{
    64  				Annotations: map[string]string{
    65  					controlplanev1.KubeadmClusterConfigurationAnnotation: "{\n  \"clusterName\": \"foo\"\n}",
    66  				},
    67  			},
    68  		}
    69  		g.Expect(matchClusterConfiguration(kcp, m)).To(BeTrue())
    70  	})
    71  	t.Run("Return false if cluster configuration does not match", func(t *testing.T) {
    72  		g := NewWithT(t)
    73  		kcp := &controlplanev1.KubeadmControlPlane{
    74  			Spec: controlplanev1.KubeadmControlPlaneSpec{
    75  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
    76  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
    77  						ClusterName: "foo",
    78  					},
    79  				},
    80  			},
    81  		}
    82  		m := &clusterv1.Machine{
    83  			ObjectMeta: metav1.ObjectMeta{
    84  				Annotations: map[string]string{
    85  					controlplanev1.KubeadmClusterConfigurationAnnotation: "{\n  \"clusterName\": \"bar\"\n}",
    86  				},
    87  			},
    88  		}
    89  		g.Expect(matchClusterConfiguration(kcp, m)).To(BeFalse())
    90  	})
    91  	t.Run("Return true if cluster configuration is nil (special case)", func(t *testing.T) {
    92  		g := NewWithT(t)
    93  		kcp := &controlplanev1.KubeadmControlPlane{
    94  			Spec: controlplanev1.KubeadmControlPlaneSpec{
    95  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{},
    96  			},
    97  		}
    98  		m := &clusterv1.Machine{
    99  			ObjectMeta: metav1.ObjectMeta{
   100  				Annotations: map[string]string{
   101  					controlplanev1.KubeadmClusterConfigurationAnnotation: "null",
   102  				},
   103  			},
   104  		}
   105  		g.Expect(matchClusterConfiguration(kcp, m)).To(BeTrue())
   106  	})
   107  	t.Run("Return true although the DNS fields are different", func(t *testing.T) {
   108  		g := NewWithT(t)
   109  		kcp := &controlplanev1.KubeadmControlPlane{
   110  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   111  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   112  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   113  						DNS: bootstrapv1.DNS{
   114  							ImageMeta: bootstrapv1.ImageMeta{
   115  								ImageTag:        "v1.10.1",
   116  								ImageRepository: "gcr.io/capi-test",
   117  							},
   118  						},
   119  					},
   120  				},
   121  			},
   122  		}
   123  		m := &clusterv1.Machine{
   124  			ObjectMeta: metav1.ObjectMeta{
   125  				Annotations: map[string]string{
   126  					controlplanev1.KubeadmClusterConfigurationAnnotation: "{\"dns\":{\"imageRepository\":\"gcr.io/capi-test\",\"imageTag\":\"v1.9.3\"}}",
   127  				},
   128  			},
   129  		}
   130  		g.Expect(matchClusterConfiguration(kcp, m)).To(BeTrue())
   131  	})
   132  }
   133  
   134  func TestGetAdjustedKcpConfig(t *testing.T) {
   135  	t.Run("if the machine is the first control plane, kcp config should get InitConfiguration", func(t *testing.T) {
   136  		g := NewWithT(t)
   137  		kcp := &controlplanev1.KubeadmControlPlane{
   138  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   139  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   140  					InitConfiguration: &bootstrapv1.InitConfiguration{},
   141  					JoinConfiguration: &bootstrapv1.JoinConfiguration{},
   142  				},
   143  			},
   144  		}
   145  		machineConfig := &bootstrapv1.KubeadmConfig{
   146  			Spec: bootstrapv1.KubeadmConfigSpec{
   147  				InitConfiguration: &bootstrapv1.InitConfiguration{}, // first control-plane
   148  			},
   149  		}
   150  		kcpConfig := getAdjustedKcpConfig(kcp, machineConfig)
   151  		g.Expect(kcpConfig.InitConfiguration).ToNot(BeNil())
   152  		g.Expect(kcpConfig.JoinConfiguration).To(BeNil())
   153  	})
   154  	t.Run("if the machine is a joining control plane, kcp config should get JoinConfiguration", func(t *testing.T) {
   155  		g := NewWithT(t)
   156  		kcp := &controlplanev1.KubeadmControlPlane{
   157  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   158  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   159  					InitConfiguration: &bootstrapv1.InitConfiguration{},
   160  					JoinConfiguration: &bootstrapv1.JoinConfiguration{},
   161  				},
   162  			},
   163  		}
   164  		machineConfig := &bootstrapv1.KubeadmConfig{
   165  			Spec: bootstrapv1.KubeadmConfigSpec{
   166  				JoinConfiguration: &bootstrapv1.JoinConfiguration{}, // joining control-plane
   167  			},
   168  		}
   169  		kcpConfig := getAdjustedKcpConfig(kcp, machineConfig)
   170  		g.Expect(kcpConfig.InitConfiguration).To(BeNil())
   171  		g.Expect(kcpConfig.JoinConfiguration).ToNot(BeNil())
   172  	})
   173  }
   174  
   175  func TestCleanupConfigFields(t *testing.T) {
   176  	t.Run("ClusterConfiguration gets removed from KcpConfig and MachineConfig", func(t *testing.T) {
   177  		g := NewWithT(t)
   178  		kcpConfig := &bootstrapv1.KubeadmConfigSpec{
   179  			ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   180  		}
   181  		machineConfig := &bootstrapv1.KubeadmConfig{
   182  			Spec: bootstrapv1.KubeadmConfigSpec{
   183  				ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   184  			},
   185  		}
   186  		cleanupConfigFields(kcpConfig, machineConfig)
   187  		g.Expect(kcpConfig.ClusterConfiguration).To(BeNil())
   188  		g.Expect(machineConfig.Spec.ClusterConfiguration).To(BeNil())
   189  	})
   190  	t.Run("JoinConfiguration gets removed from MachineConfig if it was not derived by KCPConfig", func(t *testing.T) {
   191  		g := NewWithT(t)
   192  		kcpConfig := &bootstrapv1.KubeadmConfigSpec{
   193  			JoinConfiguration: nil, // KCP not providing a JoinConfiguration
   194  		}
   195  		machineConfig := &bootstrapv1.KubeadmConfig{
   196  			Spec: bootstrapv1.KubeadmConfigSpec{
   197  				JoinConfiguration: &bootstrapv1.JoinConfiguration{}, // Machine gets a default JoinConfiguration from CABPK
   198  			},
   199  		}
   200  		cleanupConfigFields(kcpConfig, machineConfig)
   201  		g.Expect(kcpConfig.JoinConfiguration).To(BeNil())
   202  		g.Expect(machineConfig.Spec.JoinConfiguration).To(BeNil())
   203  	})
   204  	t.Run("JoinConfiguration.Discovery gets removed because it is not relevant for compare", func(t *testing.T) {
   205  		g := NewWithT(t)
   206  		kcpConfig := &bootstrapv1.KubeadmConfigSpec{
   207  			JoinConfiguration: &bootstrapv1.JoinConfiguration{
   208  				Discovery: bootstrapv1.Discovery{TLSBootstrapToken: "aaa"},
   209  			},
   210  		}
   211  		machineConfig := &bootstrapv1.KubeadmConfig{
   212  			Spec: bootstrapv1.KubeadmConfigSpec{
   213  				JoinConfiguration: &bootstrapv1.JoinConfiguration{
   214  					Discovery: bootstrapv1.Discovery{TLSBootstrapToken: "aaa"},
   215  				},
   216  			},
   217  		}
   218  		cleanupConfigFields(kcpConfig, machineConfig)
   219  		g.Expect(kcpConfig.JoinConfiguration.Discovery).To(BeComparableTo(bootstrapv1.Discovery{}))
   220  		g.Expect(machineConfig.Spec.JoinConfiguration.Discovery).To(BeComparableTo(bootstrapv1.Discovery{}))
   221  	})
   222  	t.Run("JoinConfiguration.ControlPlane gets removed from MachineConfig if it was not derived by KCPConfig", func(t *testing.T) {
   223  		g := NewWithT(t)
   224  		kcpConfig := &bootstrapv1.KubeadmConfigSpec{
   225  			JoinConfiguration: &bootstrapv1.JoinConfiguration{
   226  				ControlPlane: nil, // Control plane configuration missing in KCP
   227  			},
   228  		}
   229  		machineConfig := &bootstrapv1.KubeadmConfig{
   230  			Spec: bootstrapv1.KubeadmConfigSpec{
   231  				JoinConfiguration: &bootstrapv1.JoinConfiguration{
   232  					ControlPlane: &bootstrapv1.JoinControlPlane{}, // Machine gets a default JoinConfiguration.ControlPlane from CABPK
   233  				},
   234  			},
   235  		}
   236  		cleanupConfigFields(kcpConfig, machineConfig)
   237  		g.Expect(kcpConfig.JoinConfiguration).ToNot(BeNil())
   238  		g.Expect(machineConfig.Spec.JoinConfiguration.ControlPlane).To(BeNil())
   239  	})
   240  	t.Run("JoinConfiguration.NodeRegistrationOptions gets removed from MachineConfig if it was not derived by KCPConfig", func(t *testing.T) {
   241  		g := NewWithT(t)
   242  		kcpConfig := &bootstrapv1.KubeadmConfigSpec{
   243  			JoinConfiguration: &bootstrapv1.JoinConfiguration{
   244  				NodeRegistration: bootstrapv1.NodeRegistrationOptions{}, // NodeRegistrationOptions configuration missing in KCP
   245  			},
   246  		}
   247  		machineConfig := &bootstrapv1.KubeadmConfig{
   248  			Spec: bootstrapv1.KubeadmConfigSpec{
   249  				JoinConfiguration: &bootstrapv1.JoinConfiguration{
   250  					NodeRegistration: bootstrapv1.NodeRegistrationOptions{Name: "test"}, // Machine gets a some JoinConfiguration.NodeRegistrationOptions
   251  				},
   252  			},
   253  		}
   254  		cleanupConfigFields(kcpConfig, machineConfig)
   255  		g.Expect(kcpConfig.JoinConfiguration).ToNot(BeNil())
   256  		g.Expect(machineConfig.Spec.JoinConfiguration.NodeRegistration).To(BeComparableTo(bootstrapv1.NodeRegistrationOptions{}))
   257  	})
   258  	t.Run("InitConfiguration.TypeMeta gets removed from MachineConfig", func(t *testing.T) {
   259  		g := NewWithT(t)
   260  		kcpConfig := &bootstrapv1.KubeadmConfigSpec{
   261  			InitConfiguration: &bootstrapv1.InitConfiguration{},
   262  		}
   263  		machineConfig := &bootstrapv1.KubeadmConfig{
   264  			Spec: bootstrapv1.KubeadmConfigSpec{
   265  				InitConfiguration: &bootstrapv1.InitConfiguration{
   266  					TypeMeta: metav1.TypeMeta{
   267  						Kind:       "JoinConfiguration",
   268  						APIVersion: bootstrapv1.GroupVersion.String(),
   269  					},
   270  				},
   271  			},
   272  		}
   273  		cleanupConfigFields(kcpConfig, machineConfig)
   274  		g.Expect(kcpConfig.InitConfiguration).ToNot(BeNil())
   275  		g.Expect(machineConfig.Spec.InitConfiguration.TypeMeta).To(BeComparableTo(metav1.TypeMeta{}))
   276  	})
   277  	t.Run("JoinConfiguration.TypeMeta gets removed from MachineConfig", func(t *testing.T) {
   278  		g := NewWithT(t)
   279  		kcpConfig := &bootstrapv1.KubeadmConfigSpec{
   280  			JoinConfiguration: &bootstrapv1.JoinConfiguration{},
   281  		}
   282  		machineConfig := &bootstrapv1.KubeadmConfig{
   283  			Spec: bootstrapv1.KubeadmConfigSpec{
   284  				JoinConfiguration: &bootstrapv1.JoinConfiguration{
   285  					TypeMeta: metav1.TypeMeta{
   286  						Kind:       "JoinConfiguration",
   287  						APIVersion: bootstrapv1.GroupVersion.String(),
   288  					},
   289  				},
   290  			},
   291  		}
   292  		cleanupConfigFields(kcpConfig, machineConfig)
   293  		g.Expect(kcpConfig.JoinConfiguration).ToNot(BeNil())
   294  		g.Expect(machineConfig.Spec.JoinConfiguration.TypeMeta).To(BeComparableTo(metav1.TypeMeta{}))
   295  	})
   296  }
   297  
   298  func TestMatchInitOrJoinConfiguration(t *testing.T) {
   299  	t.Run("returns true if the machine does not have a bootstrap config", func(t *testing.T) {
   300  		g := NewWithT(t)
   301  		kcp := &controlplanev1.KubeadmControlPlane{}
   302  		g.Expect(matchInitOrJoinConfiguration(nil, kcp)).To(BeTrue())
   303  	})
   304  	t.Run("returns true if the there are problems reading the bootstrap config", func(t *testing.T) {
   305  		g := NewWithT(t)
   306  		kcp := &controlplanev1.KubeadmControlPlane{}
   307  		g.Expect(matchInitOrJoinConfiguration(nil, kcp)).To(BeTrue())
   308  	})
   309  	t.Run("returns true if one format is empty and the other one cloud-config", func(t *testing.T) {
   310  		g := NewWithT(t)
   311  		kcp := &controlplanev1.KubeadmControlPlane{
   312  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   313  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   314  					Format: bootstrapv1.CloudConfig,
   315  				},
   316  			},
   317  		}
   318  		m := &clusterv1.Machine{
   319  			TypeMeta: metav1.TypeMeta{
   320  				Kind:       "KubeadmConfig",
   321  				APIVersion: clusterv1.GroupVersion.String(),
   322  			},
   323  			ObjectMeta: metav1.ObjectMeta{
   324  				Namespace: "default",
   325  				Name:      "test",
   326  			},
   327  			Spec: clusterv1.MachineSpec{
   328  				Bootstrap: clusterv1.Bootstrap{
   329  					ConfigRef: &corev1.ObjectReference{
   330  						Kind:       "KubeadmConfig",
   331  						Namespace:  "default",
   332  						Name:       "test",
   333  						APIVersion: bootstrapv1.GroupVersion.String(),
   334  					},
   335  				},
   336  			},
   337  		}
   338  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   339  			m.Name: {
   340  				TypeMeta: metav1.TypeMeta{
   341  					Kind:       "KubeadmConfig",
   342  					APIVersion: bootstrapv1.GroupVersion.String(),
   343  				},
   344  				ObjectMeta: metav1.ObjectMeta{
   345  					Namespace: "default",
   346  					Name:      "test",
   347  				},
   348  				Spec: bootstrapv1.KubeadmConfigSpec{
   349  					Format: "",
   350  				},
   351  			},
   352  		}
   353  		g.Expect(matchInitOrJoinConfiguration(machineConfigs[m.Name], kcp)).To(BeTrue())
   354  	})
   355  	t.Run("returns true if InitConfiguration is equal", func(t *testing.T) {
   356  		g := NewWithT(t)
   357  		kcp := &controlplanev1.KubeadmControlPlane{
   358  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   359  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   360  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   361  					InitConfiguration:    &bootstrapv1.InitConfiguration{},
   362  					JoinConfiguration:    &bootstrapv1.JoinConfiguration{},
   363  				},
   364  			},
   365  		}
   366  		m := &clusterv1.Machine{
   367  			TypeMeta: metav1.TypeMeta{
   368  				Kind:       "KubeadmConfig",
   369  				APIVersion: clusterv1.GroupVersion.String(),
   370  			},
   371  			ObjectMeta: metav1.ObjectMeta{
   372  				Namespace: "default",
   373  				Name:      "test",
   374  			},
   375  			Spec: clusterv1.MachineSpec{
   376  				Bootstrap: clusterv1.Bootstrap{
   377  					ConfigRef: &corev1.ObjectReference{
   378  						Kind:       "KubeadmConfig",
   379  						Namespace:  "default",
   380  						Name:       "test",
   381  						APIVersion: bootstrapv1.GroupVersion.String(),
   382  					},
   383  				},
   384  			},
   385  		}
   386  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   387  			m.Name: {
   388  				TypeMeta: metav1.TypeMeta{
   389  					Kind:       "KubeadmConfig",
   390  					APIVersion: bootstrapv1.GroupVersion.String(),
   391  				},
   392  				ObjectMeta: metav1.ObjectMeta{
   393  					Namespace: "default",
   394  					Name:      "test",
   395  				},
   396  				Spec: bootstrapv1.KubeadmConfigSpec{
   397  					InitConfiguration: &bootstrapv1.InitConfiguration{},
   398  				},
   399  			},
   400  		}
   401  		g.Expect(matchInitOrJoinConfiguration(machineConfigs[m.Name], kcp)).To(BeTrue())
   402  	})
   403  	t.Run("returns false if InitConfiguration is NOT equal", func(t *testing.T) {
   404  		g := NewWithT(t)
   405  		kcp := &controlplanev1.KubeadmControlPlane{
   406  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   407  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   408  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   409  					InitConfiguration: &bootstrapv1.InitConfiguration{
   410  						NodeRegistration: bootstrapv1.NodeRegistrationOptions{
   411  							Name: "A new name", // This is a change
   412  						},
   413  					},
   414  					JoinConfiguration: &bootstrapv1.JoinConfiguration{},
   415  				},
   416  			},
   417  		}
   418  		m := &clusterv1.Machine{
   419  			TypeMeta: metav1.TypeMeta{
   420  				Kind:       "KubeadmConfig",
   421  				APIVersion: clusterv1.GroupVersion.String(),
   422  			},
   423  			ObjectMeta: metav1.ObjectMeta{
   424  				Namespace: "default",
   425  				Name:      "test",
   426  			},
   427  			Spec: clusterv1.MachineSpec{
   428  				Bootstrap: clusterv1.Bootstrap{
   429  					ConfigRef: &corev1.ObjectReference{
   430  						Kind:       "KubeadmConfig",
   431  						Namespace:  "default",
   432  						Name:       "test",
   433  						APIVersion: bootstrapv1.GroupVersion.String(),
   434  					},
   435  				},
   436  			},
   437  		}
   438  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   439  			m.Name: {
   440  				TypeMeta: metav1.TypeMeta{
   441  					Kind:       "KubeadmConfig",
   442  					APIVersion: bootstrapv1.GroupVersion.String(),
   443  				},
   444  				ObjectMeta: metav1.ObjectMeta{
   445  					Namespace: "default",
   446  					Name:      "test",
   447  				},
   448  				Spec: bootstrapv1.KubeadmConfigSpec{
   449  					InitConfiguration: &bootstrapv1.InitConfiguration{},
   450  				},
   451  			},
   452  		}
   453  		g.Expect(matchInitOrJoinConfiguration(machineConfigs[m.Name], kcp)).To(BeFalse())
   454  	})
   455  	t.Run("returns true if JoinConfiguration is equal", func(t *testing.T) {
   456  		g := NewWithT(t)
   457  		kcp := &controlplanev1.KubeadmControlPlane{
   458  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   459  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   460  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   461  					InitConfiguration:    &bootstrapv1.InitConfiguration{},
   462  					JoinConfiguration:    &bootstrapv1.JoinConfiguration{},
   463  				},
   464  			},
   465  		}
   466  		m := &clusterv1.Machine{
   467  			TypeMeta: metav1.TypeMeta{
   468  				Kind:       "KubeadmConfig",
   469  				APIVersion: clusterv1.GroupVersion.String(),
   470  			},
   471  			ObjectMeta: metav1.ObjectMeta{
   472  				Namespace: "default",
   473  				Name:      "test",
   474  			},
   475  			Spec: clusterv1.MachineSpec{
   476  				Bootstrap: clusterv1.Bootstrap{
   477  					ConfigRef: &corev1.ObjectReference{
   478  						Kind:       "KubeadmConfig",
   479  						Namespace:  "default",
   480  						Name:       "test",
   481  						APIVersion: bootstrapv1.GroupVersion.String(),
   482  					},
   483  				},
   484  			},
   485  		}
   486  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   487  			m.Name: {
   488  				TypeMeta: metav1.TypeMeta{
   489  					Kind:       "KubeadmConfig",
   490  					APIVersion: bootstrapv1.GroupVersion.String(),
   491  				},
   492  				ObjectMeta: metav1.ObjectMeta{
   493  					Namespace: "default",
   494  					Name:      "test",
   495  				},
   496  				Spec: bootstrapv1.KubeadmConfigSpec{
   497  					JoinConfiguration: &bootstrapv1.JoinConfiguration{},
   498  				},
   499  			},
   500  		}
   501  		g.Expect(matchInitOrJoinConfiguration(machineConfigs[m.Name], kcp)).To(BeTrue())
   502  	})
   503  	t.Run("returns false if JoinConfiguration is NOT equal", func(t *testing.T) {
   504  		g := NewWithT(t)
   505  		kcp := &controlplanev1.KubeadmControlPlane{
   506  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   507  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   508  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   509  					InitConfiguration:    &bootstrapv1.InitConfiguration{},
   510  					JoinConfiguration: &bootstrapv1.JoinConfiguration{
   511  						NodeRegistration: bootstrapv1.NodeRegistrationOptions{
   512  							Name: "A new name", // This is a change
   513  						},
   514  					},
   515  				},
   516  			},
   517  		}
   518  		m := &clusterv1.Machine{
   519  			TypeMeta: metav1.TypeMeta{
   520  				Kind:       "KubeadmConfig",
   521  				APIVersion: clusterv1.GroupVersion.String(),
   522  			},
   523  			ObjectMeta: metav1.ObjectMeta{
   524  				Namespace: "default",
   525  				Name:      "test",
   526  			},
   527  			Spec: clusterv1.MachineSpec{
   528  				Bootstrap: clusterv1.Bootstrap{
   529  					ConfigRef: &corev1.ObjectReference{
   530  						Kind:       "KubeadmConfig",
   531  						Namespace:  "default",
   532  						Name:       "test",
   533  						APIVersion: bootstrapv1.GroupVersion.String(),
   534  					},
   535  				},
   536  			},
   537  		}
   538  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   539  			m.Name: {
   540  				TypeMeta: metav1.TypeMeta{
   541  					Kind:       "KubeadmConfig",
   542  					APIVersion: bootstrapv1.GroupVersion.String(),
   543  				},
   544  				ObjectMeta: metav1.ObjectMeta{
   545  					Namespace: "default",
   546  					Name:      "test",
   547  				},
   548  				Spec: bootstrapv1.KubeadmConfigSpec{
   549  					JoinConfiguration: &bootstrapv1.JoinConfiguration{},
   550  				},
   551  			},
   552  		}
   553  		g.Expect(matchInitOrJoinConfiguration(machineConfigs[m.Name], kcp)).To(BeFalse())
   554  	})
   555  	t.Run("returns false if some other configurations are not equal", func(t *testing.T) {
   556  		g := NewWithT(t)
   557  		kcp := &controlplanev1.KubeadmControlPlane{
   558  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   559  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   560  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   561  					InitConfiguration:    &bootstrapv1.InitConfiguration{},
   562  					JoinConfiguration:    &bootstrapv1.JoinConfiguration{},
   563  					Files:                []bootstrapv1.File{}, // This is a change
   564  				},
   565  			},
   566  		}
   567  		m := &clusterv1.Machine{
   568  			TypeMeta: metav1.TypeMeta{
   569  				Kind:       "KubeadmConfig",
   570  				APIVersion: clusterv1.GroupVersion.String(),
   571  			},
   572  			ObjectMeta: metav1.ObjectMeta{
   573  				Namespace: "default",
   574  				Name:      "test",
   575  			},
   576  			Spec: clusterv1.MachineSpec{
   577  				Bootstrap: clusterv1.Bootstrap{
   578  					ConfigRef: &corev1.ObjectReference{
   579  						Kind:       "KubeadmConfig",
   580  						Namespace:  "default",
   581  						Name:       "test",
   582  						APIVersion: bootstrapv1.GroupVersion.String(),
   583  					},
   584  				},
   585  			},
   586  		}
   587  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   588  			m.Name: {
   589  				TypeMeta: metav1.TypeMeta{
   590  					Kind:       "KubeadmConfig",
   591  					APIVersion: bootstrapv1.GroupVersion.String(),
   592  				},
   593  				ObjectMeta: metav1.ObjectMeta{
   594  					Namespace: "default",
   595  					Name:      "test",
   596  				},
   597  				Spec: bootstrapv1.KubeadmConfigSpec{
   598  					InitConfiguration: &bootstrapv1.InitConfiguration{},
   599  				},
   600  			},
   601  		}
   602  		g.Expect(matchInitOrJoinConfiguration(machineConfigs[m.Name], kcp)).To(BeFalse())
   603  	})
   604  }
   605  
   606  func TestMatchesKubeadmBootstrapConfig(t *testing.T) {
   607  	t.Run("returns true if ClusterConfiguration is equal", func(t *testing.T) {
   608  		g := NewWithT(t)
   609  		kcp := &controlplanev1.KubeadmControlPlane{
   610  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   611  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   612  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   613  						ClusterName: "foo",
   614  					},
   615  				},
   616  			},
   617  		}
   618  		m := &clusterv1.Machine{
   619  			ObjectMeta: metav1.ObjectMeta{
   620  				Annotations: map[string]string{
   621  					controlplanev1.KubeadmClusterConfigurationAnnotation: "{\n  \"clusterName\": \"foo\"\n}",
   622  				},
   623  			},
   624  		}
   625  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   626  			m.Name: {},
   627  		}
   628  		reason, match := matchesKubeadmBootstrapConfig(machineConfigs, kcp, m)
   629  		g.Expect(match).To(BeTrue())
   630  		g.Expect(reason).To(BeEmpty())
   631  	})
   632  	t.Run("returns false if ClusterConfiguration is NOT equal", func(t *testing.T) {
   633  		g := NewWithT(t)
   634  		kcp := &controlplanev1.KubeadmControlPlane{
   635  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   636  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   637  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   638  						ClusterName: "foo",
   639  					},
   640  				},
   641  			},
   642  		}
   643  		m := &clusterv1.Machine{
   644  			ObjectMeta: metav1.ObjectMeta{
   645  				Annotations: map[string]string{
   646  					controlplanev1.KubeadmClusterConfigurationAnnotation: "{\n  \"clusterName\": \"bar\"\n}",
   647  				},
   648  			},
   649  		}
   650  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   651  			m.Name: {},
   652  		}
   653  		reason, match := matchesKubeadmBootstrapConfig(machineConfigs, kcp, m)
   654  		g.Expect(match).To(BeFalse())
   655  		g.Expect(reason).To(Equal("Machine ClusterConfiguration is outdated"))
   656  	})
   657  	t.Run("returns true if InitConfiguration is equal", func(t *testing.T) {
   658  		g := NewWithT(t)
   659  		kcp := &controlplanev1.KubeadmControlPlane{
   660  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   661  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   662  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   663  					InitConfiguration:    &bootstrapv1.InitConfiguration{},
   664  					JoinConfiguration:    &bootstrapv1.JoinConfiguration{},
   665  				},
   666  			},
   667  		}
   668  		m := &clusterv1.Machine{
   669  			TypeMeta: metav1.TypeMeta{
   670  				Kind:       "KubeadmConfig",
   671  				APIVersion: clusterv1.GroupVersion.String(),
   672  			},
   673  			ObjectMeta: metav1.ObjectMeta{
   674  				Namespace: "default",
   675  				Name:      "test",
   676  			},
   677  			Spec: clusterv1.MachineSpec{
   678  				Bootstrap: clusterv1.Bootstrap{
   679  					ConfigRef: &corev1.ObjectReference{
   680  						Kind:       "KubeadmConfig",
   681  						Namespace:  "default",
   682  						Name:       "test",
   683  						APIVersion: bootstrapv1.GroupVersion.String(),
   684  					},
   685  				},
   686  			},
   687  		}
   688  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   689  			m.Name: {
   690  				TypeMeta: metav1.TypeMeta{
   691  					Kind:       "KubeadmConfig",
   692  					APIVersion: bootstrapv1.GroupVersion.String(),
   693  				},
   694  				ObjectMeta: metav1.ObjectMeta{
   695  					Namespace: "default",
   696  					Name:      "test",
   697  				},
   698  				Spec: bootstrapv1.KubeadmConfigSpec{
   699  					InitConfiguration: &bootstrapv1.InitConfiguration{},
   700  				},
   701  			},
   702  		}
   703  		reason, match := matchesKubeadmBootstrapConfig(machineConfigs, kcp, m)
   704  		g.Expect(match).To(BeTrue())
   705  		g.Expect(reason).To(BeEmpty())
   706  	})
   707  	t.Run("returns false if InitConfiguration is NOT equal", func(t *testing.T) {
   708  		g := NewWithT(t)
   709  		kcp := &controlplanev1.KubeadmControlPlane{
   710  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   711  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   712  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   713  					InitConfiguration: &bootstrapv1.InitConfiguration{
   714  						NodeRegistration: bootstrapv1.NodeRegistrationOptions{
   715  							Name: "foo", // This is a change
   716  						},
   717  					},
   718  					JoinConfiguration: &bootstrapv1.JoinConfiguration{},
   719  				},
   720  			},
   721  		}
   722  		m := &clusterv1.Machine{
   723  			TypeMeta: metav1.TypeMeta{
   724  				Kind:       "KubeadmConfig",
   725  				APIVersion: clusterv1.GroupVersion.String(),
   726  			},
   727  			ObjectMeta: metav1.ObjectMeta{
   728  				Namespace: "default",
   729  				Name:      "test",
   730  			},
   731  			Spec: clusterv1.MachineSpec{
   732  				Bootstrap: clusterv1.Bootstrap{
   733  					ConfigRef: &corev1.ObjectReference{
   734  						Kind:       "KubeadmConfig",
   735  						Namespace:  "default",
   736  						Name:       "test",
   737  						APIVersion: bootstrapv1.GroupVersion.String(),
   738  					},
   739  				},
   740  			},
   741  		}
   742  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   743  			m.Name: {
   744  				TypeMeta: metav1.TypeMeta{
   745  					Kind:       "KubeadmConfig",
   746  					APIVersion: bootstrapv1.GroupVersion.String(),
   747  				},
   748  				ObjectMeta: metav1.ObjectMeta{
   749  					Namespace: "default",
   750  					Name:      "test",
   751  				},
   752  				Spec: bootstrapv1.KubeadmConfigSpec{
   753  					InitConfiguration: &bootstrapv1.InitConfiguration{},
   754  				},
   755  			},
   756  		}
   757  		reason, match := matchesKubeadmBootstrapConfig(machineConfigs, kcp, m)
   758  		g.Expect(match).To(BeFalse())
   759  		g.Expect(reason).To(Equal("Machine InitConfiguration or JoinConfiguration are outdated"))
   760  	})
   761  	t.Run("returns true if JoinConfiguration is equal", func(t *testing.T) {
   762  		g := NewWithT(t)
   763  		kcp := &controlplanev1.KubeadmControlPlane{
   764  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   765  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   766  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   767  					InitConfiguration:    &bootstrapv1.InitConfiguration{},
   768  					JoinConfiguration:    &bootstrapv1.JoinConfiguration{},
   769  				},
   770  			},
   771  		}
   772  		m := &clusterv1.Machine{
   773  			TypeMeta: metav1.TypeMeta{
   774  				Kind:       "KubeadmConfig",
   775  				APIVersion: clusterv1.GroupVersion.String(),
   776  			},
   777  			ObjectMeta: metav1.ObjectMeta{
   778  				Namespace: "default",
   779  				Name:      "test",
   780  			},
   781  			Spec: clusterv1.MachineSpec{
   782  				Bootstrap: clusterv1.Bootstrap{
   783  					ConfigRef: &corev1.ObjectReference{
   784  						Kind:       "KubeadmConfig",
   785  						Namespace:  "default",
   786  						Name:       "test",
   787  						APIVersion: bootstrapv1.GroupVersion.String(),
   788  					},
   789  				},
   790  			},
   791  		}
   792  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   793  			m.Name: {
   794  				TypeMeta: metav1.TypeMeta{
   795  					Kind:       "KubeadmConfig",
   796  					APIVersion: bootstrapv1.GroupVersion.String(),
   797  				},
   798  				ObjectMeta: metav1.ObjectMeta{
   799  					Namespace: "default",
   800  					Name:      "test",
   801  				},
   802  				Spec: bootstrapv1.KubeadmConfigSpec{
   803  					JoinConfiguration: &bootstrapv1.JoinConfiguration{},
   804  				},
   805  			},
   806  		}
   807  		reason, match := matchesKubeadmBootstrapConfig(machineConfigs, kcp, m)
   808  		g.Expect(match).To(BeTrue())
   809  		g.Expect(reason).To(BeEmpty())
   810  	})
   811  	t.Run("returns false if JoinConfiguration is NOT equal", func(t *testing.T) {
   812  		g := NewWithT(t)
   813  		kcp := &controlplanev1.KubeadmControlPlane{
   814  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   815  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   816  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   817  					InitConfiguration:    &bootstrapv1.InitConfiguration{},
   818  					JoinConfiguration: &bootstrapv1.JoinConfiguration{
   819  						NodeRegistration: bootstrapv1.NodeRegistrationOptions{
   820  							Name: "foo", // This is a change
   821  						},
   822  					},
   823  				},
   824  			},
   825  		}
   826  		m := &clusterv1.Machine{
   827  			TypeMeta: metav1.TypeMeta{
   828  				Kind:       "KubeadmConfig",
   829  				APIVersion: clusterv1.GroupVersion.String(),
   830  			},
   831  			ObjectMeta: metav1.ObjectMeta{
   832  				Namespace: "default",
   833  				Name:      "test",
   834  			},
   835  			Spec: clusterv1.MachineSpec{
   836  				Bootstrap: clusterv1.Bootstrap{
   837  					ConfigRef: &corev1.ObjectReference{
   838  						Kind:       "KubeadmConfig",
   839  						Namespace:  "default",
   840  						Name:       "test",
   841  						APIVersion: bootstrapv1.GroupVersion.String(),
   842  					},
   843  				},
   844  			},
   845  		}
   846  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   847  			m.Name: {
   848  				TypeMeta: metav1.TypeMeta{
   849  					Kind:       "KubeadmConfig",
   850  					APIVersion: bootstrapv1.GroupVersion.String(),
   851  				},
   852  				ObjectMeta: metav1.ObjectMeta{
   853  					Namespace: "default",
   854  					Name:      "test",
   855  				},
   856  				Spec: bootstrapv1.KubeadmConfigSpec{
   857  					JoinConfiguration: &bootstrapv1.JoinConfiguration{},
   858  				},
   859  			},
   860  		}
   861  		reason, match := matchesKubeadmBootstrapConfig(machineConfigs, kcp, m)
   862  		g.Expect(match).To(BeFalse())
   863  		g.Expect(reason).To(Equal("Machine InitConfiguration or JoinConfiguration are outdated"))
   864  	})
   865  	t.Run("returns false if some other configurations are not equal", func(t *testing.T) {
   866  		g := NewWithT(t)
   867  		kcp := &controlplanev1.KubeadmControlPlane{
   868  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   869  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   870  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   871  					InitConfiguration:    &bootstrapv1.InitConfiguration{},
   872  					JoinConfiguration:    &bootstrapv1.JoinConfiguration{},
   873  					Files:                []bootstrapv1.File{}, // This is a change
   874  				},
   875  			},
   876  		}
   877  		m := &clusterv1.Machine{
   878  			TypeMeta: metav1.TypeMeta{
   879  				Kind:       "KubeadmConfig",
   880  				APIVersion: clusterv1.GroupVersion.String(),
   881  			},
   882  			ObjectMeta: metav1.ObjectMeta{
   883  				Namespace: "default",
   884  				Name:      "test",
   885  			},
   886  			Spec: clusterv1.MachineSpec{
   887  				Bootstrap: clusterv1.Bootstrap{
   888  					ConfigRef: &corev1.ObjectReference{
   889  						Kind:       "KubeadmConfig",
   890  						Namespace:  "default",
   891  						Name:       "test",
   892  						APIVersion: bootstrapv1.GroupVersion.String(),
   893  					},
   894  				},
   895  			},
   896  		}
   897  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   898  			m.Name: {
   899  				TypeMeta: metav1.TypeMeta{
   900  					Kind:       "KubeadmConfig",
   901  					APIVersion: bootstrapv1.GroupVersion.String(),
   902  				},
   903  				ObjectMeta: metav1.ObjectMeta{
   904  					Namespace: "default",
   905  					Name:      "test",
   906  				},
   907  				Spec: bootstrapv1.KubeadmConfigSpec{
   908  					InitConfiguration: &bootstrapv1.InitConfiguration{},
   909  				},
   910  			},
   911  		}
   912  		reason, match := matchesKubeadmBootstrapConfig(machineConfigs, kcp, m)
   913  		g.Expect(match).To(BeFalse())
   914  		g.Expect(reason).To(Equal("Machine InitConfiguration or JoinConfiguration are outdated"))
   915  	})
   916  	t.Run("should match on labels and annotations", func(t *testing.T) {
   917  		kcp := &controlplanev1.KubeadmControlPlane{
   918  			Spec: controlplanev1.KubeadmControlPlaneSpec{
   919  				MachineTemplate: controlplanev1.KubeadmControlPlaneMachineTemplate{
   920  					ObjectMeta: clusterv1.ObjectMeta{
   921  						Annotations: map[string]string{
   922  							"test": "annotation",
   923  						},
   924  						Labels: map[string]string{
   925  							"test": "labels",
   926  						},
   927  					},
   928  				},
   929  				KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   930  					ClusterConfiguration: &bootstrapv1.ClusterConfiguration{},
   931  					InitConfiguration:    &bootstrapv1.InitConfiguration{},
   932  					JoinConfiguration:    &bootstrapv1.JoinConfiguration{},
   933  				},
   934  			},
   935  		}
   936  		m := &clusterv1.Machine{
   937  			TypeMeta: metav1.TypeMeta{
   938  				Kind:       "KubeadmConfig",
   939  				APIVersion: clusterv1.GroupVersion.String(),
   940  			},
   941  			ObjectMeta: metav1.ObjectMeta{
   942  				Namespace: "default",
   943  				Name:      "test",
   944  			},
   945  			Spec: clusterv1.MachineSpec{
   946  				Bootstrap: clusterv1.Bootstrap{
   947  					ConfigRef: &corev1.ObjectReference{
   948  						Kind:       "KubeadmConfig",
   949  						Namespace:  "default",
   950  						Name:       "test",
   951  						APIVersion: bootstrapv1.GroupVersion.String(),
   952  					},
   953  				},
   954  			},
   955  		}
   956  		machineConfigs := map[string]*bootstrapv1.KubeadmConfig{
   957  			m.Name: {
   958  				TypeMeta: metav1.TypeMeta{
   959  					Kind:       "KubeadmConfig",
   960  					APIVersion: bootstrapv1.GroupVersion.String(),
   961  				},
   962  				ObjectMeta: metav1.ObjectMeta{
   963  					Namespace: "default",
   964  					Name:      "test",
   965  				},
   966  				Spec: bootstrapv1.KubeadmConfigSpec{
   967  					JoinConfiguration: &bootstrapv1.JoinConfiguration{},
   968  				},
   969  			},
   970  		}
   971  
   972  		t.Run("by returning true if neither labels or annotations match", func(t *testing.T) {
   973  			g := NewWithT(t)
   974  			machineConfigs[m.Name].Annotations = nil
   975  			machineConfigs[m.Name].Labels = nil
   976  			reason, match := matchesKubeadmBootstrapConfig(machineConfigs, kcp, m)
   977  			g.Expect(match).To(BeTrue())
   978  			g.Expect(reason).To(BeEmpty())
   979  		})
   980  
   981  		t.Run("by returning true if only labels don't match", func(t *testing.T) {
   982  			g := NewWithT(t)
   983  			machineConfigs[m.Name].Annotations = kcp.Spec.MachineTemplate.ObjectMeta.Annotations
   984  			machineConfigs[m.Name].Labels = nil
   985  			reason, match := matchesKubeadmBootstrapConfig(machineConfigs, kcp, m)
   986  			g.Expect(match).To(BeTrue())
   987  			g.Expect(reason).To(BeEmpty())
   988  		})
   989  
   990  		t.Run("by returning true if only annotations don't match", func(t *testing.T) {
   991  			g := NewWithT(t)
   992  			machineConfigs[m.Name].Annotations = nil
   993  			machineConfigs[m.Name].Labels = kcp.Spec.MachineTemplate.ObjectMeta.Labels
   994  			reason, match := matchesKubeadmBootstrapConfig(machineConfigs, kcp, m)
   995  			g.Expect(match).To(BeTrue())
   996  			g.Expect(reason).To(BeEmpty())
   997  		})
   998  
   999  		t.Run("by returning true if both labels and annotations match", func(t *testing.T) {
  1000  			g := NewWithT(t)
  1001  			machineConfigs[m.Name].Labels = kcp.Spec.MachineTemplate.ObjectMeta.Labels
  1002  			machineConfigs[m.Name].Annotations = kcp.Spec.MachineTemplate.ObjectMeta.Annotations
  1003  			reason, match := matchesKubeadmBootstrapConfig(machineConfigs, kcp, m)
  1004  			g.Expect(match).To(BeTrue())
  1005  			g.Expect(reason).To(BeEmpty())
  1006  		})
  1007  	})
  1008  }
  1009  
  1010  func TestMatchesTemplateClonedFrom(t *testing.T) {
  1011  	t.Run("nil machine returns false", func(t *testing.T) {
  1012  		g := NewWithT(t)
  1013  		reason, match := matchesTemplateClonedFrom(nil, nil, nil)
  1014  		g.Expect(match).To(BeFalse())
  1015  		g.Expect(reason).To(Equal("Machine cannot be compared with KCP.spec.machineTemplate.infrastructureRef: Machine is nil"))
  1016  	})
  1017  
  1018  	t.Run("returns true if machine not found", func(t *testing.T) {
  1019  		g := NewWithT(t)
  1020  		kcp := &controlplanev1.KubeadmControlPlane{}
  1021  		machine := &clusterv1.Machine{
  1022  			Spec: clusterv1.MachineSpec{
  1023  				InfrastructureRef: corev1.ObjectReference{
  1024  					Kind:       "KubeadmConfig",
  1025  					Namespace:  "default",
  1026  					Name:       "test",
  1027  					APIVersion: bootstrapv1.GroupVersion.String(),
  1028  				},
  1029  			},
  1030  		}
  1031  		reason, match := matchesTemplateClonedFrom(map[string]*unstructured.Unstructured{}, kcp, machine)
  1032  		g.Expect(match).To(BeTrue())
  1033  		g.Expect(reason).To(BeEmpty())
  1034  	})
  1035  
  1036  	t.Run("matches labels or annotations", func(t *testing.T) {
  1037  		kcp := &controlplanev1.KubeadmControlPlane{
  1038  			ObjectMeta: metav1.ObjectMeta{
  1039  				Namespace: "default",
  1040  			},
  1041  			Spec: controlplanev1.KubeadmControlPlaneSpec{
  1042  				MachineTemplate: controlplanev1.KubeadmControlPlaneMachineTemplate{
  1043  					ObjectMeta: clusterv1.ObjectMeta{
  1044  						Annotations: map[string]string{
  1045  							"test": "annotation",
  1046  						},
  1047  						Labels: map[string]string{
  1048  							"test": "labels",
  1049  						},
  1050  					},
  1051  					InfrastructureRef: corev1.ObjectReference{
  1052  						Kind:       "GenericMachineTemplate",
  1053  						Namespace:  "default",
  1054  						Name:       "infra-foo",
  1055  						APIVersion: "generic.io/v1",
  1056  					},
  1057  				},
  1058  			},
  1059  		}
  1060  		m := &clusterv1.Machine{
  1061  			Spec: clusterv1.MachineSpec{
  1062  				InfrastructureRef: corev1.ObjectReference{
  1063  					Kind:       "GenericMachine",
  1064  					Namespace:  "default",
  1065  					Name:       "infra-foo",
  1066  					APIVersion: "generic.io/v1",
  1067  				},
  1068  			},
  1069  		}
  1070  
  1071  		infraConfigs := map[string]*unstructured.Unstructured{
  1072  			m.Name: {
  1073  				Object: map[string]interface{}{
  1074  					"kind":       "InfrastructureMachine",
  1075  					"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1076  					"metadata": map[string]interface{}{
  1077  						"name":      "infra-config1",
  1078  						"namespace": "default",
  1079  					},
  1080  				},
  1081  			},
  1082  		}
  1083  
  1084  		t.Run("by returning true if neither labels or annotations match", func(t *testing.T) {
  1085  			g := NewWithT(t)
  1086  			infraConfigs[m.Name].SetAnnotations(map[string]string{
  1087  				clusterv1.TemplateClonedFromNameAnnotation:      "infra-foo",
  1088  				clusterv1.TemplateClonedFromGroupKindAnnotation: "GenericMachineTemplate.generic.io",
  1089  			})
  1090  			infraConfigs[m.Name].SetLabels(nil)
  1091  			reason, match := matchesTemplateClonedFrom(infraConfigs, kcp, m)
  1092  			g.Expect(match).To(BeTrue())
  1093  			g.Expect(reason).To(BeEmpty())
  1094  		})
  1095  
  1096  		t.Run("by returning true if only labels don't match", func(t *testing.T) {
  1097  			g := NewWithT(t)
  1098  			infraConfigs[m.Name].SetAnnotations(map[string]string{
  1099  				clusterv1.TemplateClonedFromNameAnnotation:      "infra-foo",
  1100  				clusterv1.TemplateClonedFromGroupKindAnnotation: "GenericMachineTemplate.generic.io",
  1101  				"test": "annotation",
  1102  			})
  1103  			infraConfigs[m.Name].SetLabels(nil)
  1104  			reason, match := matchesTemplateClonedFrom(infraConfigs, kcp, m)
  1105  			g.Expect(match).To(BeTrue())
  1106  			g.Expect(reason).To(BeEmpty())
  1107  		})
  1108  
  1109  		t.Run("by returning true if only annotations don't match", func(t *testing.T) {
  1110  			g := NewWithT(t)
  1111  			infraConfigs[m.Name].SetAnnotations(map[string]string{
  1112  				clusterv1.TemplateClonedFromNameAnnotation:      "infra-foo",
  1113  				clusterv1.TemplateClonedFromGroupKindAnnotation: "GenericMachineTemplate.generic.io",
  1114  			})
  1115  			infraConfigs[m.Name].SetLabels(kcp.Spec.MachineTemplate.ObjectMeta.Labels)
  1116  			reason, match := matchesTemplateClonedFrom(infraConfigs, kcp, m)
  1117  			g.Expect(match).To(BeTrue())
  1118  			g.Expect(reason).To(BeEmpty())
  1119  		})
  1120  
  1121  		t.Run("by returning true if both labels and annotations match", func(t *testing.T) {
  1122  			g := NewWithT(t)
  1123  			infraConfigs[m.Name].SetAnnotations(map[string]string{
  1124  				clusterv1.TemplateClonedFromNameAnnotation:      "infra-foo",
  1125  				clusterv1.TemplateClonedFromGroupKindAnnotation: "GenericMachineTemplate.generic.io",
  1126  				"test": "annotation",
  1127  			})
  1128  			infraConfigs[m.Name].SetLabels(kcp.Spec.MachineTemplate.ObjectMeta.Labels)
  1129  			reason, match := matchesTemplateClonedFrom(infraConfigs, kcp, m)
  1130  			g.Expect(match).To(BeTrue())
  1131  			g.Expect(reason).To(BeEmpty())
  1132  		})
  1133  	})
  1134  }
  1135  
  1136  func TestMatchesTemplateClonedFrom_WithClonedFromAnnotations(t *testing.T) {
  1137  	kcp := &controlplanev1.KubeadmControlPlane{
  1138  		ObjectMeta: metav1.ObjectMeta{
  1139  			Namespace: "default",
  1140  		},
  1141  		Spec: controlplanev1.KubeadmControlPlaneSpec{
  1142  			MachineTemplate: controlplanev1.KubeadmControlPlaneMachineTemplate{
  1143  				InfrastructureRef: corev1.ObjectReference{
  1144  					Kind:       "GenericMachineTemplate",
  1145  					Namespace:  "default",
  1146  					Name:       "infra-foo",
  1147  					APIVersion: "generic.io/v1",
  1148  				},
  1149  			},
  1150  		},
  1151  	}
  1152  	machine := &clusterv1.Machine{
  1153  		Spec: clusterv1.MachineSpec{
  1154  			InfrastructureRef: corev1.ObjectReference{
  1155  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1156  				Kind:       "InfrastructureMachine",
  1157  				Name:       "infra-config1",
  1158  				Namespace:  "default",
  1159  			},
  1160  		},
  1161  	}
  1162  	tests := []struct {
  1163  		name         string
  1164  		annotations  map[string]interface{}
  1165  		expectMatch  bool
  1166  		expectReason string
  1167  	}{
  1168  		{
  1169  			name:        "returns true if annotations don't exist",
  1170  			annotations: map[string]interface{}{},
  1171  			expectMatch: true,
  1172  		},
  1173  		{
  1174  			name: "returns false if annotations don't match anything",
  1175  			annotations: map[string]interface{}{
  1176  				clusterv1.TemplateClonedFromNameAnnotation:      "barfoo1",
  1177  				clusterv1.TemplateClonedFromGroupKindAnnotation: "barfoo2",
  1178  			},
  1179  			expectMatch:  false,
  1180  			expectReason: "Infrastructure template on KCP rotated from barfoo2 barfoo1 to GenericMachineTemplate.generic.io infra-foo",
  1181  		},
  1182  		{
  1183  			name: "returns false if TemplateClonedFromNameAnnotation matches but TemplateClonedFromGroupKindAnnotation doesn't",
  1184  			annotations: map[string]interface{}{
  1185  				clusterv1.TemplateClonedFromNameAnnotation:      "infra-foo",
  1186  				clusterv1.TemplateClonedFromGroupKindAnnotation: "barfoo2",
  1187  			},
  1188  			expectMatch:  false,
  1189  			expectReason: "Infrastructure template on KCP rotated from barfoo2 infra-foo to GenericMachineTemplate.generic.io infra-foo",
  1190  		},
  1191  		{
  1192  			name: "returns true if both annotations match",
  1193  			annotations: map[string]interface{}{
  1194  				clusterv1.TemplateClonedFromNameAnnotation:      "infra-foo",
  1195  				clusterv1.TemplateClonedFromGroupKindAnnotation: "GenericMachineTemplate.generic.io",
  1196  			},
  1197  			expectMatch: true,
  1198  		},
  1199  	}
  1200  
  1201  	for _, tt := range tests {
  1202  		t.Run(tt.name, func(t *testing.T) {
  1203  			g := NewWithT(t)
  1204  			infraConfigs := map[string]*unstructured.Unstructured{
  1205  				machine.Name: {
  1206  					Object: map[string]interface{}{
  1207  						"kind":       "InfrastructureMachine",
  1208  						"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1209  						"metadata": map[string]interface{}{
  1210  							"name":        "infra-config1",
  1211  							"namespace":   "default",
  1212  							"annotations": tt.annotations,
  1213  						},
  1214  					},
  1215  				},
  1216  			}
  1217  			reason, match := matchesTemplateClonedFrom(infraConfigs, kcp, machine)
  1218  			g.Expect(match).To(Equal(tt.expectMatch))
  1219  			g.Expect(reason).To(Equal(tt.expectReason))
  1220  		})
  1221  	}
  1222  }