sigs.k8s.io/cluster-api@v1.7.1/internal/webhooks/patch_validation_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 webhooks
    18  
    19  import (
    20  	"testing"
    21  
    22  	. "github.com/onsi/gomega"
    23  	corev1 "k8s.io/api/core/v1"
    24  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/util/validation/field"
    27  	utilfeature "k8s.io/component-base/featuregate/testing"
    28  	"k8s.io/utils/ptr"
    29  
    30  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    31  	"sigs.k8s.io/cluster-api/feature"
    32  	"sigs.k8s.io/cluster-api/internal/test/builder"
    33  )
    34  
    35  func TestValidatePatches(t *testing.T) {
    36  	tests := []struct {
    37  		name         string
    38  		clusterClass clusterv1.ClusterClass
    39  		runtimeSDK   bool
    40  		wantErr      bool
    41  	}{
    42  		{
    43  			name: "pass multiple patches that are correctly formatted",
    44  			clusterClass: clusterv1.ClusterClass{
    45  				Spec: clusterv1.ClusterClassSpec{
    46  					ControlPlane: clusterv1.ControlPlaneClass{
    47  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
    48  							Ref: &corev1.ObjectReference{
    49  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
    50  								Kind:       "ControlPlaneTemplate",
    51  							},
    52  						},
    53  					},
    54  
    55  					Patches: []clusterv1.ClusterClassPatch{
    56  						{
    57  							Name: "patch1",
    58  							Definitions: []clusterv1.PatchDefinition{
    59  								{
    60  									Selector: clusterv1.PatchSelector{
    61  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
    62  										Kind:       "ControlPlaneTemplate",
    63  										MatchResources: clusterv1.PatchSelectorMatch{
    64  											ControlPlane: true,
    65  										},
    66  									},
    67  									JSONPatches: []clusterv1.JSONPatch{
    68  										{
    69  											Op:   "add",
    70  											Path: "/spec/template/spec/variableSetting/variableValue1",
    71  											ValueFrom: &clusterv1.JSONPatchValue{
    72  												Variable: ptr.To("variableName1"),
    73  											},
    74  										},
    75  									},
    76  								},
    77  							},
    78  						},
    79  						{
    80  							Name: "patch2",
    81  							Definitions: []clusterv1.PatchDefinition{
    82  								{
    83  									Selector: clusterv1.PatchSelector{
    84  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
    85  										Kind:       "ControlPlaneTemplate",
    86  										MatchResources: clusterv1.PatchSelectorMatch{
    87  											ControlPlane: true,
    88  										},
    89  									},
    90  									JSONPatches: []clusterv1.JSONPatch{
    91  										{
    92  											Op:   "add",
    93  											Path: "/spec/template/spec/variableSetting/variableValue2",
    94  											ValueFrom: &clusterv1.JSONPatchValue{
    95  												Variable: ptr.To("variableName2"),
    96  											},
    97  										},
    98  									},
    99  								},
   100  							},
   101  						},
   102  					},
   103  					Variables: []clusterv1.ClusterClassVariable{
   104  						{
   105  							Name:     "variableName1",
   106  							Required: true,
   107  							Schema: clusterv1.VariableSchema{
   108  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   109  									Type: "string",
   110  								},
   111  							},
   112  						},
   113  						{
   114  							Name:     "variableName2",
   115  							Required: true,
   116  							Schema: clusterv1.VariableSchema{
   117  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   118  									Type: "string",
   119  								},
   120  							},
   121  						},
   122  					},
   123  				},
   124  			},
   125  			wantErr: false,
   126  		},
   127  
   128  		// Patch name validation
   129  		{
   130  			name: "error if patch name is empty",
   131  			clusterClass: clusterv1.ClusterClass{
   132  				Spec: clusterv1.ClusterClassSpec{
   133  					ControlPlane: clusterv1.ControlPlaneClass{
   134  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   135  							Ref: &corev1.ObjectReference{
   136  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   137  								Kind:       "ControlPlaneTemplate",
   138  							},
   139  						},
   140  					},
   141  
   142  					Patches: []clusterv1.ClusterClassPatch{
   143  
   144  						{
   145  							Name: "",
   146  							Definitions: []clusterv1.PatchDefinition{
   147  								{
   148  									Selector: clusterv1.PatchSelector{
   149  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   150  										Kind:       "ControlPlaneTemplate",
   151  										MatchResources: clusterv1.PatchSelectorMatch{
   152  											ControlPlane: true,
   153  										},
   154  									},
   155  									JSONPatches: []clusterv1.JSONPatch{
   156  										{
   157  											Op:   "add",
   158  											Path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/controllerManager/extraArgs/cluster-name",
   159  											ValueFrom: &clusterv1.JSONPatchValue{
   160  												Variable: ptr.To("variableName"),
   161  											},
   162  										},
   163  									},
   164  								},
   165  							},
   166  						},
   167  					},
   168  					Variables: []clusterv1.ClusterClassVariable{
   169  						{
   170  							Name:     "variableName",
   171  							Required: true,
   172  							Schema: clusterv1.VariableSchema{
   173  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   174  									Type: "string",
   175  								},
   176  							},
   177  						},
   178  					},
   179  				},
   180  			},
   181  			wantErr: true,
   182  		},
   183  		{
   184  			name: "error if patches name is not unique",
   185  			clusterClass: clusterv1.ClusterClass{
   186  				Spec: clusterv1.ClusterClassSpec{
   187  					ControlPlane: clusterv1.ControlPlaneClass{
   188  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   189  							Ref: &corev1.ObjectReference{
   190  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   191  								Kind:       "ControlPlaneTemplate",
   192  							},
   193  						},
   194  					},
   195  
   196  					Patches: []clusterv1.ClusterClassPatch{
   197  
   198  						{
   199  							Name: "patch1",
   200  							Definitions: []clusterv1.PatchDefinition{
   201  								{
   202  									Selector: clusterv1.PatchSelector{
   203  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   204  										Kind:       "ControlPlaneTemplate",
   205  										MatchResources: clusterv1.PatchSelectorMatch{
   206  											ControlPlane: true,
   207  										},
   208  									},
   209  									JSONPatches: []clusterv1.JSONPatch{
   210  										{
   211  											Op:   "add",
   212  											Path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/controllerManager/extraArgs/cluster-name",
   213  											ValueFrom: &clusterv1.JSONPatchValue{
   214  												Variable: ptr.To("variableName1"),
   215  											},
   216  										},
   217  									},
   218  								},
   219  							},
   220  						},
   221  						{
   222  							Name: "patch1",
   223  							Definitions: []clusterv1.PatchDefinition{
   224  								{
   225  									Selector: clusterv1.PatchSelector{
   226  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   227  										Kind:       "ControlPlaneTemplate",
   228  										MatchResources: clusterv1.PatchSelectorMatch{
   229  											ControlPlane: true,
   230  										},
   231  									},
   232  									JSONPatches: []clusterv1.JSONPatch{
   233  										{
   234  											Op:   "add",
   235  											Path: "/spec/template/spec/variableSetting/variableValue",
   236  											ValueFrom: &clusterv1.JSONPatchValue{
   237  												Variable: ptr.To("variableName2"),
   238  											},
   239  										},
   240  									},
   241  								},
   242  							},
   243  						},
   244  					},
   245  					Variables: []clusterv1.ClusterClassVariable{
   246  						{
   247  							Name:     "variableName1",
   248  							Required: true,
   249  							Schema: clusterv1.VariableSchema{
   250  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   251  									Type: "string",
   252  								},
   253  							},
   254  						},
   255  						{
   256  							Name:     "variableName2",
   257  							Required: true,
   258  							Schema: clusterv1.VariableSchema{
   259  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   260  									Type: "string",
   261  								},
   262  							},
   263  						},
   264  					},
   265  				},
   266  			},
   267  			wantErr: true,
   268  		},
   269  
   270  		// enabledIf validation
   271  		{
   272  			name: "pass if enabledIf is a valid Go template",
   273  			clusterClass: clusterv1.ClusterClass{
   274  				Spec: clusterv1.ClusterClassSpec{
   275  					ControlPlane: clusterv1.ControlPlaneClass{
   276  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   277  							Ref: &corev1.ObjectReference{
   278  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   279  								Kind:       "ControlPlaneTemplate",
   280  							},
   281  						},
   282  					},
   283  					Patches: []clusterv1.ClusterClassPatch{
   284  						{
   285  							Name:        "patch1",
   286  							EnabledIf:   ptr.To(`template {{ .variableB }}`),
   287  							Definitions: []clusterv1.PatchDefinition{},
   288  						},
   289  					},
   290  				},
   291  			},
   292  			wantErr: false,
   293  		},
   294  		{
   295  			name: "error if enabledIf is an invalid Go template",
   296  			clusterClass: clusterv1.ClusterClass{
   297  				Spec: clusterv1.ClusterClassSpec{
   298  					ControlPlane: clusterv1.ControlPlaneClass{
   299  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   300  							Ref: &corev1.ObjectReference{
   301  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   302  								Kind:       "ControlPlaneTemplate",
   303  							},
   304  						},
   305  					},
   306  					Patches: []clusterv1.ClusterClassPatch{
   307  						{
   308  							Name:      "patch1",
   309  							EnabledIf: ptr.To(`template {{{{{{{{ .variableB }}`),
   310  						},
   311  					},
   312  				},
   313  			},
   314  			wantErr: true,
   315  		},
   316  		// Patch "op" (operation) validation
   317  		{
   318  			name: "error if patch op is not \"add\" \"remove\" or \"replace\"",
   319  			clusterClass: clusterv1.ClusterClass{
   320  				Spec: clusterv1.ClusterClassSpec{
   321  					ControlPlane: clusterv1.ControlPlaneClass{
   322  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   323  							Ref: &corev1.ObjectReference{
   324  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   325  								Kind:       "ControlPlaneTemplate",
   326  							},
   327  						},
   328  					},
   329  
   330  					Patches: []clusterv1.ClusterClassPatch{
   331  
   332  						{
   333  							Name: "patch1",
   334  							Definitions: []clusterv1.PatchDefinition{
   335  								{
   336  									Selector: clusterv1.PatchSelector{
   337  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   338  										Kind:       "ControlPlaneTemplate",
   339  										MatchResources: clusterv1.PatchSelectorMatch{
   340  											ControlPlane: true,
   341  										},
   342  									},
   343  									JSONPatches: []clusterv1.JSONPatch{
   344  										{
   345  											// OP is set to an unrecognized value here.
   346  											Op:   "drop",
   347  											Path: "/spec/template/spec/variableSetting/variableValue2",
   348  										},
   349  									},
   350  								},
   351  							},
   352  						},
   353  					},
   354  				},
   355  			},
   356  			wantErr: true,
   357  		},
   358  
   359  		// Patch path validation
   360  		{
   361  			name: "error if jsonPath does not begin with \"/spec/\"",
   362  			clusterClass: clusterv1.ClusterClass{
   363  				Spec: clusterv1.ClusterClassSpec{
   364  					ControlPlane: clusterv1.ControlPlaneClass{
   365  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   366  							Ref: &corev1.ObjectReference{
   367  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   368  								Kind:       "ControlPlaneTemplate",
   369  							},
   370  						},
   371  					},
   372  
   373  					Patches: []clusterv1.ClusterClassPatch{
   374  
   375  						{
   376  							Name: "patch1",
   377  							Definitions: []clusterv1.PatchDefinition{
   378  								{
   379  									Selector: clusterv1.PatchSelector{
   380  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   381  										Kind:       "ControlPlaneTemplate",
   382  										MatchResources: clusterv1.PatchSelectorMatch{
   383  											ControlPlane: true,
   384  										},
   385  									},
   386  									JSONPatches: []clusterv1.JSONPatch{
   387  										{
   388  											Op: "remove",
   389  											// Path is set to status.
   390  											Path: "/status/template/spec/variableSetting/variableValue2",
   391  										},
   392  									},
   393  								},
   394  							},
   395  						},
   396  					},
   397  				},
   398  			},
   399  			wantErr: true,
   400  		},
   401  		{
   402  			name: "pass if jsonPatch path uses a valid index for add i.e. 0",
   403  			clusterClass: clusterv1.ClusterClass{
   404  				Spec: clusterv1.ClusterClassSpec{
   405  					ControlPlane: clusterv1.ControlPlaneClass{
   406  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   407  							Ref: &corev1.ObjectReference{
   408  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   409  								Kind:       "ControlPlaneTemplate",
   410  							},
   411  						},
   412  					},
   413  
   414  					Patches: []clusterv1.ClusterClassPatch{
   415  						{
   416  							Name: "patch1",
   417  							Definitions: []clusterv1.PatchDefinition{
   418  								{
   419  									Selector: clusterv1.PatchSelector{
   420  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   421  										Kind:       "ControlPlaneTemplate",
   422  										MatchResources: clusterv1.PatchSelectorMatch{
   423  											ControlPlane: true,
   424  										},
   425  									},
   426  									JSONPatches: []clusterv1.JSONPatch{
   427  										{
   428  											Op:   "add",
   429  											Path: "/spec/template/0/",
   430  											ValueFrom: &clusterv1.JSONPatchValue{
   431  												Variable: ptr.To("variableName"),
   432  											},
   433  										},
   434  									},
   435  								},
   436  							},
   437  						},
   438  					},
   439  					Variables: []clusterv1.ClusterClassVariable{
   440  						{
   441  							Name:     "variableName",
   442  							Required: true,
   443  							Schema: clusterv1.VariableSchema{
   444  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   445  									Type: "string",
   446  								},
   447  							},
   448  						},
   449  					},
   450  				},
   451  			},
   452  		},
   453  		{
   454  			name: "error if jsonPatch path uses an invalid index for add i.e. a number greater than 0.",
   455  			clusterClass: clusterv1.ClusterClass{
   456  				Spec: clusterv1.ClusterClassSpec{
   457  					ControlPlane: clusterv1.ControlPlaneClass{
   458  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   459  							Ref: &corev1.ObjectReference{
   460  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   461  								Kind:       "ControlPlaneTemplate",
   462  							},
   463  						},
   464  					},
   465  
   466  					Patches: []clusterv1.ClusterClassPatch{
   467  
   468  						{
   469  							Name: "patch1",
   470  							Definitions: []clusterv1.PatchDefinition{
   471  								{
   472  									Selector: clusterv1.PatchSelector{
   473  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   474  										Kind:       "ControlPlaneTemplate",
   475  										MatchResources: clusterv1.PatchSelectorMatch{
   476  											ControlPlane: true,
   477  										},
   478  									},
   479  									JSONPatches: []clusterv1.JSONPatch{
   480  										{
   481  											Op:   "add",
   482  											Path: "/spec/template/1/",
   483  											ValueFrom: &clusterv1.JSONPatchValue{
   484  												Variable: ptr.To("variableName"),
   485  											},
   486  										},
   487  									},
   488  								},
   489  							},
   490  						},
   491  					},
   492  					Variables: []clusterv1.ClusterClassVariable{
   493  						{
   494  							Name:     "variableName",
   495  							Required: true,
   496  							Schema: clusterv1.VariableSchema{
   497  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   498  									Type: "string",
   499  								},
   500  							},
   501  						},
   502  					},
   503  				},
   504  			},
   505  			wantErr: true,
   506  		},
   507  		{
   508  			name: "error if jsonPatch path uses an invalid index for add i.e. 01",
   509  			clusterClass: clusterv1.ClusterClass{
   510  				Spec: clusterv1.ClusterClassSpec{
   511  					ControlPlane: clusterv1.ControlPlaneClass{
   512  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   513  							Ref: &corev1.ObjectReference{
   514  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   515  								Kind:       "ControlPlaneTemplate",
   516  							},
   517  						},
   518  					},
   519  
   520  					Patches: []clusterv1.ClusterClassPatch{
   521  
   522  						{
   523  							Name: "patch1",
   524  							Definitions: []clusterv1.PatchDefinition{
   525  								{
   526  									Selector: clusterv1.PatchSelector{
   527  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   528  										Kind:       "ControlPlaneTemplate",
   529  										MatchResources: clusterv1.PatchSelectorMatch{
   530  											ControlPlane: true,
   531  										},
   532  									},
   533  									JSONPatches: []clusterv1.JSONPatch{
   534  										{
   535  											Op:   "add",
   536  											Path: "/spec/template/01/",
   537  											ValueFrom: &clusterv1.JSONPatchValue{
   538  												Variable: ptr.To("variableName"),
   539  											},
   540  										},
   541  									},
   542  								},
   543  							},
   544  						},
   545  					},
   546  					Variables: []clusterv1.ClusterClassVariable{
   547  						{
   548  							Name:     "variableName",
   549  							Required: true,
   550  							Schema: clusterv1.VariableSchema{
   551  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   552  									Type: "string",
   553  								},
   554  							},
   555  						},
   556  					},
   557  				},
   558  			},
   559  			wantErr: true,
   560  		},
   561  		{
   562  			name: "error if jsonPatch path uses any index for remove i.e. 0 or -.",
   563  			clusterClass: clusterv1.ClusterClass{
   564  				Spec: clusterv1.ClusterClassSpec{
   565  					ControlPlane: clusterv1.ControlPlaneClass{
   566  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   567  							Ref: &corev1.ObjectReference{
   568  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   569  								Kind:       "ControlPlaneTemplate",
   570  							},
   571  						},
   572  					},
   573  
   574  					Patches: []clusterv1.ClusterClassPatch{
   575  
   576  						{
   577  							Name: "patch1",
   578  							Definitions: []clusterv1.PatchDefinition{
   579  								{
   580  									Selector: clusterv1.PatchSelector{
   581  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   582  										Kind:       "ControlPlaneTemplate",
   583  										MatchResources: clusterv1.PatchSelectorMatch{
   584  											ControlPlane: true,
   585  										},
   586  									},
   587  									JSONPatches: []clusterv1.JSONPatch{
   588  										{
   589  											Op:   "remove",
   590  											Path: "/spec/template/0/",
   591  											ValueFrom: &clusterv1.JSONPatchValue{
   592  												Variable: ptr.To("variableName"),
   593  											},
   594  										},
   595  									},
   596  								},
   597  							},
   598  						},
   599  					},
   600  					Variables: []clusterv1.ClusterClassVariable{
   601  						{
   602  							Name:     "variableName",
   603  							Required: true,
   604  							Schema: clusterv1.VariableSchema{
   605  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   606  									Type: "string",
   607  								},
   608  							},
   609  						},
   610  					},
   611  				},
   612  			},
   613  			wantErr: true,
   614  		},
   615  		{
   616  			name: "error if jsonPatch path uses any index for replace i.e. 0",
   617  			clusterClass: clusterv1.ClusterClass{
   618  				Spec: clusterv1.ClusterClassSpec{
   619  					ControlPlane: clusterv1.ControlPlaneClass{
   620  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   621  							Ref: &corev1.ObjectReference{
   622  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   623  								Kind:       "ControlPlaneTemplate",
   624  							},
   625  						},
   626  					},
   627  
   628  					Patches: []clusterv1.ClusterClassPatch{
   629  
   630  						{
   631  							Name: "patch1",
   632  							Definitions: []clusterv1.PatchDefinition{
   633  								{
   634  									Selector: clusterv1.PatchSelector{
   635  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   636  										Kind:       "ControlPlaneTemplate",
   637  										MatchResources: clusterv1.PatchSelectorMatch{
   638  											ControlPlane: true,
   639  										},
   640  									},
   641  									JSONPatches: []clusterv1.JSONPatch{
   642  										{
   643  											Op:   "replace",
   644  											Path: "/spec/template/0/",
   645  											ValueFrom: &clusterv1.JSONPatchValue{
   646  												Variable: ptr.To("variableName"),
   647  											},
   648  										},
   649  									},
   650  								},
   651  							},
   652  						},
   653  					},
   654  					Variables: []clusterv1.ClusterClassVariable{
   655  						{
   656  							Name:     "variableName",
   657  							Required: true,
   658  							Schema: clusterv1.VariableSchema{
   659  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   660  									Type: "string",
   661  								},
   662  							},
   663  						},
   664  					},
   665  				},
   666  			},
   667  			wantErr: true,
   668  		},
   669  
   670  		// Patch Value/ValueFrom validation
   671  		{
   672  			name: "error if jsonPatch has neither Value nor ValueFrom",
   673  			clusterClass: clusterv1.ClusterClass{
   674  				Spec: clusterv1.ClusterClassSpec{
   675  					ControlPlane: clusterv1.ControlPlaneClass{
   676  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   677  							Ref: &corev1.ObjectReference{
   678  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   679  								Kind:       "ControlPlaneTemplate",
   680  							},
   681  						},
   682  					},
   683  
   684  					Patches: []clusterv1.ClusterClassPatch{
   685  
   686  						{
   687  							Name: "patch1",
   688  							Definitions: []clusterv1.PatchDefinition{
   689  								{
   690  									Selector: clusterv1.PatchSelector{
   691  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   692  										Kind:       "ControlPlaneTemplate",
   693  										MatchResources: clusterv1.PatchSelectorMatch{
   694  											ControlPlane: true,
   695  										},
   696  									},
   697  									JSONPatches: []clusterv1.JSONPatch{
   698  										{
   699  											Op:   "add",
   700  											Path: "/spec/template/spec/",
   701  											// Value and ValueFrom not defined.
   702  										},
   703  									},
   704  								},
   705  							},
   706  						},
   707  					},
   708  				},
   709  			},
   710  			wantErr: true,
   711  		},
   712  		{
   713  			name: "error if jsonPatch has both Value and ValueFrom",
   714  			clusterClass: clusterv1.ClusterClass{
   715  				Spec: clusterv1.ClusterClassSpec{
   716  					ControlPlane: clusterv1.ControlPlaneClass{
   717  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   718  							Ref: &corev1.ObjectReference{
   719  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   720  								Kind:       "ControlPlaneTemplate",
   721  							},
   722  						},
   723  					},
   724  					Patches: []clusterv1.ClusterClassPatch{
   725  
   726  						{
   727  							Name: "patch1",
   728  							Definitions: []clusterv1.PatchDefinition{
   729  								{
   730  									Selector: clusterv1.PatchSelector{
   731  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   732  										Kind:       "ControlPlaneTemplate",
   733  										MatchResources: clusterv1.PatchSelectorMatch{
   734  											ControlPlane: true,
   735  										},
   736  									},
   737  									JSONPatches: []clusterv1.JSONPatch{
   738  										{
   739  											Op:   "add",
   740  											Path: "/spec/template/spec/",
   741  											ValueFrom: &clusterv1.JSONPatchValue{
   742  												Variable: ptr.To("variableName"),
   743  											},
   744  											Value: &apiextensionsv1.JSON{Raw: []byte("1")},
   745  										},
   746  									},
   747  								},
   748  							},
   749  						},
   750  					},
   751  					Variables: []clusterv1.ClusterClassVariable{
   752  						{
   753  							Name:     "variableName",
   754  							Required: true,
   755  							Schema: clusterv1.VariableSchema{
   756  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   757  									Type: "string",
   758  								},
   759  							},
   760  						},
   761  					},
   762  				},
   763  			},
   764  			wantErr: true,
   765  		},
   766  
   767  		// Patch value validation
   768  		{
   769  			name: "pass if jsonPatch value is valid json literal",
   770  			clusterClass: clusterv1.ClusterClass{
   771  				Spec: clusterv1.ClusterClassSpec{
   772  					ControlPlane: clusterv1.ControlPlaneClass{
   773  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   774  							Ref: &corev1.ObjectReference{
   775  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   776  								Kind:       "ControlPlaneTemplate",
   777  							},
   778  						},
   779  					},
   780  					Patches: []clusterv1.ClusterClassPatch{
   781  
   782  						{
   783  							Name: "patch1",
   784  							Definitions: []clusterv1.PatchDefinition{
   785  								{
   786  									Selector: clusterv1.PatchSelector{
   787  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   788  										Kind:       "ControlPlaneTemplate",
   789  										MatchResources: clusterv1.PatchSelectorMatch{
   790  											ControlPlane: true,
   791  										},
   792  									},
   793  									JSONPatches: []clusterv1.JSONPatch{
   794  										{
   795  											Op:    "add",
   796  											Path:  "/spec/template/spec/",
   797  											Value: &apiextensionsv1.JSON{Raw: []byte(`"stringValue"`)},
   798  										},
   799  									},
   800  								},
   801  							},
   802  						},
   803  					},
   804  				},
   805  			},
   806  			wantErr: false,
   807  		},
   808  		{
   809  			name: "pass if jsonPatch value is valid json",
   810  			clusterClass: clusterv1.ClusterClass{
   811  				Spec: clusterv1.ClusterClassSpec{
   812  					ControlPlane: clusterv1.ControlPlaneClass{
   813  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   814  							Ref: &corev1.ObjectReference{
   815  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   816  								Kind:       "ControlPlaneTemplate",
   817  							},
   818  						},
   819  					},
   820  					Patches: []clusterv1.ClusterClassPatch{
   821  
   822  						{
   823  							Name: "patch1",
   824  							Definitions: []clusterv1.PatchDefinition{
   825  								{
   826  									Selector: clusterv1.PatchSelector{
   827  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   828  										Kind:       "ControlPlaneTemplate",
   829  										MatchResources: clusterv1.PatchSelectorMatch{
   830  											ControlPlane: true,
   831  										},
   832  									},
   833  									JSONPatches: []clusterv1.JSONPatch{
   834  										{
   835  											Op:   "add",
   836  											Path: "/spec/template/spec/",
   837  											Value: &apiextensionsv1.JSON{Raw: []byte(
   838  												"{\"id\": \"file\"" +
   839  													"," +
   840  													"\"value\": \"File\"}")},
   841  										},
   842  									},
   843  								},
   844  							},
   845  						},
   846  					},
   847  				},
   848  			},
   849  			wantErr: false,
   850  		},
   851  		{
   852  			name: "pass if jsonPatch value is nil",
   853  			clusterClass: clusterv1.ClusterClass{
   854  				Spec: clusterv1.ClusterClassSpec{
   855  					ControlPlane: clusterv1.ControlPlaneClass{
   856  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   857  							Ref: &corev1.ObjectReference{
   858  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   859  								Kind:       "ControlPlaneTemplate",
   860  							},
   861  						},
   862  					},
   863  					Patches: []clusterv1.ClusterClassPatch{
   864  
   865  						{
   866  							Name: "patch1",
   867  							Definitions: []clusterv1.PatchDefinition{
   868  								{
   869  									Selector: clusterv1.PatchSelector{
   870  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   871  										Kind:       "ControlPlaneTemplate",
   872  										MatchResources: clusterv1.PatchSelectorMatch{
   873  											ControlPlane: true,
   874  										},
   875  									},
   876  									JSONPatches: []clusterv1.JSONPatch{
   877  										{
   878  											Op:   "add",
   879  											Path: "/spec/template/spec/",
   880  											Value: &apiextensionsv1.JSON{
   881  												Raw: nil,
   882  											},
   883  										},
   884  									},
   885  								},
   886  							},
   887  						},
   888  					},
   889  				},
   890  			},
   891  			wantErr: false,
   892  		},
   893  		{
   894  			name: "error if jsonPatch value is invalid json",
   895  			clusterClass: clusterv1.ClusterClass{
   896  				Spec: clusterv1.ClusterClassSpec{
   897  					ControlPlane: clusterv1.ControlPlaneClass{
   898  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   899  							Ref: &corev1.ObjectReference{
   900  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   901  								Kind:       "ControlPlaneTemplate",
   902  							},
   903  						},
   904  					},
   905  
   906  					Patches: []clusterv1.ClusterClassPatch{
   907  
   908  						{
   909  							Name: "patch1",
   910  							Definitions: []clusterv1.PatchDefinition{
   911  								{
   912  									Selector: clusterv1.PatchSelector{
   913  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   914  										Kind:       "ControlPlaneTemplate",
   915  										MatchResources: clusterv1.PatchSelectorMatch{
   916  											ControlPlane: true,
   917  										},
   918  									},
   919  									JSONPatches: []clusterv1.JSONPatch{
   920  										{
   921  											Op:   "add",
   922  											Path: "/spec/template/spec/",
   923  											Value: &apiextensionsv1.JSON{Raw: []byte(
   924  												"{\"id\": \"file\"" +
   925  													// missing comma here +
   926  													"\"value\": \"File\"}")},
   927  										},
   928  									},
   929  								},
   930  							},
   931  						},
   932  					},
   933  				},
   934  			},
   935  			wantErr: true,
   936  		},
   937  
   938  		// Patch valueFrom validation
   939  		{
   940  			name: "error if jsonPatch defines neither ValueFrom.Template nor ValueFrom.Variable",
   941  			clusterClass: clusterv1.ClusterClass{
   942  				Spec: clusterv1.ClusterClassSpec{
   943  					ControlPlane: clusterv1.ControlPlaneClass{
   944  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   945  							Ref: &corev1.ObjectReference{
   946  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   947  								Kind:       "ControlPlaneTemplate",
   948  							},
   949  						},
   950  					},
   951  					Patches: []clusterv1.ClusterClassPatch{
   952  						{
   953  							Name: "patch1",
   954  							Definitions: []clusterv1.PatchDefinition{
   955  								{
   956  									Selector: clusterv1.PatchSelector{
   957  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   958  										Kind:       "ControlPlaneTemplate",
   959  										MatchResources: clusterv1.PatchSelectorMatch{
   960  											ControlPlane: true,
   961  										},
   962  									},
   963  									JSONPatches: []clusterv1.JSONPatch{
   964  										{
   965  											Op:        "add",
   966  											Path:      "/spec/template/spec/",
   967  											ValueFrom: &clusterv1.JSONPatchValue{},
   968  										},
   969  									},
   970  								},
   971  							},
   972  						},
   973  					},
   974  				},
   975  			},
   976  			wantErr: true,
   977  		},
   978  		{
   979  			name: "error if jsonPatch has both ValueFrom.Template and ValueFrom.Variable",
   980  			clusterClass: clusterv1.ClusterClass{
   981  				Spec: clusterv1.ClusterClassSpec{
   982  					ControlPlane: clusterv1.ControlPlaneClass{
   983  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
   984  							Ref: &corev1.ObjectReference{
   985  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   986  								Kind:       "ControlPlaneTemplate",
   987  							},
   988  						},
   989  					},
   990  					Patches: []clusterv1.ClusterClassPatch{
   991  						{
   992  							Name: "patch1",
   993  							Definitions: []clusterv1.PatchDefinition{
   994  								{
   995  									Selector: clusterv1.PatchSelector{
   996  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
   997  										Kind:       "ControlPlaneTemplate",
   998  										MatchResources: clusterv1.PatchSelectorMatch{
   999  											ControlPlane: true,
  1000  										},
  1001  									},
  1002  									JSONPatches: []clusterv1.JSONPatch{
  1003  										{
  1004  											Op:   "add",
  1005  											Path: "/spec/template/spec/",
  1006  											ValueFrom: &clusterv1.JSONPatchValue{
  1007  												Variable: ptr.To("variableName"),
  1008  												Template: ptr.To(`template {{ .variableB }}`),
  1009  											},
  1010  										},
  1011  									},
  1012  								},
  1013  							},
  1014  						},
  1015  					},
  1016  					Variables: []clusterv1.ClusterClassVariable{
  1017  						{
  1018  							Name:     "variableName",
  1019  							Required: true,
  1020  							Schema: clusterv1.VariableSchema{
  1021  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
  1022  									Type: "string",
  1023  								},
  1024  							},
  1025  						},
  1026  					},
  1027  				},
  1028  			},
  1029  			wantErr: true,
  1030  		},
  1031  
  1032  		// Patch valueFrom.Template validation
  1033  		{
  1034  			name: "pass if jsonPatch defines a valid ValueFrom.Template",
  1035  			clusterClass: clusterv1.ClusterClass{
  1036  				Spec: clusterv1.ClusterClassSpec{
  1037  					ControlPlane: clusterv1.ControlPlaneClass{
  1038  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
  1039  							Ref: &corev1.ObjectReference{
  1040  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1041  								Kind:       "ControlPlaneTemplate",
  1042  							},
  1043  						},
  1044  					},
  1045  					Patches: []clusterv1.ClusterClassPatch{
  1046  						{
  1047  							Name: "patch1",
  1048  							Definitions: []clusterv1.PatchDefinition{
  1049  								{
  1050  									Selector: clusterv1.PatchSelector{
  1051  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1052  										Kind:       "ControlPlaneTemplate",
  1053  										MatchResources: clusterv1.PatchSelectorMatch{
  1054  											ControlPlane: true,
  1055  										},
  1056  									},
  1057  									JSONPatches: []clusterv1.JSONPatch{
  1058  										{
  1059  											Op:   "add",
  1060  											Path: "/spec/template/spec/",
  1061  											ValueFrom: &clusterv1.JSONPatchValue{
  1062  												Template: ptr.To(`template {{ .variableB }}`),
  1063  											},
  1064  										},
  1065  									},
  1066  								},
  1067  							},
  1068  						},
  1069  					},
  1070  					Variables: []clusterv1.ClusterClassVariable{
  1071  						{
  1072  							Name:     "variableName",
  1073  							Required: true,
  1074  							Schema: clusterv1.VariableSchema{
  1075  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
  1076  									Type: "string",
  1077  								},
  1078  							},
  1079  						},
  1080  					},
  1081  				},
  1082  			},
  1083  			wantErr: false,
  1084  		},
  1085  		{
  1086  			name: "error if jsonPatch defines an invalid ValueFrom.Template",
  1087  			clusterClass: clusterv1.ClusterClass{
  1088  				Spec: clusterv1.ClusterClassSpec{
  1089  					ControlPlane: clusterv1.ControlPlaneClass{
  1090  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
  1091  							Ref: &corev1.ObjectReference{
  1092  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1093  								Kind:       "ControlPlaneTemplate",
  1094  							},
  1095  						},
  1096  					},
  1097  					Patches: []clusterv1.ClusterClassPatch{
  1098  						{
  1099  							Name: "patch1",
  1100  							Definitions: []clusterv1.PatchDefinition{
  1101  								{
  1102  									Selector: clusterv1.PatchSelector{
  1103  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1104  										Kind:       "ControlPlaneTemplate",
  1105  										MatchResources: clusterv1.PatchSelectorMatch{
  1106  											ControlPlane: true,
  1107  										},
  1108  									},
  1109  									JSONPatches: []clusterv1.JSONPatch{
  1110  										{
  1111  											Op:   "add",
  1112  											Path: "/spec/template/spec/",
  1113  											ValueFrom: &clusterv1.JSONPatchValue{
  1114  												// Template is invalid - too many leading curly braces.
  1115  												Template: ptr.To(`template {{{{{{{{ .variableB }}`),
  1116  											},
  1117  										},
  1118  									},
  1119  								},
  1120  							},
  1121  						},
  1122  					},
  1123  					Variables: []clusterv1.ClusterClassVariable{
  1124  						{
  1125  							Name:     "variableName",
  1126  							Required: true,
  1127  							Schema: clusterv1.VariableSchema{
  1128  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
  1129  									Type: "string",
  1130  								},
  1131  							},
  1132  						},
  1133  					},
  1134  				},
  1135  			},
  1136  			wantErr: true,
  1137  		},
  1138  
  1139  		// Patch valueFrom.Variable validation
  1140  		{
  1141  			name: "error if jsonPatch valueFrom uses a variable which is not defined",
  1142  			clusterClass: clusterv1.ClusterClass{
  1143  				Spec: clusterv1.ClusterClassSpec{
  1144  					ControlPlane: clusterv1.ControlPlaneClass{
  1145  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
  1146  							Ref: &corev1.ObjectReference{
  1147  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1148  								Kind:       "ControlPlaneTemplate",
  1149  							},
  1150  						},
  1151  					},
  1152  					Patches: []clusterv1.ClusterClassPatch{
  1153  						{
  1154  							Name: "patch1",
  1155  							Definitions: []clusterv1.PatchDefinition{
  1156  								{
  1157  									Selector: clusterv1.PatchSelector{
  1158  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1159  										Kind:       "ControlPlaneTemplate",
  1160  										MatchResources: clusterv1.PatchSelectorMatch{
  1161  											ControlPlane: true,
  1162  										},
  1163  									},
  1164  									JSONPatches: []clusterv1.JSONPatch{
  1165  										{
  1166  											Op:   "add",
  1167  											Path: "/spec/template/spec/",
  1168  											ValueFrom: &clusterv1.JSONPatchValue{
  1169  												Variable: ptr.To("undefinedVariable"),
  1170  											},
  1171  										},
  1172  									},
  1173  								},
  1174  							},
  1175  						},
  1176  					},
  1177  					Variables: []clusterv1.ClusterClassVariable{
  1178  						{
  1179  							Name:     "variableName",
  1180  							Required: true,
  1181  							Schema: clusterv1.VariableSchema{
  1182  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
  1183  									Type: "string",
  1184  								},
  1185  							},
  1186  						},
  1187  					},
  1188  				},
  1189  			},
  1190  			wantErr: true,
  1191  		},
  1192  		{
  1193  			name: "pass if jsonPatch uses a user-defined variable which is defined",
  1194  			clusterClass: clusterv1.ClusterClass{
  1195  				Spec: clusterv1.ClusterClassSpec{
  1196  					ControlPlane: clusterv1.ControlPlaneClass{
  1197  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
  1198  							Ref: &corev1.ObjectReference{
  1199  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1200  								Kind:       "ControlPlaneTemplate",
  1201  							},
  1202  						},
  1203  					},
  1204  					Patches: []clusterv1.ClusterClassPatch{
  1205  						{
  1206  							Name: "patch1",
  1207  							Definitions: []clusterv1.PatchDefinition{
  1208  								{
  1209  									Selector: clusterv1.PatchSelector{
  1210  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1211  										Kind:       "ControlPlaneTemplate",
  1212  										MatchResources: clusterv1.PatchSelectorMatch{
  1213  											ControlPlane: true,
  1214  										},
  1215  									},
  1216  									JSONPatches: []clusterv1.JSONPatch{
  1217  										{
  1218  											Op:   "add",
  1219  											Path: "/spec/template/spec/",
  1220  											ValueFrom: &clusterv1.JSONPatchValue{
  1221  												Variable: ptr.To("variableName"),
  1222  											},
  1223  										},
  1224  									},
  1225  								},
  1226  							},
  1227  						},
  1228  					},
  1229  					Variables: []clusterv1.ClusterClassVariable{
  1230  						{
  1231  							Name:     "variableName",
  1232  							Required: true,
  1233  							Schema: clusterv1.VariableSchema{
  1234  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
  1235  									Type: "string",
  1236  								},
  1237  							},
  1238  						},
  1239  					},
  1240  				},
  1241  			},
  1242  			wantErr: false,
  1243  		},
  1244  		{
  1245  			name: "pass if jsonPatch uses a nested user-defined variable which is defined",
  1246  			clusterClass: clusterv1.ClusterClass{
  1247  				Spec: clusterv1.ClusterClassSpec{
  1248  					ControlPlane: clusterv1.ControlPlaneClass{
  1249  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
  1250  							Ref: &corev1.ObjectReference{
  1251  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1252  								Kind:       "ControlPlaneTemplate",
  1253  							},
  1254  						},
  1255  					},
  1256  					Patches: []clusterv1.ClusterClassPatch{
  1257  						{
  1258  							Name: "patch1",
  1259  							Definitions: []clusterv1.PatchDefinition{
  1260  								{
  1261  									Selector: clusterv1.PatchSelector{
  1262  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1263  										Kind:       "ControlPlaneTemplate",
  1264  										MatchResources: clusterv1.PatchSelectorMatch{
  1265  											ControlPlane: true,
  1266  										},
  1267  									},
  1268  									JSONPatches: []clusterv1.JSONPatch{
  1269  										{
  1270  											Op:   "add",
  1271  											Path: "/spec/template/spec/",
  1272  											ValueFrom: &clusterv1.JSONPatchValue{
  1273  												Variable: ptr.To("variableName.nestedField"),
  1274  											},
  1275  										},
  1276  									},
  1277  								},
  1278  							},
  1279  						},
  1280  					},
  1281  					Variables: []clusterv1.ClusterClassVariable{
  1282  						{
  1283  							Name:     "variableName",
  1284  							Required: true,
  1285  							Schema: clusterv1.VariableSchema{
  1286  								OpenAPIV3Schema: clusterv1.JSONSchemaProps{
  1287  									Type: "object",
  1288  									Properties: map[string]clusterv1.JSONSchemaProps{
  1289  										"nestedField": {
  1290  											Type: "string",
  1291  										},
  1292  									},
  1293  								},
  1294  							},
  1295  						},
  1296  					},
  1297  				},
  1298  			},
  1299  			wantErr: false,
  1300  		},
  1301  		{
  1302  			name: "error if jsonPatch uses a builtin variable which is not defined",
  1303  			clusterClass: clusterv1.ClusterClass{
  1304  				Spec: clusterv1.ClusterClassSpec{
  1305  					ControlPlane: clusterv1.ControlPlaneClass{
  1306  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
  1307  							Ref: &corev1.ObjectReference{
  1308  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1309  								Kind:       "ControlPlaneTemplate",
  1310  							},
  1311  						},
  1312  					},
  1313  					Patches: []clusterv1.ClusterClassPatch{
  1314  						{
  1315  							Name: "patch1",
  1316  							Definitions: []clusterv1.PatchDefinition{
  1317  								{
  1318  									Selector: clusterv1.PatchSelector{
  1319  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1320  										Kind:       "ControlPlaneTemplate",
  1321  										MatchResources: clusterv1.PatchSelectorMatch{
  1322  											ControlPlane: true,
  1323  										},
  1324  									},
  1325  									JSONPatches: []clusterv1.JSONPatch{
  1326  										{
  1327  											Op:   "add",
  1328  											Path: "/spec/template/spec/",
  1329  											ValueFrom: &clusterv1.JSONPatchValue{
  1330  												Variable: ptr.To("builtin.notDefined"),
  1331  											},
  1332  										},
  1333  									},
  1334  								},
  1335  							},
  1336  						},
  1337  					},
  1338  				},
  1339  			},
  1340  			wantErr: true,
  1341  		},
  1342  		{
  1343  			name: "pass if jsonPatch uses a builtin variable which is defined",
  1344  			clusterClass: clusterv1.ClusterClass{
  1345  				Spec: clusterv1.ClusterClassSpec{
  1346  					ControlPlane: clusterv1.ControlPlaneClass{
  1347  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
  1348  							Ref: &corev1.ObjectReference{
  1349  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1350  								Kind:       "ControlPlaneTemplate",
  1351  							},
  1352  						},
  1353  					},
  1354  
  1355  					Patches: []clusterv1.ClusterClassPatch{
  1356  
  1357  						{
  1358  							Name: "patch1",
  1359  							Definitions: []clusterv1.PatchDefinition{
  1360  								{
  1361  									Selector: clusterv1.PatchSelector{
  1362  										APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1363  										Kind:       "ControlPlaneTemplate",
  1364  										MatchResources: clusterv1.PatchSelectorMatch{
  1365  											ControlPlane: true,
  1366  										},
  1367  									},
  1368  									JSONPatches: []clusterv1.JSONPatch{
  1369  										{
  1370  											Op:   "add",
  1371  											Path: "/spec/template/spec/",
  1372  											ValueFrom: &clusterv1.JSONPatchValue{
  1373  												Variable: ptr.To("builtin.machineDeployment.version"),
  1374  											},
  1375  										},
  1376  									},
  1377  								},
  1378  							},
  1379  						},
  1380  					},
  1381  				},
  1382  			},
  1383  			wantErr: false,
  1384  		},
  1385  
  1386  		// Patch with External
  1387  		{
  1388  			name: "pass if patch defines both external.generateExtension and external.validateExtension",
  1389  			clusterClass: clusterv1.ClusterClass{
  1390  				Spec: clusterv1.ClusterClassSpec{
  1391  					ControlPlane: clusterv1.ControlPlaneClass{
  1392  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
  1393  							Ref: &corev1.ObjectReference{
  1394  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1395  								Kind:       "ControlPlaneTemplate",
  1396  							},
  1397  						},
  1398  					},
  1399  
  1400  					Patches: []clusterv1.ClusterClassPatch{
  1401  						{
  1402  							Name: "patch1",
  1403  							External: &clusterv1.ExternalPatchDefinition{
  1404  								GenerateExtension: ptr.To("generate-extension"),
  1405  								ValidateExtension: ptr.To("generate-extension"),
  1406  							},
  1407  						},
  1408  					},
  1409  				},
  1410  			},
  1411  			runtimeSDK: true,
  1412  			wantErr:    false,
  1413  		},
  1414  		{
  1415  			name: "error if patch defines both external and RuntimeSDK is not enabled",
  1416  			clusterClass: clusterv1.ClusterClass{
  1417  				Spec: clusterv1.ClusterClassSpec{
  1418  					ControlPlane: clusterv1.ControlPlaneClass{
  1419  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
  1420  							Ref: &corev1.ObjectReference{
  1421  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1422  								Kind:       "ControlPlaneTemplate",
  1423  							},
  1424  						},
  1425  					},
  1426  
  1427  					Patches: []clusterv1.ClusterClassPatch{
  1428  						{
  1429  							Name: "patch1",
  1430  							External: &clusterv1.ExternalPatchDefinition{
  1431  								GenerateExtension: ptr.To("generate-extension"),
  1432  								ValidateExtension: ptr.To("generate-extension"),
  1433  							},
  1434  						},
  1435  					},
  1436  				},
  1437  			},
  1438  			runtimeSDK: false,
  1439  			wantErr:    true,
  1440  		},
  1441  		{
  1442  			name: "error if patch defines neither external.generateExtension nor external.validateExtension",
  1443  			clusterClass: clusterv1.ClusterClass{
  1444  				Spec: clusterv1.ClusterClassSpec{
  1445  					ControlPlane: clusterv1.ControlPlaneClass{
  1446  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
  1447  							Ref: &corev1.ObjectReference{
  1448  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1449  								Kind:       "ControlPlaneTemplate",
  1450  							},
  1451  						},
  1452  					},
  1453  
  1454  					Patches: []clusterv1.ClusterClassPatch{
  1455  						{
  1456  							Name:     "patch1",
  1457  							External: &clusterv1.ExternalPatchDefinition{},
  1458  						},
  1459  					},
  1460  				},
  1461  			},
  1462  			runtimeSDK: true,
  1463  			wantErr:    true,
  1464  		},
  1465  		{
  1466  			name: "error if patch defines both external and definitions",
  1467  			clusterClass: clusterv1.ClusterClass{
  1468  				Spec: clusterv1.ClusterClassSpec{
  1469  					ControlPlane: clusterv1.ControlPlaneClass{
  1470  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
  1471  							Ref: &corev1.ObjectReference{
  1472  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1473  								Kind:       "ControlPlaneTemplate",
  1474  							},
  1475  						},
  1476  					},
  1477  
  1478  					Patches: []clusterv1.ClusterClassPatch{
  1479  						{
  1480  							Name: "patch1",
  1481  							External: &clusterv1.ExternalPatchDefinition{
  1482  								GenerateExtension: ptr.To("generate-extension"),
  1483  								ValidateExtension: ptr.To("generate-extension"),
  1484  							},
  1485  							Definitions: []clusterv1.PatchDefinition{},
  1486  						},
  1487  					},
  1488  				},
  1489  			},
  1490  			runtimeSDK: true,
  1491  			wantErr:    true,
  1492  		},
  1493  		{
  1494  			name: "error if neither external nor definitions is defined",
  1495  			clusterClass: clusterv1.ClusterClass{
  1496  				Spec: clusterv1.ClusterClassSpec{
  1497  					ControlPlane: clusterv1.ControlPlaneClass{
  1498  						LocalObjectTemplate: clusterv1.LocalObjectTemplate{
  1499  							Ref: &corev1.ObjectReference{
  1500  								APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1501  								Kind:       "ControlPlaneTemplate",
  1502  							},
  1503  						},
  1504  					},
  1505  
  1506  					Patches: []clusterv1.ClusterClassPatch{
  1507  						{
  1508  							Name: "patch1",
  1509  						},
  1510  					},
  1511  				},
  1512  			},
  1513  			runtimeSDK: true,
  1514  			wantErr:    true,
  1515  		},
  1516  	}
  1517  	for i := range tests {
  1518  		tt := tests[i]
  1519  		t.Run(tt.name, func(t *testing.T) {
  1520  			defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.RuntimeSDK, tt.runtimeSDK)()
  1521  
  1522  			g := NewWithT(t)
  1523  
  1524  			errList := validatePatches(&tt.clusterClass)
  1525  			if tt.wantErr {
  1526  				g.Expect(errList).NotTo(BeEmpty())
  1527  				return
  1528  			}
  1529  			g.Expect(errList).To(BeEmpty())
  1530  		})
  1531  	}
  1532  }
  1533  
  1534  func Test_validateSelectors(t *testing.T) {
  1535  	tests := []struct {
  1536  		name         string
  1537  		selector     clusterv1.PatchSelector
  1538  		clusterClass *clusterv1.ClusterClass
  1539  		wantErr      bool
  1540  	}{
  1541  		{
  1542  			name: "error if selectors are all set to false or empty",
  1543  			selector: clusterv1.PatchSelector{
  1544  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1545  				Kind:       "InfrastructureClusterTemplate",
  1546  				MatchResources: clusterv1.PatchSelectorMatch{
  1547  					ControlPlane:           false,
  1548  					InfrastructureCluster:  false,
  1549  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{},
  1550  					MachinePoolClass:       &clusterv1.PatchSelectorMatchMachinePoolClass{},
  1551  				},
  1552  			},
  1553  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1554  				WithControlPlaneTemplate(
  1555  					refToUnstructured(
  1556  						&corev1.ObjectReference{
  1557  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1558  							Kind:       "InfrastructureClusterTemplate",
  1559  						}),
  1560  				).
  1561  				Build(),
  1562  			wantErr: true,
  1563  		},
  1564  		{
  1565  			name: "pass if selector targets an existing infrastructureCluster reference",
  1566  			selector: clusterv1.PatchSelector{
  1567  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1568  				Kind:       "InfrastructureClusterTemplate",
  1569  				MatchResources: clusterv1.PatchSelectorMatch{
  1570  					InfrastructureCluster: true,
  1571  				},
  1572  			},
  1573  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1574  				WithInfrastructureClusterTemplate(
  1575  					refToUnstructured(
  1576  						&corev1.ObjectReference{
  1577  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1578  							Kind:       "InfrastructureClusterTemplate",
  1579  						}),
  1580  				).
  1581  				Build(),
  1582  		},
  1583  		{
  1584  			name: "error if selector targets a non-existing infrastructureCluster APIVersion reference",
  1585  			selector: clusterv1.PatchSelector{
  1586  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1587  				Kind:       "InfrastructureClusterTemplate",
  1588  				MatchResources: clusterv1.PatchSelectorMatch{
  1589  					InfrastructureCluster: true,
  1590  				},
  1591  			},
  1592  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1593  				WithInfrastructureClusterTemplate(
  1594  					refToUnstructured(
  1595  						&corev1.ObjectReference{
  1596  							APIVersion: "nonmatchinginfrastructure.cluster.x-k8s.io/v1beta1",
  1597  							Kind:       "InfrastructureClusterTemplate",
  1598  						}),
  1599  				).
  1600  				Build(),
  1601  			wantErr: true,
  1602  		},
  1603  		{
  1604  			name: "pass if selector targets an existing controlPlane reference",
  1605  			selector: clusterv1.PatchSelector{
  1606  				APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1607  				Kind:       "ControlPlaneTemplate",
  1608  				MatchResources: clusterv1.PatchSelectorMatch{
  1609  					ControlPlane: true,
  1610  				},
  1611  			},
  1612  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1613  				WithControlPlaneTemplate(
  1614  					refToUnstructured(
  1615  						&corev1.ObjectReference{
  1616  							APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1617  							Kind:       "ControlPlaneTemplate",
  1618  						}),
  1619  				).
  1620  				Build(),
  1621  		},
  1622  		{
  1623  			name: "error if selector targets a non-existing controlPlane Kind reference",
  1624  			selector: clusterv1.PatchSelector{
  1625  				APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1626  				Kind:       "ControlPlaneTemplate",
  1627  				MatchResources: clusterv1.PatchSelectorMatch{
  1628  					ControlPlane: true,
  1629  				},
  1630  			},
  1631  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1632  				WithControlPlaneTemplate(
  1633  					refToUnstructured(
  1634  						&corev1.ObjectReference{
  1635  							APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1636  							Kind:       "NonMatchingControlPlaneTemplate",
  1637  						}),
  1638  				).
  1639  				Build(),
  1640  			wantErr: true,
  1641  		},
  1642  		{
  1643  			name: "pass if selector targets an existing controlPlane machineInfrastructure reference",
  1644  			selector: clusterv1.PatchSelector{
  1645  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1646  				Kind:       "InfrastructureMachineTemplate",
  1647  				MatchResources: clusterv1.PatchSelectorMatch{
  1648  					ControlPlane: true,
  1649  				},
  1650  			},
  1651  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1652  				WithControlPlaneTemplate(
  1653  					refToUnstructured(
  1654  						&corev1.ObjectReference{
  1655  							APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1656  							Kind:       "NonMatchingControlPlaneTemplate",
  1657  						}),
  1658  				).
  1659  				WithControlPlaneInfrastructureMachineTemplate(
  1660  					refToUnstructured(
  1661  						&corev1.ObjectReference{
  1662  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1663  							Kind:       "InfrastructureMachineTemplate",
  1664  						}),
  1665  				).
  1666  				Build(),
  1667  		},
  1668  		{
  1669  			name: "error if selector targets a non-existing controlPlane machineInfrastructure reference",
  1670  			selector: clusterv1.PatchSelector{
  1671  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1672  				Kind:       "InfrastructureMachineTemplate",
  1673  				MatchResources: clusterv1.PatchSelectorMatch{
  1674  					ControlPlane: true,
  1675  				},
  1676  			},
  1677  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1678  				WithControlPlaneTemplate(
  1679  					refToUnstructured(
  1680  						&corev1.ObjectReference{
  1681  							APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  1682  							Kind:       "NonMatchingControlPlaneTemplate",
  1683  						}),
  1684  				).
  1685  				WithControlPlaneInfrastructureMachineTemplate(
  1686  					refToUnstructured(
  1687  						&corev1.ObjectReference{
  1688  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1689  							Kind:       "NonMatchingInfrastructureMachineTemplate",
  1690  						}),
  1691  				).
  1692  				Build(),
  1693  			wantErr: true,
  1694  		},
  1695  		{
  1696  			name: "pass if selector targets an existing MachineDeploymentClass and MachinePoolClass BootstrapTemplate",
  1697  			selector: clusterv1.PatchSelector{
  1698  				APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1699  				Kind:       "BootstrapTemplate",
  1700  				MatchResources: clusterv1.PatchSelectorMatch{
  1701  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{
  1702  						Names: []string{"aa"},
  1703  					},
  1704  					MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{
  1705  						Names: []string{"aa"},
  1706  					},
  1707  				},
  1708  			},
  1709  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1710  				WithWorkerMachineDeploymentClasses(
  1711  					*builder.MachineDeploymentClass("aa").
  1712  						WithInfrastructureTemplate(
  1713  							refToUnstructured(&corev1.ObjectReference{
  1714  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1715  								Kind:       "InfrastructureMachineTemplate",
  1716  							})).
  1717  						WithBootstrapTemplate(
  1718  							refToUnstructured(&corev1.ObjectReference{
  1719  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1720  								Kind:       "BootstrapTemplate",
  1721  							})).
  1722  						Build(),
  1723  				).
  1724  				WithWorkerMachinePoolClasses(
  1725  					*builder.MachinePoolClass("aa").
  1726  						WithInfrastructureTemplate(
  1727  							refToUnstructured(&corev1.ObjectReference{
  1728  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1729  								Kind:       "InfrastructureMachinePoolTemplate",
  1730  							})).
  1731  						WithBootstrapTemplate(
  1732  							refToUnstructured(&corev1.ObjectReference{
  1733  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1734  								Kind:       "BootstrapTemplate",
  1735  							})).
  1736  						Build(),
  1737  				).
  1738  				Build(),
  1739  		},
  1740  		{
  1741  			name: "pass if selector targets an existing MachineDeploymentClass InfrastructureTemplate",
  1742  			selector: clusterv1.PatchSelector{
  1743  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1744  				Kind:       "InfrastructureMachineTemplate",
  1745  				MatchResources: clusterv1.PatchSelectorMatch{
  1746  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{
  1747  						Names: []string{"aa"},
  1748  					},
  1749  				},
  1750  			},
  1751  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1752  				WithWorkerMachineDeploymentClasses(
  1753  					*builder.MachineDeploymentClass("aa").
  1754  						WithInfrastructureTemplate(
  1755  							refToUnstructured(&corev1.ObjectReference{
  1756  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1757  								Kind:       "InfrastructureMachineTemplate",
  1758  							})).
  1759  						WithBootstrapTemplate(
  1760  							refToUnstructured(&corev1.ObjectReference{
  1761  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1762  								Kind:       "BootstrapTemplate",
  1763  							})).
  1764  						Build(),
  1765  				).
  1766  				Build(),
  1767  		},
  1768  		{
  1769  			name: "pass if selector targets an existing MachinePoolClass InfrastructureTemplate",
  1770  			selector: clusterv1.PatchSelector{
  1771  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1772  				Kind:       "InfrastructureMachinePoolTemplate",
  1773  				MatchResources: clusterv1.PatchSelectorMatch{
  1774  					MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{
  1775  						Names: []string{"aa"},
  1776  					},
  1777  				},
  1778  			},
  1779  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1780  				WithWorkerMachinePoolClasses(
  1781  					*builder.MachinePoolClass("aa").
  1782  						WithInfrastructureTemplate(
  1783  							refToUnstructured(&corev1.ObjectReference{
  1784  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1785  								Kind:       "InfrastructureMachinePoolTemplate",
  1786  							})).
  1787  						WithBootstrapTemplate(
  1788  							refToUnstructured(&corev1.ObjectReference{
  1789  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1790  								Kind:       "BootstrapTemplate",
  1791  							})).
  1792  						Build(),
  1793  				).
  1794  				Build(),
  1795  		},
  1796  		{
  1797  			name: "error if selector targets a non-existing MachineDeploymentClass InfrastructureTemplate",
  1798  			selector: clusterv1.PatchSelector{
  1799  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1800  				Kind:       "InfrastructureMachineTemplate",
  1801  				MatchResources: clusterv1.PatchSelectorMatch{
  1802  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{
  1803  						Names: []string{"bb"},
  1804  					},
  1805  				},
  1806  			},
  1807  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1808  				WithWorkerMachineDeploymentClasses(
  1809  					*builder.MachineDeploymentClass("aa").
  1810  						WithInfrastructureTemplate(
  1811  							refToUnstructured(&corev1.ObjectReference{
  1812  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1813  								Kind:       "InfrastructureMachineTemplate",
  1814  							})).
  1815  						WithBootstrapTemplate(
  1816  							refToUnstructured(&corev1.ObjectReference{
  1817  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1818  								Kind:       "BootstrapTemplate",
  1819  							})).
  1820  						Build(),
  1821  					*builder.MachineDeploymentClass("bb").
  1822  						WithInfrastructureTemplate(
  1823  							refToUnstructured(&corev1.ObjectReference{
  1824  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1825  								Kind:       "NonMatchingInfrastructureMachineTemplate",
  1826  							})).
  1827  						WithBootstrapTemplate(
  1828  							refToUnstructured(&corev1.ObjectReference{
  1829  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1830  								Kind:       "BootstrapTemplate",
  1831  							})).
  1832  						Build(),
  1833  				).
  1834  				Build(),
  1835  			wantErr: true,
  1836  		},
  1837  		{
  1838  			name: "error if selector targets a non-existing MachinePoolClass InfrastructureTemplate",
  1839  			selector: clusterv1.PatchSelector{
  1840  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1841  				Kind:       "InfrastructureMachinePoolTemplate",
  1842  				MatchResources: clusterv1.PatchSelectorMatch{
  1843  					MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{
  1844  						Names: []string{"bb"},
  1845  					},
  1846  				},
  1847  			},
  1848  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1849  				WithWorkerMachinePoolClasses(
  1850  					*builder.MachinePoolClass("aa").
  1851  						WithInfrastructureTemplate(
  1852  							refToUnstructured(&corev1.ObjectReference{
  1853  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1854  								Kind:       "InfrastructureMachinePoolTemplate",
  1855  							})).
  1856  						WithBootstrapTemplate(
  1857  							refToUnstructured(&corev1.ObjectReference{
  1858  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1859  								Kind:       "BootstrapTemplate",
  1860  							})).
  1861  						Build(),
  1862  					*builder.MachinePoolClass("bb").
  1863  						WithInfrastructureTemplate(
  1864  							refToUnstructured(&corev1.ObjectReference{
  1865  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1866  								Kind:       "NonMatchingInfrastructureMachinePoolTemplate",
  1867  							})).
  1868  						WithBootstrapTemplate(
  1869  							refToUnstructured(&corev1.ObjectReference{
  1870  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1871  								Kind:       "BootstrapTemplate",
  1872  							})).
  1873  						Build(),
  1874  				).
  1875  				Build(),
  1876  			wantErr: true,
  1877  		},
  1878  		{
  1879  			name: "fail if selector targets ControlPlane Machine Infrastructure but does not have MatchResources.ControlPlane enabled",
  1880  			selector: clusterv1.PatchSelector{
  1881  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1882  				Kind:       "InfrastructureMachineTemplate",
  1883  				MatchResources: clusterv1.PatchSelectorMatch{
  1884  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{
  1885  						Names: []string{"bb"},
  1886  					},
  1887  					ControlPlane: false,
  1888  				},
  1889  			},
  1890  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1891  				WithControlPlaneInfrastructureMachineTemplate(
  1892  					refToUnstructured(
  1893  						&corev1.ObjectReference{
  1894  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1895  							Kind:       "InfrastructureMachineTemplate",
  1896  						}),
  1897  				).
  1898  				Build(),
  1899  			wantErr: true,
  1900  		},
  1901  		{
  1902  			name: "error if selector targets an empty MachineDeploymentClass InfrastructureTemplate",
  1903  			selector: clusterv1.PatchSelector{
  1904  				APIVersion:     "infrastructure.cluster.x-k8s.io/v1beta1",
  1905  				Kind:           "InfrastructureMachineTemplate",
  1906  				MatchResources: clusterv1.PatchSelectorMatch{},
  1907  			},
  1908  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1909  				WithWorkerMachineDeploymentClasses(
  1910  					*builder.MachineDeploymentClass("aa").
  1911  						WithInfrastructureTemplate(
  1912  							refToUnstructured(&corev1.ObjectReference{
  1913  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1914  								Kind:       "InfrastructureMachineTemplate",
  1915  							})).
  1916  						WithBootstrapTemplate(
  1917  							refToUnstructured(&corev1.ObjectReference{
  1918  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1919  								Kind:       "BootstrapTemplate",
  1920  							})).
  1921  						Build(),
  1922  					*builder.MachineDeploymentClass("bb").
  1923  						WithInfrastructureTemplate(
  1924  							refToUnstructured(&corev1.ObjectReference{
  1925  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1926  								Kind:       "NonMatchingInfrastructureMachineTemplate",
  1927  							})).
  1928  						WithBootstrapTemplate(
  1929  							refToUnstructured(&corev1.ObjectReference{
  1930  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1931  								Kind:       "BootstrapTemplate",
  1932  							})).
  1933  						Build(),
  1934  				).
  1935  				Build(),
  1936  			wantErr: true,
  1937  		},
  1938  		{
  1939  			name: "error if selector targets an empty MachinePoolClass InfrastructureTemplate",
  1940  			selector: clusterv1.PatchSelector{
  1941  				APIVersion:     "infrastructure.cluster.x-k8s.io/v1beta1",
  1942  				Kind:           "InfrastructureMachinePoolTemplate",
  1943  				MatchResources: clusterv1.PatchSelectorMatch{},
  1944  			},
  1945  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1946  				WithWorkerMachinePoolClasses(
  1947  					*builder.MachinePoolClass("aa").
  1948  						WithInfrastructureTemplate(
  1949  							refToUnstructured(&corev1.ObjectReference{
  1950  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1951  								Kind:       "InfrastructureMachinePoolTemplate",
  1952  							})).
  1953  						WithBootstrapTemplate(
  1954  							refToUnstructured(&corev1.ObjectReference{
  1955  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1956  								Kind:       "BootstrapTemplate",
  1957  							})).
  1958  						Build(),
  1959  					*builder.MachinePoolClass("bb").
  1960  						WithInfrastructureTemplate(
  1961  							refToUnstructured(&corev1.ObjectReference{
  1962  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1963  								Kind:       "NonMatchingInfrastructureMachinePoolTemplate",
  1964  							})).
  1965  						WithBootstrapTemplate(
  1966  							refToUnstructured(&corev1.ObjectReference{
  1967  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1968  								Kind:       "BootstrapTemplate",
  1969  							})).
  1970  						Build(),
  1971  				).
  1972  				Build(),
  1973  			wantErr: true,
  1974  		},
  1975  		{
  1976  			name: "error if selector targets a bad pattern for matching MachineDeploymentClass InfrastructureTemplate",
  1977  			selector: clusterv1.PatchSelector{
  1978  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1979  				Kind:       "InfrastructureMachineTemplate",
  1980  				MatchResources: clusterv1.PatchSelectorMatch{
  1981  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{
  1982  						Names: []string{"a*a"},
  1983  					},
  1984  				},
  1985  			},
  1986  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1987  				WithWorkerMachineDeploymentClasses(
  1988  					*builder.MachineDeploymentClass("a-something-a").
  1989  						WithInfrastructureTemplate(
  1990  							refToUnstructured(&corev1.ObjectReference{
  1991  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1992  								Kind:       "InfrastructureMachineTemplate",
  1993  							})).
  1994  						WithBootstrapTemplate(
  1995  							refToUnstructured(&corev1.ObjectReference{
  1996  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1997  								Kind:       "BootstrapTemplate",
  1998  							})).
  1999  						Build(),
  2000  				).
  2001  				Build(),
  2002  			wantErr: true,
  2003  		},
  2004  		{
  2005  			name: "error if selector targets a bad pattern for matching MachinePoolClass InfrastructureTemplate",
  2006  			selector: clusterv1.PatchSelector{
  2007  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2008  				Kind:       "InfrastructureMachinePoolTemplate",
  2009  				MatchResources: clusterv1.PatchSelectorMatch{
  2010  					MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{
  2011  						Names: []string{"a*a"},
  2012  					},
  2013  				},
  2014  			},
  2015  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  2016  				WithWorkerMachinePoolClasses(
  2017  					*builder.MachinePoolClass("a-something-a").
  2018  						WithInfrastructureTemplate(
  2019  							refToUnstructured(&corev1.ObjectReference{
  2020  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2021  								Kind:       "InfrastructureMachinePoolTemplate",
  2022  							})).
  2023  						WithBootstrapTemplate(
  2024  							refToUnstructured(&corev1.ObjectReference{
  2025  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2026  								Kind:       "BootstrapTemplate",
  2027  							})).
  2028  						Build(),
  2029  				).
  2030  				Build(),
  2031  			wantErr: true,
  2032  		},
  2033  		{
  2034  			name: "pass if selector targets an existing MachineDeploymentClass InfrastructureTemplate with prefix *",
  2035  			selector: clusterv1.PatchSelector{
  2036  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2037  				Kind:       "InfrastructureMachineTemplate",
  2038  				MatchResources: clusterv1.PatchSelectorMatch{
  2039  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{
  2040  						Names: []string{"a-*"},
  2041  					},
  2042  				},
  2043  			},
  2044  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  2045  				WithWorkerMachineDeploymentClasses(
  2046  					*builder.MachineDeploymentClass("a-something-a").
  2047  						WithInfrastructureTemplate(
  2048  							refToUnstructured(&corev1.ObjectReference{
  2049  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2050  								Kind:       "InfrastructureMachineTemplate",
  2051  							})).
  2052  						WithBootstrapTemplate(
  2053  							refToUnstructured(&corev1.ObjectReference{
  2054  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2055  								Kind:       "BootstrapTemplate",
  2056  							})).
  2057  						Build(),
  2058  				).
  2059  				Build(),
  2060  		},
  2061  		{
  2062  			name: "pass if selector targets an existing MachinePoolClass InfrastructureTemplate with prefix *",
  2063  			selector: clusterv1.PatchSelector{
  2064  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2065  				Kind:       "InfrastructureMachinePoolTemplate",
  2066  				MatchResources: clusterv1.PatchSelectorMatch{
  2067  					MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{
  2068  						Names: []string{"a-*"},
  2069  					},
  2070  				},
  2071  			},
  2072  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  2073  				WithWorkerMachinePoolClasses(
  2074  					*builder.MachinePoolClass("a-something-a").
  2075  						WithInfrastructureTemplate(
  2076  							refToUnstructured(&corev1.ObjectReference{
  2077  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2078  								Kind:       "InfrastructureMachinePoolTemplate",
  2079  							})).
  2080  						WithBootstrapTemplate(
  2081  							refToUnstructured(&corev1.ObjectReference{
  2082  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2083  								Kind:       "BootstrapTemplate",
  2084  							})).
  2085  						Build(),
  2086  				).
  2087  				Build(),
  2088  		},
  2089  		{
  2090  			name: "pass if selector targets an existing MachineDeploymentClass InfrastructureTemplate with suffix *",
  2091  			selector: clusterv1.PatchSelector{
  2092  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2093  				Kind:       "InfrastructureMachineTemplate",
  2094  				MatchResources: clusterv1.PatchSelectorMatch{
  2095  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{
  2096  						Names: []string{"*-a"},
  2097  					},
  2098  				},
  2099  			},
  2100  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  2101  				WithWorkerMachineDeploymentClasses(
  2102  					*builder.MachineDeploymentClass("a-something-a").
  2103  						WithInfrastructureTemplate(
  2104  							refToUnstructured(&corev1.ObjectReference{
  2105  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2106  								Kind:       "InfrastructureMachineTemplate",
  2107  							})).
  2108  						WithBootstrapTemplate(
  2109  							refToUnstructured(&corev1.ObjectReference{
  2110  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2111  								Kind:       "BootstrapTemplate",
  2112  							})).
  2113  						Build(),
  2114  				).
  2115  				Build(),
  2116  		},
  2117  		{
  2118  			name: "pass if selector targets an existing MachinePoolClass InfrastructureTemplate with suffix *",
  2119  			selector: clusterv1.PatchSelector{
  2120  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2121  				Kind:       "InfrastructureMachinePoolTemplate",
  2122  				MatchResources: clusterv1.PatchSelectorMatch{
  2123  					MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{
  2124  						Names: []string{"*-a"},
  2125  					},
  2126  				},
  2127  			},
  2128  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  2129  				WithWorkerMachinePoolClasses(
  2130  					*builder.MachinePoolClass("a-something-a").
  2131  						WithInfrastructureTemplate(
  2132  							refToUnstructured(&corev1.ObjectReference{
  2133  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2134  								Kind:       "InfrastructureMachinePoolTemplate",
  2135  							})).
  2136  						WithBootstrapTemplate(
  2137  							refToUnstructured(&corev1.ObjectReference{
  2138  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2139  								Kind:       "BootstrapTemplate",
  2140  							})).
  2141  						Build(),
  2142  				).
  2143  				Build(),
  2144  		},
  2145  		{
  2146  			name: "pass if selector targets all existing MachineDeploymentClass InfrastructureTemplate with *",
  2147  			selector: clusterv1.PatchSelector{
  2148  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2149  				Kind:       "InfrastructureMachineTemplate",
  2150  				MatchResources: clusterv1.PatchSelectorMatch{
  2151  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{
  2152  						Names: []string{"*"},
  2153  					},
  2154  				},
  2155  			},
  2156  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  2157  				WithWorkerMachineDeploymentClasses(
  2158  					*builder.MachineDeploymentClass("a-something-a").
  2159  						WithInfrastructureTemplate(
  2160  							refToUnstructured(&corev1.ObjectReference{
  2161  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2162  								Kind:       "InfrastructureMachineTemplate",
  2163  							})).
  2164  						WithBootstrapTemplate(
  2165  							refToUnstructured(&corev1.ObjectReference{
  2166  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2167  								Kind:       "BootstrapTemplate",
  2168  							})).
  2169  						Build(),
  2170  				).
  2171  				Build(),
  2172  		},
  2173  		{
  2174  			name: "pass if selector targets all existing MachinePoolClass InfrastructureTemplate with *",
  2175  			selector: clusterv1.PatchSelector{
  2176  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2177  				Kind:       "InfrastructureMachinePoolTemplate",
  2178  				MatchResources: clusterv1.PatchSelectorMatch{
  2179  					MachinePoolClass: &clusterv1.PatchSelectorMatchMachinePoolClass{
  2180  						Names: []string{"*"},
  2181  					},
  2182  				},
  2183  			},
  2184  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  2185  				WithWorkerMachinePoolClasses(
  2186  					*builder.MachinePoolClass("a-something-a").
  2187  						WithInfrastructureTemplate(
  2188  							refToUnstructured(&corev1.ObjectReference{
  2189  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2190  								Kind:       "InfrastructureMachinePoolTemplate",
  2191  							})).
  2192  						WithBootstrapTemplate(
  2193  							refToUnstructured(&corev1.ObjectReference{
  2194  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2195  								Kind:       "BootstrapTemplate",
  2196  							})).
  2197  						Build(),
  2198  				).
  2199  				Build(),
  2200  		},
  2201  		// The following tests have selectors which match multiple resources at the same time.
  2202  		{
  2203  			name: "fail if selector targets a matching infrastructureCluster reference and a not matching control plane",
  2204  			selector: clusterv1.PatchSelector{
  2205  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2206  				Kind:       "InfrastructureClusterTemplate",
  2207  				MatchResources: clusterv1.PatchSelectorMatch{
  2208  					InfrastructureCluster: true,
  2209  					ControlPlane:          true,
  2210  				},
  2211  			},
  2212  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  2213  				WithInfrastructureClusterTemplate(
  2214  					refToUnstructured(
  2215  						&corev1.ObjectReference{
  2216  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2217  							Kind:       "InfrastructureClusterTemplate",
  2218  						}),
  2219  				).
  2220  				WithControlPlaneTemplate(
  2221  					refToUnstructured(
  2222  						&corev1.ObjectReference{
  2223  							APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  2224  							Kind:       "NonMatchingControlPlaneTemplate",
  2225  						}),
  2226  				).
  2227  				WithControlPlaneInfrastructureMachineTemplate(
  2228  					refToUnstructured(
  2229  						&corev1.ObjectReference{
  2230  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2231  							Kind:       "NonMatchingInfrastructureMachineTemplate",
  2232  						}),
  2233  				).
  2234  				Build(),
  2235  			wantErr: true,
  2236  		},
  2237  		{
  2238  			name: "pass if selector targets BOTH an existing ControlPlane MachineInfrastructureTemplate and an existing MachineDeploymentClass InfrastructureTemplate",
  2239  			selector: clusterv1.PatchSelector{
  2240  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2241  				Kind:       "InfrastructureMachineTemplate",
  2242  				MatchResources: clusterv1.PatchSelectorMatch{
  2243  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{
  2244  						Names: []string{"bb"},
  2245  					},
  2246  					ControlPlane: true,
  2247  				},
  2248  			},
  2249  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  2250  				WithControlPlaneInfrastructureMachineTemplate(
  2251  					refToUnstructured(
  2252  						&corev1.ObjectReference{
  2253  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2254  							Kind:       "InfrastructureMachineTemplate",
  2255  						}),
  2256  				).
  2257  				WithWorkerMachineDeploymentClasses(
  2258  					*builder.MachineDeploymentClass("aa").
  2259  						WithInfrastructureTemplate(
  2260  							refToUnstructured(&corev1.ObjectReference{
  2261  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2262  								Kind:       "InfrastructureMachineTemplate",
  2263  							})).
  2264  						WithBootstrapTemplate(
  2265  							refToUnstructured(&corev1.ObjectReference{
  2266  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2267  								Kind:       "BootstrapTemplate",
  2268  							})).
  2269  						Build(),
  2270  					*builder.MachineDeploymentClass("bb").
  2271  						WithInfrastructureTemplate(
  2272  							refToUnstructured(&corev1.ObjectReference{
  2273  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2274  								Kind:       "InfrastructureMachineTemplate",
  2275  							})).
  2276  						WithBootstrapTemplate(
  2277  							refToUnstructured(&corev1.ObjectReference{
  2278  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2279  								Kind:       "BootstrapTemplate",
  2280  							})).
  2281  						Build(),
  2282  				).
  2283  				Build(),
  2284  			wantErr: false,
  2285  		},
  2286  		{
  2287  			name: "fail if selector targets BOTH an existing ControlPlane MachineInfrastructureTemplate and an existing MachineDeploymentClass InfrastructureTemplate but does not match all MachineDeployment classes",
  2288  			selector: clusterv1.PatchSelector{
  2289  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2290  				Kind:       "InfrastructureMachineTemplate",
  2291  				MatchResources: clusterv1.PatchSelectorMatch{
  2292  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{
  2293  						Names: []string{"aa", "bb"},
  2294  					},
  2295  					ControlPlane: true,
  2296  				},
  2297  			},
  2298  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  2299  				WithControlPlaneTemplate(
  2300  					refToUnstructured(
  2301  						&corev1.ObjectReference{
  2302  							APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  2303  							Kind:       "NonMatchingControlPlaneTemplate",
  2304  						}),
  2305  				).
  2306  				WithControlPlaneInfrastructureMachineTemplate(
  2307  					refToUnstructured(
  2308  						&corev1.ObjectReference{
  2309  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2310  							Kind:       "InfrastructureMachineTemplate",
  2311  						}),
  2312  				).
  2313  				WithWorkerMachineDeploymentClasses(
  2314  					*builder.MachineDeploymentClass("aa").
  2315  						WithInfrastructureTemplate(
  2316  							refToUnstructured(&corev1.ObjectReference{
  2317  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2318  								Kind:       "NonMatchingInfrastructureMachineTemplate",
  2319  							})).
  2320  						WithBootstrapTemplate(
  2321  							refToUnstructured(&corev1.ObjectReference{
  2322  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2323  								Kind:       "BootstrapTemplate",
  2324  							})).
  2325  						Build(),
  2326  					*builder.MachineDeploymentClass("bb").
  2327  						WithInfrastructureTemplate(
  2328  							refToUnstructured(&corev1.ObjectReference{
  2329  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2330  								Kind:       "InfrastructureMachineTemplate",
  2331  							})).
  2332  						WithBootstrapTemplate(
  2333  							refToUnstructured(&corev1.ObjectReference{
  2334  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2335  								Kind:       "BootstrapTemplate",
  2336  							})).
  2337  						Build(),
  2338  				).
  2339  				Build(),
  2340  			wantErr: true,
  2341  		},
  2342  		{
  2343  			name: "fail if selector targets BOTH an existing ControlPlane MachineInfrastructureTemplate and an existing MachineDeploymentClass InfrastructureTemplate but matches only one",
  2344  			selector: clusterv1.PatchSelector{
  2345  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2346  				Kind:       "InfrastructureMachineTemplate",
  2347  				MatchResources: clusterv1.PatchSelectorMatch{
  2348  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{
  2349  						Names: []string{"bb"},
  2350  					},
  2351  					ControlPlane: true,
  2352  				},
  2353  			},
  2354  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  2355  				WithControlPlaneTemplate(
  2356  					refToUnstructured(
  2357  						&corev1.ObjectReference{
  2358  							APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  2359  							Kind:       "NonMatchingControlPlaneTemplate",
  2360  						}),
  2361  				).
  2362  				WithControlPlaneInfrastructureMachineTemplate(
  2363  					refToUnstructured(
  2364  						&corev1.ObjectReference{
  2365  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2366  							Kind:       "InfrastructureMachineTemplate",
  2367  						}),
  2368  				).
  2369  				WithWorkerMachineDeploymentClasses(
  2370  					*builder.MachineDeploymentClass("bb").
  2371  						WithInfrastructureTemplate(
  2372  							refToUnstructured(&corev1.ObjectReference{
  2373  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2374  								Kind:       "OtherInfrastructureMachineTemplate",
  2375  							})).
  2376  						WithBootstrapTemplate(
  2377  							refToUnstructured(&corev1.ObjectReference{
  2378  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2379  								Kind:       "BootstrapTemplate",
  2380  							})).
  2381  						Build(),
  2382  				).
  2383  				Build(),
  2384  			wantErr: true,
  2385  		},
  2386  		{
  2387  			name: "fail if selector targets everything but nothing matches",
  2388  			selector: clusterv1.PatchSelector{
  2389  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2390  				Kind:       "NotMatchingInfrastructureMachineTemplate",
  2391  				MatchResources: clusterv1.PatchSelectorMatch{
  2392  					ControlPlane:          true,
  2393  					InfrastructureCluster: true,
  2394  					MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{
  2395  						Names: []string{"bb"},
  2396  					},
  2397  				},
  2398  			},
  2399  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  2400  				WithInfrastructureClusterTemplate(
  2401  					refToUnstructured(
  2402  						&corev1.ObjectReference{
  2403  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2404  							Kind:       "InfrastructureClusterTemplate",
  2405  						}),
  2406  				).
  2407  				WithControlPlaneTemplate(
  2408  					refToUnstructured(
  2409  						&corev1.ObjectReference{
  2410  							APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
  2411  							Kind:       "NonMatchingControlPlaneTemplate",
  2412  						}),
  2413  				).
  2414  				WithControlPlaneInfrastructureMachineTemplate(
  2415  					refToUnstructured(
  2416  						&corev1.ObjectReference{
  2417  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2418  							Kind:       "InfrastructureMachineTemplate",
  2419  						}),
  2420  				).
  2421  				WithWorkerMachineDeploymentClasses(
  2422  					*builder.MachineDeploymentClass("bb").
  2423  						WithInfrastructureTemplate(
  2424  							refToUnstructured(&corev1.ObjectReference{
  2425  								APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  2426  								Kind:       "OtherInfrastructureMachineTemplate",
  2427  							})).
  2428  						WithBootstrapTemplate(
  2429  							refToUnstructured(&corev1.ObjectReference{
  2430  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  2431  								Kind:       "BootstrapTemplate",
  2432  							})).
  2433  						Build(),
  2434  				).
  2435  				Build(),
  2436  			wantErr: true,
  2437  		},
  2438  	}
  2439  	for _, tt := range tests {
  2440  		t.Run(tt.name, func(t *testing.T) {
  2441  			g := NewWithT(t)
  2442  
  2443  			err := validateSelectors(tt.selector, tt.clusterClass, field.NewPath(""))
  2444  
  2445  			if tt.wantErr {
  2446  				g.Expect(err.ToAggregate()).To(HaveOccurred())
  2447  				return
  2448  			}
  2449  			g.Expect(err.ToAggregate()).ToNot(HaveOccurred())
  2450  		})
  2451  	}
  2452  }
  2453  
  2454  func TestGetVariableName(t *testing.T) {
  2455  	tests := []struct {
  2456  		name         string
  2457  		variable     string
  2458  		variableName string
  2459  	}{
  2460  		{
  2461  			name:         "simple variable",
  2462  			variable:     "variableA",
  2463  			variableName: "variableA",
  2464  		},
  2465  		{
  2466  			name:         "variable object",
  2467  			variable:     "variableObject.field",
  2468  			variableName: "variableObject",
  2469  		},
  2470  		{
  2471  			name:         "variable array",
  2472  			variable:     "variableArray[0]",
  2473  			variableName: "variableArray",
  2474  		},
  2475  	}
  2476  
  2477  	for _, tt := range tests {
  2478  		t.Run(tt.name, func(t *testing.T) {
  2479  			g := NewWithT(t)
  2480  
  2481  			g.Expect(getVariableName(tt.variable)).To(Equal(tt.variableName))
  2482  		})
  2483  	}
  2484  }