sigs.k8s.io/cluster-api@v1.6.3/internal/topology/check/compatibility_test.go (about)

     1  /*
     2  Copyright 2021 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 check
    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  	"k8s.io/apimachinery/pkg/util/validation/field"
    27  
    28  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    29  	"sigs.k8s.io/cluster-api/internal/test/builder"
    30  )
    31  
    32  type referencedObjectsCompatibilityTestCase struct {
    33  	name    string
    34  	current *unstructured.Unstructured
    35  	desired *unstructured.Unstructured
    36  	wantErr bool
    37  }
    38  
    39  var referencedObjectsCompatibilityTestCases = []referencedObjectsCompatibilityTestCase{
    40  	{
    41  		name: "Fails if group changes",
    42  		current: &unstructured.Unstructured{
    43  			Object: map[string]interface{}{
    44  				"apiVersion": "foo/v1beta1",
    45  			},
    46  		},
    47  		desired: &unstructured.Unstructured{
    48  			Object: map[string]interface{}{
    49  				"apiVersion": "bar/v1beta1",
    50  			},
    51  		},
    52  		wantErr: true,
    53  	},
    54  	{
    55  		name: "Fails if kind changes",
    56  		current: &unstructured.Unstructured{
    57  			Object: map[string]interface{}{
    58  				"kind": "foo",
    59  			},
    60  		},
    61  		desired: &unstructured.Unstructured{
    62  			Object: map[string]interface{}{
    63  				"kind": "bar",
    64  			},
    65  		},
    66  		wantErr: true,
    67  	},
    68  	{
    69  		name: "Pass if gvk remains the same",
    70  		current: &unstructured.Unstructured{
    71  			Object: map[string]interface{}{
    72  				"apiVersion": "infrastructure.cluster.x-k8s.io/foo",
    73  				"kind":       "foo",
    74  			},
    75  		},
    76  		desired: &unstructured.Unstructured{
    77  			Object: map[string]interface{}{
    78  				"apiVersion": "infrastructure.cluster.x-k8s.io/foo",
    79  				"kind":       "foo",
    80  			},
    81  		},
    82  		wantErr: false,
    83  	},
    84  	{
    85  		name: "Pass if version changes but group and kind remains the same",
    86  		current: &unstructured.Unstructured{
    87  			Object: map[string]interface{}{
    88  				"apiVersion": "infrastructure.cluster.x-k8s.io/foo",
    89  				"kind":       "foo",
    90  			},
    91  		},
    92  		desired: &unstructured.Unstructured{
    93  			Object: map[string]interface{}{
    94  				"apiVersion": "infrastructure.cluster.x-k8s.io/bar",
    95  				"kind":       "foo",
    96  			},
    97  		},
    98  		wantErr: false,
    99  	},
   100  	{
   101  		name: "Fails if namespace changes",
   102  		current: &unstructured.Unstructured{
   103  			Object: map[string]interface{}{
   104  				"metadata": map[string]interface{}{
   105  					"namespace": "foo",
   106  				},
   107  			},
   108  		},
   109  		desired: &unstructured.Unstructured{
   110  			Object: map[string]interface{}{
   111  				"metadata": map[string]interface{}{
   112  					"namespace": "bar",
   113  				},
   114  			},
   115  		},
   116  		wantErr: true,
   117  	},
   118  }
   119  
   120  func TestObjectsAreCompatible(t *testing.T) {
   121  	for _, tt := range referencedObjectsCompatibilityTestCases {
   122  		t.Run(tt.name, func(t *testing.T) {
   123  			g := NewWithT(t)
   124  			allErrs := ObjectsAreCompatible(tt.current, tt.desired)
   125  			if tt.wantErr {
   126  				g.Expect(allErrs).ToNot(BeEmpty())
   127  				return
   128  			}
   129  			g.Expect(allErrs).To(BeEmpty())
   130  		})
   131  	}
   132  }
   133  
   134  func TestObjectsAreStrictlyCompatible(t *testing.T) {
   135  	referencedObjectsStrictCompatibilityTestCases := append(referencedObjectsCompatibilityTestCases, []referencedObjectsCompatibilityTestCase{
   136  		{
   137  			name: "Fails if name changes",
   138  			current: &unstructured.Unstructured{
   139  				Object: map[string]interface{}{
   140  					"metadata": map[string]interface{}{
   141  						"name": "foo",
   142  					},
   143  				},
   144  			},
   145  			desired: &unstructured.Unstructured{
   146  				Object: map[string]interface{}{
   147  					"metadata": map[string]interface{}{
   148  						"name": "bar",
   149  					},
   150  				},
   151  			},
   152  			wantErr: true,
   153  		},
   154  		{
   155  			name: "Pass if name remains the same",
   156  			current: &unstructured.Unstructured{
   157  				Object: map[string]interface{}{
   158  					"metadata": map[string]interface{}{
   159  						"name": "foo",
   160  					},
   161  				},
   162  			},
   163  			desired: &unstructured.Unstructured{
   164  				Object: map[string]interface{}{
   165  					"metadata": map[string]interface{}{
   166  						"name": "foo",
   167  					},
   168  				},
   169  			},
   170  			wantErr: false,
   171  		},
   172  	}...)
   173  
   174  	for _, tt := range referencedObjectsStrictCompatibilityTestCases {
   175  		t.Run(tt.name, func(t *testing.T) {
   176  			g := NewWithT(t)
   177  
   178  			allErrs := ObjectsAreStrictlyCompatible(tt.current, tt.desired)
   179  			if tt.wantErr {
   180  				g.Expect(allErrs).ToNot(BeEmpty())
   181  				return
   182  			}
   183  			g.Expect(allErrs).To(BeEmpty())
   184  		})
   185  	}
   186  }
   187  
   188  func TestLocalObjectTemplatesAreCompatible(t *testing.T) {
   189  	template := clusterv1.LocalObjectTemplate{
   190  		Ref: &corev1.ObjectReference{
   191  			Namespace:  "default",
   192  			Name:       "foo",
   193  			Kind:       "bar",
   194  			APIVersion: "test.group.io/versionone",
   195  		},
   196  	}
   197  	compatibleNameChangeTemplate := clusterv1.LocalObjectTemplate{
   198  		Ref: &corev1.ObjectReference{
   199  			Namespace:  "default",
   200  			Name:       "newFoo",
   201  			Kind:       "bar",
   202  			APIVersion: "test.group.io/versionone",
   203  		},
   204  	}
   205  	compatibleAPIVersionChangeTemplate := clusterv1.LocalObjectTemplate{
   206  		Ref: &corev1.ObjectReference{
   207  			Namespace:  "default",
   208  			Name:       "foo",
   209  			Kind:       "bar",
   210  			APIVersion: "test.group.io/versiontwo",
   211  		},
   212  	}
   213  	incompatibleNamespaceChangeTemplate := clusterv1.LocalObjectTemplate{
   214  		Ref: &corev1.ObjectReference{
   215  			Namespace:  "different",
   216  			Name:       "foo",
   217  			Kind:       "bar",
   218  			APIVersion: "test.group.io/versionone",
   219  		},
   220  	}
   221  	incompatibleAPIGroupChangeTemplate := clusterv1.LocalObjectTemplate{
   222  		Ref: &corev1.ObjectReference{
   223  			Namespace:  "default",
   224  			Name:       "foo",
   225  			Kind:       "bar",
   226  			APIVersion: "production.group.io/versionone",
   227  		},
   228  	}
   229  	incompatibleAPIKindChangeTemplate := clusterv1.LocalObjectTemplate{
   230  		Ref: &corev1.ObjectReference{
   231  			Namespace:  "default",
   232  			Name:       "foo",
   233  			Kind:       "notabar",
   234  			APIVersion: "test.group.io/versionone",
   235  		},
   236  	}
   237  	tests := []struct {
   238  		name    string
   239  		current clusterv1.LocalObjectTemplate
   240  		desired clusterv1.LocalObjectTemplate
   241  		wantErr bool
   242  	}{
   243  		{
   244  			name:    "Allow change to template name",
   245  			current: template,
   246  			desired: compatibleNameChangeTemplate,
   247  			wantErr: false,
   248  		},
   249  		{
   250  			name:    "Allow change to template APIVersion",
   251  			current: template,
   252  			desired: compatibleAPIVersionChangeTemplate,
   253  			wantErr: false,
   254  		},
   255  		{
   256  			name:    "Block change to template API Group",
   257  			current: template,
   258  			desired: incompatibleAPIGroupChangeTemplate,
   259  			wantErr: true,
   260  		},
   261  		{
   262  			name:    "Block change to template namespace",
   263  			current: template,
   264  			desired: incompatibleNamespaceChangeTemplate,
   265  			wantErr: true,
   266  		},
   267  		{
   268  			name:    "Block change to template API Kind",
   269  			current: template,
   270  			desired: incompatibleAPIKindChangeTemplate,
   271  			wantErr: true,
   272  		},
   273  	}
   274  	for _, tt := range tests {
   275  		t.Run(tt.name, func(t *testing.T) {
   276  			g := NewWithT(t)
   277  			allErrs := LocalObjectTemplatesAreCompatible(tt.current, tt.desired, field.NewPath("spec"))
   278  			if tt.wantErr {
   279  				g.Expect(allErrs).ToNot(BeEmpty())
   280  				return
   281  			}
   282  			g.Expect(allErrs).To(BeEmpty())
   283  		})
   284  	}
   285  }
   286  
   287  func TestLocalObjectTemplateIsValid(t *testing.T) {
   288  	namespace := metav1.NamespaceDefault
   289  	pathPrefix := field.NewPath("this", "is", "a", "prefix")
   290  
   291  	validTemplate := &clusterv1.LocalObjectTemplate{
   292  		Ref: &corev1.ObjectReference{
   293  			Namespace:  "default",
   294  			Name:       "valid",
   295  			Kind:       "barTemplate",
   296  			APIVersion: "test.group.io/versionone",
   297  		},
   298  	}
   299  
   300  	nilTemplate := &clusterv1.LocalObjectTemplate{
   301  		Ref: nil,
   302  	}
   303  	emptyNameTemplate := &clusterv1.LocalObjectTemplate{
   304  		Ref: &corev1.ObjectReference{
   305  			Namespace:  "default",
   306  			Name:       "",
   307  			Kind:       "barTemplate",
   308  			APIVersion: "test.group.io/versionone",
   309  		},
   310  	}
   311  	wrongNamespaceTemplate := &clusterv1.LocalObjectTemplate{
   312  		Ref: &corev1.ObjectReference{
   313  			Namespace:  "wrongNamespace",
   314  			Name:       "foo",
   315  			Kind:       "barTemplate",
   316  			APIVersion: "test.group.io/versiontwo",
   317  		},
   318  	}
   319  	notTemplateKindTemplate := &clusterv1.LocalObjectTemplate{
   320  		Ref: &corev1.ObjectReference{
   321  			Namespace:  "default",
   322  			Name:       "foo",
   323  			Kind:       "bar",
   324  			APIVersion: "test.group.io/versionone",
   325  		},
   326  	}
   327  	invalidAPIVersionTemplate := &clusterv1.LocalObjectTemplate{
   328  		Ref: &corev1.ObjectReference{
   329  			Namespace:  "default",
   330  			Name:       "foo",
   331  			Kind:       "barTemplate",
   332  			APIVersion: "this/has/too/many/slashes",
   333  		},
   334  	}
   335  	emptyAPIVersionTemplate := &clusterv1.LocalObjectTemplate{
   336  		Ref: &corev1.ObjectReference{
   337  			Namespace:  "default",
   338  			Name:       "foo",
   339  			Kind:       "barTemplate",
   340  			APIVersion: "",
   341  		},
   342  	}
   343  
   344  	tests := []struct {
   345  		template *clusterv1.LocalObjectTemplate
   346  		name     string
   347  		wantErr  bool
   348  	}{
   349  		{
   350  			name:     "No error with valid Template",
   351  			template: validTemplate,
   352  			wantErr:  false,
   353  		},
   354  
   355  		{
   356  			name:     "Invalid if ref is nil",
   357  			template: nilTemplate,
   358  			wantErr:  true,
   359  		},
   360  		{
   361  			name:     "Invalid if name is empty",
   362  			template: emptyNameTemplate,
   363  			wantErr:  true,
   364  		},
   365  		{
   366  			name:     "Invalid if namespace doesn't match",
   367  			template: wrongNamespaceTemplate,
   368  			wantErr:  true,
   369  		},
   370  		{
   371  			name:     "Invalid if Kind doesn't have Template suffix",
   372  			template: notTemplateKindTemplate,
   373  			wantErr:  true,
   374  		},
   375  		{
   376  			name:     "Invalid if apiVersion is not valid",
   377  			template: invalidAPIVersionTemplate,
   378  			wantErr:  true,
   379  		},
   380  		{
   381  			name:     "Empty apiVersion is not valid",
   382  			template: emptyAPIVersionTemplate,
   383  			wantErr:  true,
   384  		},
   385  	}
   386  	for _, tt := range tests {
   387  		t.Run(tt.name, func(t *testing.T) {
   388  			g := NewWithT(t)
   389  			allErrs := LocalObjectTemplateIsValid(tt.template, namespace, pathPrefix)
   390  			if tt.wantErr {
   391  				g.Expect(allErrs).ToNot(BeEmpty())
   392  				return
   393  			}
   394  			g.Expect(allErrs).To(BeEmpty())
   395  		})
   396  	}
   397  }
   398  
   399  func TestClusterClassesAreCompatible(t *testing.T) {
   400  	ref := &corev1.ObjectReference{
   401  		APIVersion: "group.test.io/foo",
   402  		Kind:       "barTemplate",
   403  		Name:       "baz",
   404  		Namespace:  "default",
   405  	}
   406  	incompatibleRef := &corev1.ObjectReference{
   407  		APIVersion: "group.test.io/foo",
   408  		Kind:       "another-barTemplate",
   409  		Name:       "baz",
   410  		Namespace:  "default",
   411  	}
   412  	compatibleRef := &corev1.ObjectReference{
   413  		APIVersion: "group.test.io/another-foo",
   414  		Kind:       "barTemplate",
   415  		Name:       "another-baz",
   416  		Namespace:  "default",
   417  	}
   418  
   419  	tests := []struct {
   420  		name    string
   421  		current *clusterv1.ClusterClass
   422  		desired *clusterv1.ClusterClass
   423  		wantErr bool
   424  	}{
   425  		{
   426  			name:    "error if current is nil",
   427  			current: nil,
   428  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   429  				WithInfrastructureClusterTemplate(
   430  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   431  				WithControlPlaneTemplate(
   432  					refToUnstructured(ref)).
   433  				WithControlPlaneInfrastructureMachineTemplate(
   434  					refToUnstructured(ref)).
   435  				Build(),
   436  			wantErr: true,
   437  		},
   438  		{
   439  			name: "error if desired is nil",
   440  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   441  				WithInfrastructureClusterTemplate(
   442  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   443  				WithControlPlaneTemplate(
   444  					refToUnstructured(ref)).
   445  				WithControlPlaneInfrastructureMachineTemplate(
   446  					refToUnstructured(ref)).
   447  				Build(),
   448  			desired: nil,
   449  			wantErr: true,
   450  		},
   451  
   452  		{
   453  			name: "pass for compatible clusterClasses",
   454  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   455  				WithInfrastructureClusterTemplate(
   456  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   457  				WithControlPlaneTemplate(
   458  					refToUnstructured(ref)).
   459  				WithControlPlaneInfrastructureMachineTemplate(
   460  					refToUnstructured(ref)).
   461  				WithWorkerMachineDeploymentClasses(
   462  					*builder.MachineDeploymentClass("aa").
   463  						WithInfrastructureTemplate(
   464  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   465  						WithBootstrapTemplate(
   466  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   467  						Build()).
   468  				Build(),
   469  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   470  				WithInfrastructureClusterTemplate(
   471  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   472  				WithControlPlaneTemplate(
   473  					refToUnstructured(compatibleRef)).
   474  				WithControlPlaneInfrastructureMachineTemplate(
   475  					refToUnstructured(compatibleRef)).
   476  				WithWorkerMachineDeploymentClasses(
   477  					*builder.MachineDeploymentClass("aa").
   478  						WithInfrastructureTemplate(
   479  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   480  						WithBootstrapTemplate(
   481  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   482  						Build()).
   483  				Build(),
   484  			wantErr: false,
   485  		},
   486  		{
   487  			name: "error if clusterClass has incompatible ControlPlane ref",
   488  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   489  				WithInfrastructureClusterTemplate(
   490  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   491  				WithControlPlaneTemplate(
   492  					refToUnstructured(ref)).
   493  				WithControlPlaneInfrastructureMachineTemplate(
   494  					refToUnstructured(ref)).
   495  				WithWorkerMachineDeploymentClasses(
   496  					*builder.MachineDeploymentClass("aa").
   497  						WithInfrastructureTemplate(
   498  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   499  						WithBootstrapTemplate(
   500  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   501  						Build()).
   502  				Build(),
   503  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   504  				WithInfrastructureClusterTemplate(
   505  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   506  				WithControlPlaneTemplate(
   507  					refToUnstructured(incompatibleRef)).
   508  				WithControlPlaneInfrastructureMachineTemplate(
   509  					refToUnstructured(ref)).
   510  				WithWorkerMachineDeploymentClasses(
   511  					*builder.MachineDeploymentClass("aa").
   512  						WithInfrastructureTemplate(
   513  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   514  						WithBootstrapTemplate(
   515  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   516  						Build()).
   517  				Build(),
   518  			wantErr: true,
   519  		},
   520  		{
   521  			name: "pass for incompatible ref in MachineDeploymentClass bootstrapTemplate clusterClasses",
   522  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   523  				WithInfrastructureClusterTemplate(
   524  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   525  				WithControlPlaneTemplate(
   526  					refToUnstructured(ref)).
   527  				WithControlPlaneInfrastructureMachineTemplate(
   528  					refToUnstructured(ref)).
   529  				WithWorkerMachineDeploymentClasses(
   530  					*builder.MachineDeploymentClass("aa").
   531  						WithInfrastructureTemplate(
   532  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   533  						WithBootstrapTemplate(
   534  							refToUnstructured(ref)).Build()).
   535  				Build(),
   536  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   537  				WithInfrastructureClusterTemplate(
   538  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   539  				WithControlPlaneTemplate(
   540  					refToUnstructured(ref)).
   541  				WithControlPlaneInfrastructureMachineTemplate(
   542  					refToUnstructured(ref)).
   543  				WithWorkerMachineDeploymentClasses(
   544  					*builder.MachineDeploymentClass("aa").
   545  						WithInfrastructureTemplate(
   546  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   547  						WithBootstrapTemplate(
   548  							refToUnstructured(incompatibleRef)).Build()).
   549  				Build(),
   550  			wantErr: false,
   551  		},
   552  		{
   553  			name: "pass if machineDeploymentClass is removed from ClusterClass",
   554  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   555  				WithInfrastructureClusterTemplate(
   556  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   557  				WithControlPlaneTemplate(
   558  					refToUnstructured(ref)).
   559  				WithControlPlaneInfrastructureMachineTemplate(
   560  					refToUnstructured(ref)).
   561  				WithWorkerMachineDeploymentClasses(
   562  					*builder.MachineDeploymentClass("aa").
   563  						WithInfrastructureTemplate(
   564  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   565  						WithBootstrapTemplate(
   566  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   567  						Build(),
   568  					*builder.MachineDeploymentClass("bb").
   569  						WithInfrastructureTemplate(
   570  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   571  						WithBootstrapTemplate(
   572  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   573  						Build()).
   574  				Build(),
   575  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   576  				WithInfrastructureClusterTemplate(
   577  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   578  				WithControlPlaneTemplate(
   579  					refToUnstructured(ref)).
   580  				WithControlPlaneInfrastructureMachineTemplate(
   581  					refToUnstructured(ref)).
   582  				WithWorkerMachineDeploymentClasses(
   583  					*builder.MachineDeploymentClass("aa").
   584  						WithInfrastructureTemplate(
   585  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   586  						WithBootstrapTemplate(
   587  							refToUnstructured(incompatibleRef)).Build()).
   588  				Build(),
   589  			wantErr: false,
   590  		},
   591  	}
   592  	for _, tt := range tests {
   593  		g := NewWithT(t)
   594  		t.Run(tt.name, func(t *testing.T) {
   595  			allErrs := ClusterClassesAreCompatible(tt.current, tt.desired)
   596  			if tt.wantErr {
   597  				g.Expect(allErrs).ToNot(BeEmpty())
   598  				return
   599  			}
   600  			g.Expect(allErrs).To(BeEmpty())
   601  		})
   602  	}
   603  }
   604  
   605  func TestMachineDeploymentClassesAreCompatible(t *testing.T) {
   606  	ref := &corev1.ObjectReference{
   607  		APIVersion: "group.test.io/foo",
   608  		Kind:       "barTemplate",
   609  		Name:       "baz",
   610  		Namespace:  "default",
   611  	}
   612  	compatibleRef := &corev1.ObjectReference{
   613  		APIVersion: "group.test.io/another-foo",
   614  		Kind:       "barTemplate",
   615  		Name:       "another-baz",
   616  		Namespace:  "default",
   617  	}
   618  	incompatibleRef := &corev1.ObjectReference{
   619  		APIVersion: "group.test.io/foo",
   620  		Kind:       "another-barTemplate",
   621  		Name:       "baz",
   622  		Namespace:  "default",
   623  	}
   624  
   625  	tests := []struct {
   626  		name    string
   627  		current *clusterv1.ClusterClass
   628  		desired *clusterv1.ClusterClass
   629  		wantErr bool
   630  	}{
   631  		{
   632  			name: "pass if machineDeploymentClasses are compatible",
   633  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   634  				WithInfrastructureClusterTemplate(
   635  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   636  				WithControlPlaneTemplate(
   637  					refToUnstructured(ref)).
   638  				WithControlPlaneInfrastructureMachineTemplate(
   639  					refToUnstructured(ref)).
   640  				WithWorkerMachineDeploymentClasses(
   641  					*builder.MachineDeploymentClass("aa").
   642  						WithInfrastructureTemplate(
   643  							refToUnstructured(ref)).
   644  						WithBootstrapTemplate(
   645  							refToUnstructured(ref)).
   646  						Build()).
   647  				Build(),
   648  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   649  				WithInfrastructureClusterTemplate(
   650  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   651  				WithControlPlaneTemplate(
   652  					refToUnstructured(ref)).
   653  				WithControlPlaneInfrastructureMachineTemplate(
   654  					refToUnstructured(ref)).
   655  				WithWorkerMachineDeploymentClasses(
   656  					*builder.MachineDeploymentClass("aa").
   657  						WithInfrastructureTemplate(
   658  							refToUnstructured(compatibleRef)).
   659  						WithBootstrapTemplate(
   660  							refToUnstructured(incompatibleRef)).Build()).
   661  				Build(),
   662  			wantErr: false,
   663  		},
   664  		{
   665  			name: "pass if machineDeploymentClass is removed from ClusterClass",
   666  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   667  				WithInfrastructureClusterTemplate(
   668  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   669  				WithControlPlaneTemplate(
   670  					refToUnstructured(ref)).
   671  				WithControlPlaneInfrastructureMachineTemplate(
   672  					refToUnstructured(ref)).
   673  				WithWorkerMachineDeploymentClasses(
   674  					*builder.MachineDeploymentClass("aa").
   675  						WithInfrastructureTemplate(
   676  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   677  						WithBootstrapTemplate(
   678  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   679  						Build(),
   680  					*builder.MachineDeploymentClass("bb").
   681  						WithInfrastructureTemplate(
   682  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   683  						WithBootstrapTemplate(
   684  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   685  						Build()).
   686  				Build(),
   687  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   688  				WithInfrastructureClusterTemplate(
   689  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   690  				WithControlPlaneTemplate(
   691  					refToUnstructured(ref)).
   692  				WithControlPlaneInfrastructureMachineTemplate(
   693  					refToUnstructured(ref)).
   694  				WithWorkerMachineDeploymentClasses(
   695  					*builder.MachineDeploymentClass("aa").
   696  						WithInfrastructureTemplate(
   697  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   698  						WithBootstrapTemplate(
   699  							refToUnstructured(incompatibleRef)).Build()).
   700  				Build(),
   701  			wantErr: false,
   702  		},
   703  		{
   704  			name: "error if machineDeploymentClass has multiple incompatible references",
   705  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   706  				WithInfrastructureClusterTemplate(
   707  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   708  				WithControlPlaneTemplate(
   709  					refToUnstructured(ref)).
   710  				WithControlPlaneInfrastructureMachineTemplate(
   711  					refToUnstructured(ref)).
   712  				WithWorkerMachineDeploymentClasses(
   713  					*builder.MachineDeploymentClass("aa").
   714  						WithInfrastructureTemplate(
   715  							refToUnstructured(ref)).
   716  						WithBootstrapTemplate(
   717  							refToUnstructured(ref)).
   718  						Build(),
   719  				).
   720  				Build(),
   721  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   722  				WithInfrastructureClusterTemplate(
   723  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   724  				WithControlPlaneTemplate(
   725  					refToUnstructured(incompatibleRef)).
   726  				WithControlPlaneInfrastructureMachineTemplate(
   727  					refToUnstructured(incompatibleRef)).
   728  				WithWorkerMachineDeploymentClasses(
   729  					*builder.MachineDeploymentClass("aa").
   730  						WithInfrastructureTemplate(
   731  							refToUnstructured(incompatibleRef)).
   732  						WithBootstrapTemplate(
   733  							refToUnstructured(compatibleRef)).Build()).
   734  				Build(),
   735  			wantErr: true,
   736  		},
   737  	}
   738  	for _, tt := range tests {
   739  		t.Run(tt.name, func(t *testing.T) {
   740  			g := NewWithT(t)
   741  			allErrs := MachineDeploymentClassesAreCompatible(tt.current, tt.desired)
   742  			if tt.wantErr {
   743  				g.Expect(allErrs).ToNot(BeEmpty())
   744  				return
   745  			}
   746  			g.Expect(allErrs).To(BeEmpty())
   747  		})
   748  	}
   749  }
   750  
   751  func TestMachineDeploymentClassesAreUnique(t *testing.T) {
   752  	tests := []struct {
   753  		name         string
   754  		clusterClass *clusterv1.ClusterClass
   755  		wantErr      bool
   756  	}{
   757  		{
   758  			name: "pass if MachineDeploymentClasses are unique",
   759  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   760  				WithInfrastructureClusterTemplate(
   761  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   762  				WithControlPlaneTemplate(
   763  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
   764  				WithControlPlaneInfrastructureMachineTemplate(
   765  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
   766  				WithWorkerMachineDeploymentClasses(
   767  					*builder.MachineDeploymentClass("aa").
   768  						WithInfrastructureTemplate(
   769  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   770  						WithBootstrapTemplate(
   771  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   772  						Build(),
   773  					*builder.MachineDeploymentClass("bb").
   774  						WithInfrastructureTemplate(
   775  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   776  						WithBootstrapTemplate(
   777  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   778  						Build()).
   779  				Build(),
   780  			wantErr: false,
   781  		},
   782  		{
   783  			name: "fail if MachineDeploymentClasses are duplicated",
   784  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   785  				WithInfrastructureClusterTemplate(
   786  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   787  				WithControlPlaneTemplate(
   788  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
   789  				WithControlPlaneInfrastructureMachineTemplate(
   790  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
   791  				WithWorkerMachineDeploymentClasses(
   792  					*builder.MachineDeploymentClass("aa").
   793  						WithInfrastructureTemplate(
   794  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   795  						WithBootstrapTemplate(
   796  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   797  						Build(),
   798  					*builder.MachineDeploymentClass("aa").
   799  						WithInfrastructureTemplate(
   800  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   801  						WithBootstrapTemplate(
   802  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   803  						Build()).
   804  				Build(),
   805  			wantErr: true,
   806  		},
   807  		{
   808  			name: "fail if multiple MachineDeploymentClasses are identical",
   809  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   810  				WithInfrastructureClusterTemplate(
   811  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   812  				WithControlPlaneTemplate(
   813  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
   814  				WithControlPlaneInfrastructureMachineTemplate(
   815  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
   816  				WithWorkerMachineDeploymentClasses(
   817  					*builder.MachineDeploymentClass("aa").
   818  						WithInfrastructureTemplate(
   819  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   820  						WithBootstrapTemplate(
   821  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   822  						Build(),
   823  					*builder.MachineDeploymentClass("aa").
   824  						WithInfrastructureTemplate(
   825  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   826  						WithBootstrapTemplate(
   827  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   828  						Build(),
   829  					*builder.MachineDeploymentClass("aa").
   830  						WithInfrastructureTemplate(
   831  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   832  						WithBootstrapTemplate(
   833  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   834  						Build(),
   835  					*builder.MachineDeploymentClass("aa").
   836  						WithInfrastructureTemplate(
   837  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   838  						WithBootstrapTemplate(
   839  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   840  						Build(),
   841  				).
   842  				Build(),
   843  			wantErr: true,
   844  		},
   845  	}
   846  	for _, tt := range tests {
   847  		t.Run(tt.name, func(t *testing.T) {
   848  			g := NewWithT(t)
   849  			allErrs := MachineDeploymentClassesAreUnique(tt.clusterClass)
   850  			if tt.wantErr {
   851  				g.Expect(allErrs).ToNot(BeEmpty())
   852  				return
   853  			}
   854  			g.Expect(allErrs).To(BeEmpty())
   855  		})
   856  	}
   857  }
   858  
   859  func TestMachineDeploymentTopologiesAreUniqueAndDefinedInClusterClass(t *testing.T) {
   860  	tests := []struct {
   861  		name         string
   862  		clusterClass *clusterv1.ClusterClass
   863  		cluster      *clusterv1.Cluster
   864  		wantErr      bool
   865  	}{
   866  		{
   867  			name: "fail if MachineDeploymentTopologies name is empty",
   868  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   869  				WithInfrastructureClusterTemplate(
   870  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   871  				WithControlPlaneTemplate(
   872  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
   873  				WithWorkerMachineDeploymentClasses(
   874  					*builder.MachineDeploymentClass("aa").
   875  						WithInfrastructureTemplate(
   876  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   877  						WithBootstrapTemplate(
   878  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   879  						Build()).
   880  				Build(),
   881  			cluster: builder.Cluster("fooboo", "cluster1").
   882  				WithTopology(builder.ClusterTopology().
   883  					WithClass("foo").
   884  					WithVersion("v1.19.1").
   885  					WithMachineDeployment(
   886  						// The name should not be empty.
   887  						builder.MachineDeploymentTopology("").
   888  							WithClass("aa").
   889  							Build()).
   890  					Build()).
   891  				Build(),
   892  			wantErr: true,
   893  		},
   894  		{
   895  			name: "pass if MachineDeploymentTopologies are unique and defined in ClusterClass",
   896  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   897  				WithInfrastructureClusterTemplate(
   898  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   899  				WithControlPlaneTemplate(
   900  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
   901  				WithControlPlaneInfrastructureMachineTemplate(
   902  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
   903  				WithWorkerMachineDeploymentClasses(
   904  					*builder.MachineDeploymentClass("aa").
   905  						WithInfrastructureTemplate(
   906  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   907  						WithBootstrapTemplate(
   908  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   909  						Build()).
   910  				Build(),
   911  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   912  				WithTopology(
   913  					builder.ClusterTopology().
   914  						WithClass("class1").
   915  						WithVersion("v1.22.2").
   916  						WithMachineDeployment(
   917  							builder.MachineDeploymentTopology("workers1").
   918  								WithClass("aa").
   919  								Build()).
   920  						Build()).
   921  				Build(),
   922  			wantErr: false,
   923  		},
   924  		{
   925  			name: "fail if MachineDeploymentTopologies are unique but not defined in ClusterClass",
   926  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   927  				WithInfrastructureClusterTemplate(
   928  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   929  				WithControlPlaneTemplate(
   930  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
   931  				WithControlPlaneInfrastructureMachineTemplate(
   932  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
   933  				WithWorkerMachineDeploymentClasses(
   934  					*builder.MachineDeploymentClass("aa").
   935  						WithInfrastructureTemplate(
   936  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   937  						WithBootstrapTemplate(
   938  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   939  						Build()).
   940  				Build(),
   941  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   942  				WithTopology(
   943  					builder.ClusterTopology().
   944  						WithClass("class1").
   945  						WithVersion("v1.22.2").
   946  						WithMachineDeployment(
   947  							builder.MachineDeploymentTopology("workers1").
   948  								WithClass("bb").
   949  								Build()).
   950  						Build()).
   951  				Build(),
   952  			wantErr: true,
   953  		},
   954  		{
   955  			name: "fail if MachineDeploymentTopologies are not unique but is defined in ClusterClass",
   956  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   957  				WithInfrastructureClusterTemplate(
   958  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   959  				WithControlPlaneTemplate(
   960  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
   961  				WithControlPlaneInfrastructureMachineTemplate(
   962  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
   963  				WithWorkerMachineDeploymentClasses(
   964  					*builder.MachineDeploymentClass("aa").
   965  						WithInfrastructureTemplate(
   966  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   967  						WithBootstrapTemplate(
   968  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   969  						Build()).
   970  				Build(),
   971  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   972  				WithTopology(
   973  					builder.ClusterTopology().
   974  						WithClass("class1").
   975  						WithVersion("v1.22.2").
   976  						WithMachineDeployment(
   977  							builder.MachineDeploymentTopology("workers1").
   978  								WithClass("aa").
   979  								Build()).
   980  						WithMachineDeployment(
   981  							builder.MachineDeploymentTopology("workers1").
   982  								WithClass("aa").
   983  								Build()).
   984  						Build()).
   985  				Build(),
   986  			wantErr: true,
   987  		},
   988  		{
   989  			name: "pass if MachineDeploymentTopologies are unique and share a class that is defined in ClusterClass",
   990  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   991  				WithInfrastructureClusterTemplate(
   992  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   993  				WithControlPlaneTemplate(
   994  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
   995  				WithControlPlaneInfrastructureMachineTemplate(
   996  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
   997  				WithWorkerMachineDeploymentClasses(
   998  					*builder.MachineDeploymentClass("aa").
   999  						WithInfrastructureTemplate(
  1000  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1001  						WithBootstrapTemplate(
  1002  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1003  						Build()).
  1004  				Build(),
  1005  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1006  				WithTopology(
  1007  					builder.ClusterTopology().
  1008  						WithClass("class1").
  1009  						WithVersion("v1.22.2").
  1010  						WithMachineDeployment(
  1011  							builder.MachineDeploymentTopology("workers1").
  1012  								WithClass("aa").
  1013  								Build()).
  1014  						WithMachineDeployment(
  1015  							builder.MachineDeploymentTopology("workers2").
  1016  								WithClass("aa").
  1017  								Build()).
  1018  						Build()).
  1019  				Build(),
  1020  			wantErr: false,
  1021  		},
  1022  		{
  1023  			name: "fail if MachineDeploymentTopology name is longer than 63 characters",
  1024  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1025  				WithInfrastructureClusterTemplate(
  1026  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1027  				WithControlPlaneTemplate(
  1028  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1029  				WithControlPlaneInfrastructureMachineTemplate(
  1030  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1031  				WithWorkerMachineDeploymentClasses(
  1032  					*builder.MachineDeploymentClass("aa").
  1033  						WithInfrastructureTemplate(
  1034  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1035  						WithBootstrapTemplate(
  1036  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1037  						Build()).
  1038  				Build(),
  1039  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1040  				WithTopology(
  1041  					builder.ClusterTopology().
  1042  						WithClass("class1").
  1043  						WithVersion("v1.22.2").
  1044  						WithMachineDeployment(
  1045  							builder.MachineDeploymentTopology("machine-deployment-topology-name-that-has-longerthan63characterlooooooooooooooooooooooongname").
  1046  								WithClass("aa").
  1047  								Build()).
  1048  						Build()).
  1049  				Build(),
  1050  			wantErr: true,
  1051  		},
  1052  	}
  1053  	for _, tt := range tests {
  1054  		t.Run(tt.name, func(t *testing.T) {
  1055  			g := NewWithT(t)
  1056  			allErrs := MachineDeploymentTopologiesAreValidAndDefinedInClusterClass(tt.cluster, tt.clusterClass)
  1057  			if tt.wantErr {
  1058  				g.Expect(allErrs).ToNot(BeEmpty())
  1059  				return
  1060  			}
  1061  			g.Expect(allErrs).To(BeEmpty())
  1062  		})
  1063  	}
  1064  }
  1065  
  1066  func TestClusterClassReferencesAreValid(t *testing.T) {
  1067  	ref := &corev1.ObjectReference{
  1068  		APIVersion: "group.test.io/foo",
  1069  		Kind:       "barTemplate",
  1070  		Name:       "baz",
  1071  		Namespace:  "default",
  1072  	}
  1073  	invalidRef := &corev1.ObjectReference{
  1074  		APIVersion: "group.test.io/foo",
  1075  		Kind:       "another-barTemplate",
  1076  		Name:       "baz",
  1077  		Namespace:  "",
  1078  	}
  1079  
  1080  	tests := []struct {
  1081  		name         string
  1082  		clusterClass *clusterv1.ClusterClass
  1083  		wantErr      bool
  1084  	}{
  1085  		{
  1086  			name: "pass for clusterClass with valid references",
  1087  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1088  				WithInfrastructureClusterTemplate(
  1089  					refToUnstructured(ref)).
  1090  				WithControlPlaneTemplate(
  1091  					refToUnstructured(ref)).
  1092  				WithControlPlaneInfrastructureMachineTemplate(
  1093  					refToUnstructured(ref)).
  1094  				WithWorkerMachineDeploymentClasses(
  1095  					*builder.MachineDeploymentClass("aa").
  1096  						WithInfrastructureTemplate(
  1097  							refToUnstructured(ref)).
  1098  						WithBootstrapTemplate(
  1099  							refToUnstructured(ref)).
  1100  						Build()).
  1101  				Build(),
  1102  			wantErr: false,
  1103  		},
  1104  		{
  1105  			name: "error if clusterClass has multiple invalid refs",
  1106  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1107  				WithInfrastructureClusterTemplate(
  1108  					refToUnstructured(invalidRef)).
  1109  				WithControlPlaneTemplate(
  1110  					refToUnstructured(invalidRef)).
  1111  				WithControlPlaneInfrastructureMachineTemplate(
  1112  					refToUnstructured(invalidRef)).
  1113  				WithWorkerMachineDeploymentClasses(
  1114  					*builder.MachineDeploymentClass("a").
  1115  						WithInfrastructureTemplate(
  1116  							refToUnstructured(invalidRef)).
  1117  						WithBootstrapTemplate(
  1118  							refToUnstructured(invalidRef)).
  1119  						Build(),
  1120  					*builder.MachineDeploymentClass("b").
  1121  						WithInfrastructureTemplate(
  1122  							refToUnstructured(invalidRef)).
  1123  						WithBootstrapTemplate(
  1124  							refToUnstructured(invalidRef)).
  1125  						Build(),
  1126  					*builder.MachineDeploymentClass("c").
  1127  						WithInfrastructureTemplate(
  1128  							refToUnstructured(invalidRef)).
  1129  						WithBootstrapTemplate(
  1130  							refToUnstructured(invalidRef)).
  1131  						Build()).
  1132  				Build(),
  1133  			wantErr: true,
  1134  		},
  1135  
  1136  		{
  1137  			name: "error if clusterClass has invalid ControlPlane ref",
  1138  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1139  				WithInfrastructureClusterTemplate(
  1140  					refToUnstructured(ref)).
  1141  				WithControlPlaneTemplate(
  1142  					refToUnstructured(invalidRef)).
  1143  				WithControlPlaneInfrastructureMachineTemplate(
  1144  					refToUnstructured(ref)).
  1145  				WithWorkerMachineDeploymentClasses(
  1146  					*builder.MachineDeploymentClass("aa").
  1147  						WithInfrastructureTemplate(
  1148  							refToUnstructured(ref)).
  1149  						WithBootstrapTemplate(
  1150  							refToUnstructured(ref)).
  1151  						Build()).
  1152  				Build(),
  1153  			wantErr: true,
  1154  		},
  1155  	}
  1156  	for _, tt := range tests {
  1157  		t.Run(tt.name, func(t *testing.T) {
  1158  			g := NewWithT(t)
  1159  			allErrs := ClusterClassReferencesAreValid(tt.clusterClass)
  1160  			if tt.wantErr {
  1161  				g.Expect(allErrs).ToNot(BeEmpty())
  1162  				return
  1163  			}
  1164  			g.Expect(allErrs).To(BeEmpty())
  1165  		})
  1166  	}
  1167  }
  1168  
  1169  func refToUnstructured(ref *corev1.ObjectReference) *unstructured.Unstructured {
  1170  	gvk := ref.GetObjectKind().GroupVersionKind()
  1171  	output := &unstructured.Unstructured{}
  1172  	output.SetKind(gvk.Kind)
  1173  	output.SetAPIVersion(gvk.GroupVersion().String())
  1174  	output.SetName(ref.Name)
  1175  	output.SetNamespace(ref.Namespace)
  1176  	return output
  1177  }