sigs.k8s.io/cluster-api@v1.6.3/internal/topology/variables/clusterclass_variable_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 variables
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	. "github.com/onsi/gomega"
    24  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    25  	"k8s.io/apimachinery/pkg/util/validation/field"
    26  	"k8s.io/utils/pointer"
    27  
    28  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    29  )
    30  
    31  func Test_ValidateClusterClassVariables(t *testing.T) {
    32  	tests := []struct {
    33  		name                  string
    34  		clusterClassVariables []clusterv1.ClusterClassVariable
    35  		wantErr               bool
    36  	}{
    37  		{
    38  			name: "Error if multiple variables share a name",
    39  			clusterClassVariables: []clusterv1.ClusterClassVariable{
    40  				{
    41  					Name: "cpu",
    42  					Schema: clusterv1.VariableSchema{
    43  						OpenAPIV3Schema: clusterv1.JSONSchemaProps{
    44  							Type:    "integer",
    45  							Minimum: pointer.Int64(1),
    46  						},
    47  					},
    48  				},
    49  				{
    50  					Name: "cpu",
    51  					Schema: clusterv1.VariableSchema{
    52  						OpenAPIV3Schema: clusterv1.JSONSchemaProps{
    53  							Type:    "integer",
    54  							Minimum: pointer.Int64(1),
    55  						},
    56  					},
    57  				},
    58  			},
    59  			wantErr: true,
    60  		},
    61  		{
    62  			name: "Pass multiple variable validation",
    63  			clusterClassVariables: []clusterv1.ClusterClassVariable{
    64  				{
    65  					Name: "cpu",
    66  					Schema: clusterv1.VariableSchema{
    67  						OpenAPIV3Schema: clusterv1.JSONSchemaProps{
    68  							Type:    "integer",
    69  							Minimum: pointer.Int64(1),
    70  						},
    71  					},
    72  				},
    73  				{
    74  					Name: "validNumber",
    75  					Schema: clusterv1.VariableSchema{
    76  						OpenAPIV3Schema: clusterv1.JSONSchemaProps{
    77  							Type:    "number",
    78  							Maximum: pointer.Int64(1),
    79  						},
    80  					},
    81  				},
    82  
    83  				{
    84  					Name: "validVariable",
    85  					Schema: clusterv1.VariableSchema{
    86  						OpenAPIV3Schema: clusterv1.JSONSchemaProps{
    87  							Type:      "string",
    88  							MinLength: pointer.Int64(1),
    89  						},
    90  					},
    91  				},
    92  				{
    93  					Name: "location",
    94  					Schema: clusterv1.VariableSchema{
    95  						OpenAPIV3Schema: clusterv1.JSONSchemaProps{
    96  							Type:      "string",
    97  							MinLength: pointer.Int64(1),
    98  						},
    99  					},
   100  				},
   101  			},
   102  		},
   103  	}
   104  
   105  	for _, tt := range tests {
   106  		t.Run(tt.name, func(t *testing.T) {
   107  			g := NewWithT(t)
   108  
   109  			errList := ValidateClusterClassVariables(context.TODO(),
   110  				tt.clusterClassVariables,
   111  				field.NewPath("spec", "variables"))
   112  
   113  			if tt.wantErr {
   114  				g.Expect(errList).NotTo(BeEmpty())
   115  				return
   116  			}
   117  			g.Expect(errList).To(BeEmpty())
   118  		})
   119  	}
   120  }
   121  
   122  func Test_ValidateClusterClassVariable(t *testing.T) {
   123  	tests := []struct {
   124  		name                 string
   125  		clusterClassVariable *clusterv1.ClusterClassVariable
   126  		wantErr              bool
   127  	}{
   128  		{
   129  			name: "Valid integer schema",
   130  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   131  				Name: "cpu",
   132  				Schema: clusterv1.VariableSchema{
   133  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   134  						Type:    "integer",
   135  						Minimum: pointer.Int64(1),
   136  					},
   137  				},
   138  			},
   139  		},
   140  		{
   141  			name: "Valid string schema",
   142  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   143  				Name: "location",
   144  				Schema: clusterv1.VariableSchema{
   145  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   146  						Type:      "string",
   147  						MinLength: pointer.Int64(1),
   148  					},
   149  				},
   150  			},
   151  		},
   152  		{
   153  			name: "Valid variable name",
   154  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   155  				Name: "validVariable",
   156  				Schema: clusterv1.VariableSchema{
   157  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   158  						Type:      "string",
   159  						MinLength: pointer.Int64(1),
   160  					},
   161  				},
   162  			},
   163  		},
   164  		{
   165  			name: "fail on variable name is builtin",
   166  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   167  				Name: "builtin",
   168  				Schema: clusterv1.VariableSchema{
   169  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   170  						Type:      "string",
   171  						MinLength: pointer.Int64(1),
   172  					},
   173  				},
   174  			},
   175  			wantErr: true,
   176  		},
   177  		{
   178  			name: "fail on empty variable name",
   179  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   180  				Name: "",
   181  				Schema: clusterv1.VariableSchema{
   182  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   183  						Type:      "string",
   184  						MinLength: pointer.Int64(1),
   185  					},
   186  				},
   187  			},
   188  			wantErr: true,
   189  		},
   190  		{
   191  			name: "fail on variable name containing dot (.)",
   192  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   193  				Name: "path.tovariable",
   194  				Schema: clusterv1.VariableSchema{
   195  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   196  						Type:      "string",
   197  						MinLength: pointer.Int64(1),
   198  					},
   199  				},
   200  			},
   201  			wantErr: true,
   202  		},
   203  		{
   204  			name: "Valid default value regular string",
   205  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   206  				Name:     "var",
   207  				Required: true,
   208  				Schema: clusterv1.VariableSchema{
   209  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   210  						Type: "string",
   211  						Default: &apiextensionsv1.JSON{
   212  							Raw: []byte(`"defaultValue"`),
   213  						},
   214  					},
   215  				},
   216  			},
   217  		},
   218  		{
   219  			name: "fail on default value with invalid JSON",
   220  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   221  				Name:     "var",
   222  				Required: true,
   223  				Schema: clusterv1.VariableSchema{
   224  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   225  						Type: "string",
   226  						Default: &apiextensionsv1.JSON{
   227  							Raw: []byte(`"defaultValue": "value"`), // invalid JSON
   228  						},
   229  					},
   230  				},
   231  			},
   232  			wantErr: true,
   233  		},
   234  		{
   235  			name: "Valid example value regular string",
   236  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   237  				Name:     "var",
   238  				Required: true,
   239  				Schema: clusterv1.VariableSchema{
   240  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   241  						Type: "string",
   242  						Example: &apiextensionsv1.JSON{
   243  							Raw: []byte(`"exampleValue"`),
   244  						},
   245  					},
   246  				},
   247  			},
   248  		},
   249  		{
   250  			name: "fail on example value with invalid JSON",
   251  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   252  				Name:     "var",
   253  				Required: true,
   254  				Schema: clusterv1.VariableSchema{
   255  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   256  						Type: "string",
   257  						Example: &apiextensionsv1.JSON{
   258  							Raw: []byte(`"exampleValue": "value"`), // invalid JSON
   259  						},
   260  					},
   261  				},
   262  			},
   263  			wantErr: true,
   264  		},
   265  		{
   266  			name: "Valid enum values",
   267  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   268  				Name:     "var",
   269  				Required: true,
   270  				Schema: clusterv1.VariableSchema{
   271  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   272  						Type: "string",
   273  						Enum: []apiextensionsv1.JSON{
   274  							{Raw: []byte(`"enumValue1"`)},
   275  							{Raw: []byte(`"enumValue2"`)},
   276  						},
   277  					},
   278  				},
   279  			},
   280  		},
   281  		{
   282  			name: "fail on enum value with invalid JSON",
   283  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   284  				Name:     "var",
   285  				Required: true,
   286  				Schema: clusterv1.VariableSchema{
   287  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   288  						Type: "string",
   289  						Enum: []apiextensionsv1.JSON{
   290  							{Raw: []byte(`"defaultValue": "value"`)}, // invalid JSON
   291  						},
   292  					},
   293  				},
   294  			},
   295  			wantErr: true,
   296  		},
   297  		{
   298  			name: "fail on variable type is null",
   299  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   300  				Name: "var",
   301  				Schema: clusterv1.VariableSchema{
   302  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   303  						Type: "null",
   304  					},
   305  				},
   306  			},
   307  			wantErr: true,
   308  		},
   309  		{
   310  			name: "fail on variable type is not valid",
   311  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   312  				Name: "var",
   313  				Schema: clusterv1.VariableSchema{
   314  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   315  						Type: "invalidVariableType",
   316  					},
   317  				},
   318  			},
   319  			wantErr: true,
   320  		},
   321  		{
   322  			name: "fail on variable type length zero",
   323  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   324  				Name: "var",
   325  				Schema: clusterv1.VariableSchema{
   326  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   327  						Type: "",
   328  					},
   329  				},
   330  			},
   331  			wantErr: true,
   332  		},
   333  		{
   334  			name: "Valid object schema",
   335  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   336  				Name:     "httpProxy",
   337  				Required: true,
   338  				Schema: clusterv1.VariableSchema{
   339  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   340  						Type: "object",
   341  						Properties: map[string]clusterv1.JSONSchemaProps{
   342  							"enabled": {
   343  								Type:    "boolean",
   344  								Default: &apiextensionsv1.JSON{Raw: []byte(`false`)},
   345  							},
   346  							"url": {
   347  								Type: "string",
   348  							},
   349  							"noProxy": {
   350  								Type: "string",
   351  							},
   352  						},
   353  					},
   354  				},
   355  			},
   356  		},
   357  		{
   358  			name: "fail on invalid object schema",
   359  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   360  				Name:     "httpProxy",
   361  				Required: true,
   362  				Schema: clusterv1.VariableSchema{
   363  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   364  						Type: "object",
   365  						Properties: map[string]clusterv1.JSONSchemaProps{
   366  							"enabled": {
   367  								Type:    "boolean",
   368  								Default: &apiextensionsv1.JSON{Raw: []byte(`false`)},
   369  							},
   370  							"url": {
   371  								Type: "string",
   372  							},
   373  							"noProxy": {
   374  								Type: "invalidType", // invalid type.
   375  							},
   376  						},
   377  					},
   378  				},
   379  			},
   380  			wantErr: true,
   381  		},
   382  		{
   383  			name: "Valid map schema",
   384  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   385  				Name:     "httpProxy",
   386  				Required: true,
   387  				Schema: clusterv1.VariableSchema{
   388  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   389  						Type: "object",
   390  						AdditionalProperties: &clusterv1.JSONSchemaProps{
   391  							Type: "object",
   392  							Properties: map[string]clusterv1.JSONSchemaProps{
   393  								"enabled": {
   394  									Type:    "boolean",
   395  									Default: &apiextensionsv1.JSON{Raw: []byte(`false`)},
   396  								},
   397  								"url": {
   398  									Type: "string",
   399  								},
   400  								"noProxy": {
   401  									Type: "string",
   402  								},
   403  							},
   404  						},
   405  					},
   406  				},
   407  			},
   408  		},
   409  		{
   410  			name: "fail on invalid map schema",
   411  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   412  				Name:     "httpProxy",
   413  				Required: true,
   414  				Schema: clusterv1.VariableSchema{
   415  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   416  						Type: "object",
   417  						AdditionalProperties: &clusterv1.JSONSchemaProps{
   418  							Type: "object",
   419  							Properties: map[string]clusterv1.JSONSchemaProps{
   420  								"enabled": {
   421  									Type:    "boolean",
   422  									Default: &apiextensionsv1.JSON{Raw: []byte(`false`)},
   423  								},
   424  								"url": {
   425  									Type: "string",
   426  								},
   427  								"noProxy": {
   428  									Type: "invalidType", // invalid type.
   429  								},
   430  							},
   431  						},
   432  					},
   433  				},
   434  			},
   435  			wantErr: true,
   436  		},
   437  		{
   438  			name: "fail on object (properties) and map (additionalProperties) both set",
   439  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   440  				Name:     "httpProxy",
   441  				Required: true,
   442  				Schema: clusterv1.VariableSchema{
   443  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   444  						Type: "object",
   445  						AdditionalProperties: &clusterv1.JSONSchemaProps{
   446  							Type: "object",
   447  							Properties: map[string]clusterv1.JSONSchemaProps{
   448  								"enabled": {
   449  									Type:    "boolean",
   450  									Default: &apiextensionsv1.JSON{Raw: []byte(`false`)},
   451  								},
   452  							},
   453  						},
   454  						Properties: map[string]clusterv1.JSONSchemaProps{
   455  							"enabled": {
   456  								Type:    "boolean",
   457  								Default: &apiextensionsv1.JSON{Raw: []byte(`false`)},
   458  							},
   459  						},
   460  					},
   461  				},
   462  			},
   463  			wantErr: true,
   464  		},
   465  		{
   466  			name: "Valid array schema",
   467  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   468  				Name:     "arrayVariable",
   469  				Required: true,
   470  				Schema: clusterv1.VariableSchema{
   471  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   472  						Type: "array",
   473  						Items: &clusterv1.JSONSchemaProps{
   474  							Type:    "boolean",
   475  							Default: &apiextensionsv1.JSON{Raw: []byte(`false`)},
   476  						},
   477  					},
   478  				},
   479  			},
   480  		},
   481  		{
   482  			name: "fail on invalid array schema",
   483  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   484  				Name: "arrayVariable",
   485  				Schema: clusterv1.VariableSchema{
   486  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   487  						Type: "array",
   488  						Items: &clusterv1.JSONSchemaProps{
   489  							Type:    "string",
   490  							Default: &apiextensionsv1.JSON{Raw: []byte(`invalidString`)}, // missing quotes.
   491  						},
   492  					},
   493  				},
   494  			},
   495  			wantErr: true,
   496  		},
   497  		{
   498  			name: "pass on variable with required set true with a default defined",
   499  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   500  				Name:     "var",
   501  				Required: true,
   502  				Schema: clusterv1.VariableSchema{
   503  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   504  						Type:    "string",
   505  						Default: &apiextensionsv1.JSON{Raw: []byte(`"defaultValue"`)},
   506  					},
   507  				},
   508  			},
   509  		},
   510  		{
   511  			name: "pass on variable with a default that is valid by the given schema",
   512  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   513  				Name: "var",
   514  				Schema: clusterv1.VariableSchema{
   515  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   516  						Type:      "string",
   517  						MaxLength: pointer.Int64(6),
   518  						Default:   &apiextensionsv1.JSON{Raw: []byte(`"short"`)},
   519  					},
   520  				},
   521  			},
   522  		},
   523  		{
   524  			name: "fail on variable with a default that is invalid by the given schema",
   525  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   526  				Name: "var",
   527  				Schema: clusterv1.VariableSchema{
   528  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   529  						Type:      "string",
   530  						MaxLength: pointer.Int64(6),
   531  						Default:   &apiextensionsv1.JSON{Raw: []byte(`"veryLongValueIsInvalid"`)},
   532  					},
   533  				},
   534  			},
   535  			wantErr: true,
   536  		},
   537  		{
   538  			name: "pass if variable is an object with default value valid by the given schema",
   539  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   540  				Name: "var",
   541  				Schema: clusterv1.VariableSchema{
   542  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   543  						Type: "object",
   544  						Properties: map[string]clusterv1.JSONSchemaProps{
   545  							"spec": {
   546  								Type: "object",
   547  								Properties: map[string]clusterv1.JSONSchemaProps{
   548  									"replicas": {
   549  										Type:    "integer",
   550  										Default: &apiextensionsv1.JSON{Raw: []byte(`100`)},
   551  										Minimum: pointer.Int64(1),
   552  									},
   553  								},
   554  							},
   555  						},
   556  					},
   557  				},
   558  			},
   559  		},
   560  		{
   561  			name: "fail if variable is an object with default value invalidated by the given schema",
   562  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   563  				Name: "var",
   564  				Schema: clusterv1.VariableSchema{
   565  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   566  						Type: "object",
   567  						Properties: map[string]clusterv1.JSONSchemaProps{
   568  							"spec": {
   569  								Type: "object",
   570  								Properties: map[string]clusterv1.JSONSchemaProps{
   571  									"replicas": {
   572  										Type:    "integer",
   573  										Default: &apiextensionsv1.JSON{Raw: []byte(`-100`)},
   574  										Minimum: pointer.Int64(1),
   575  									},
   576  								},
   577  							},
   578  						},
   579  					},
   580  				},
   581  			},
   582  			wantErr: true,
   583  		},
   584  		{
   585  			name: "fail if variable is an object with a top level default value invalidated by the given schema",
   586  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   587  				Name: "var",
   588  				Schema: clusterv1.VariableSchema{
   589  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   590  						Type: "object",
   591  						Default: &apiextensionsv1.JSON{
   592  							Raw: []byte(`{"spec":{"replicas": -100}}`),
   593  						},
   594  						Properties: map[string]clusterv1.JSONSchemaProps{
   595  							"spec": {
   596  								Type: "object",
   597  								Properties: map[string]clusterv1.JSONSchemaProps{
   598  									"replicas": {
   599  										Type:    "integer",
   600  										Minimum: pointer.Int64(1),
   601  									},
   602  								},
   603  							},
   604  						},
   605  					},
   606  				},
   607  			},
   608  			wantErr: true,
   609  		},
   610  		{
   611  			name: "pass if variable is an object with a top level default value valid by the given schema",
   612  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   613  				Name: "var",
   614  				Schema: clusterv1.VariableSchema{
   615  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   616  						Type: "object",
   617  						Default: &apiextensionsv1.JSON{
   618  							Raw: []byte(`{"spec":{"replicas": 100}}`),
   619  						},
   620  						Properties: map[string]clusterv1.JSONSchemaProps{
   621  							"spec": {
   622  								Type: "object",
   623  								Properties: map[string]clusterv1.JSONSchemaProps{
   624  									"replicas": {
   625  										Type:    "integer",
   626  										Minimum: pointer.Int64(1),
   627  									},
   628  								},
   629  							},
   630  						},
   631  					},
   632  				},
   633  			},
   634  		},
   635  		{
   636  			name: "fail if field is required below properties and sets a default that misses the field",
   637  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   638  				Name: "var",
   639  				Schema: clusterv1.VariableSchema{
   640  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   641  						Type: "object",
   642  						Properties: map[string]clusterv1.JSONSchemaProps{
   643  							"spec": {
   644  								Type:     "object",
   645  								Required: []string{"replicas"},
   646  								Default: &apiextensionsv1.JSON{
   647  									// replicas missing results in failure
   648  									Raw: []byte(`{"value": 100}`),
   649  								},
   650  								Properties: map[string]clusterv1.JSONSchemaProps{
   651  									"replicas": {
   652  										Type:    "integer",
   653  										Default: &apiextensionsv1.JSON{Raw: []byte(`100`)},
   654  										Minimum: pointer.Int64(1),
   655  									},
   656  									"value": {
   657  										Type:    "integer",
   658  										Default: &apiextensionsv1.JSON{Raw: []byte(`100`)},
   659  										Minimum: pointer.Int64(1),
   660  									},
   661  								},
   662  							},
   663  						},
   664  					},
   665  				},
   666  			},
   667  			wantErr: true,
   668  		},
   669  		{
   670  			name: "pass if field is required below properties and sets a default",
   671  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   672  				Name: "var",
   673  				Schema: clusterv1.VariableSchema{
   674  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   675  						Type: "object",
   676  						Properties: map[string]clusterv1.JSONSchemaProps{
   677  							"spec": {
   678  								Type:     "object",
   679  								Required: []string{"replicas"},
   680  								Default: &apiextensionsv1.JSON{
   681  									// replicas is set here so the `required` property is met.
   682  									Raw: []byte(`{"replicas": 100}`),
   683  								},
   684  								Properties: map[string]clusterv1.JSONSchemaProps{
   685  									"replicas": {
   686  										Type:    "integer",
   687  										Default: &apiextensionsv1.JSON{Raw: []byte(`100`)},
   688  										Minimum: pointer.Int64(1),
   689  									},
   690  								},
   691  							},
   692  						},
   693  					},
   694  				},
   695  			},
   696  		},
   697  
   698  		{
   699  			name: "pass on variable with an example that is valid by the given schema",
   700  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   701  				Name: "var",
   702  				Schema: clusterv1.VariableSchema{
   703  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   704  						Type:      "string",
   705  						MaxLength: pointer.Int64(6),
   706  						Example:   &apiextensionsv1.JSON{Raw: []byte(`"short"`)},
   707  					},
   708  				},
   709  			},
   710  		},
   711  		{
   712  			name: "fail on variable with an example that is invalid by the given schema",
   713  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   714  				Name: "var",
   715  				Schema: clusterv1.VariableSchema{
   716  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   717  						Type:      "string",
   718  						MaxLength: pointer.Int64(6),
   719  						Example:   &apiextensionsv1.JSON{Raw: []byte(`"veryLongValueIsInvalid"`)},
   720  					},
   721  				},
   722  			},
   723  			wantErr: true,
   724  		},
   725  		{
   726  			name: "pass if variable is an object with an example valid by the given schema",
   727  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   728  				Name: "var",
   729  				Schema: clusterv1.VariableSchema{
   730  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   731  						Type: "object",
   732  						Properties: map[string]clusterv1.JSONSchemaProps{
   733  							"spec": {
   734  								Type: "object",
   735  								Properties: map[string]clusterv1.JSONSchemaProps{
   736  									"replicas": {
   737  										Type:    "integer",
   738  										Minimum: pointer.Int64(0),
   739  										Example: &apiextensionsv1.JSON{Raw: []byte(`100`)},
   740  									},
   741  								},
   742  							},
   743  						},
   744  					},
   745  				},
   746  			},
   747  		},
   748  		{
   749  			name: "fail if variable is an object with an example invalidated by the given schema",
   750  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   751  				Name: "var",
   752  				Schema: clusterv1.VariableSchema{
   753  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   754  						Type: "object",
   755  						Properties: map[string]clusterv1.JSONSchemaProps{
   756  							"spec": {
   757  								Type: "object",
   758  								Properties: map[string]clusterv1.JSONSchemaProps{
   759  									"replicas": {
   760  										Type:    "integer",
   761  										Minimum: pointer.Int64(0),
   762  										Example: &apiextensionsv1.JSON{Raw: []byte(`-100`)},
   763  									},
   764  								},
   765  							},
   766  						},
   767  					},
   768  				},
   769  			},
   770  			wantErr: true,
   771  		},
   772  		{
   773  			name: "fail if variable is an object with a top level example value invalidated by the given schema",
   774  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   775  				Name: "var",
   776  				Schema: clusterv1.VariableSchema{
   777  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   778  						Type: "object",
   779  						Example: &apiextensionsv1.JSON{
   780  							Raw: []byte(`{"spec":{"replicas": -100}}`),
   781  						},
   782  						Properties: map[string]clusterv1.JSONSchemaProps{
   783  							"spec": {
   784  								Type: "object",
   785  								Properties: map[string]clusterv1.JSONSchemaProps{
   786  									"replicas": {
   787  										Type:    "integer",
   788  										Minimum: pointer.Int64(1),
   789  									},
   790  								},
   791  							},
   792  						},
   793  					},
   794  				},
   795  			},
   796  			wantErr: true,
   797  		},
   798  		{
   799  			name: "pass if variable is an object with a top level default value valid by the given schema",
   800  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   801  				Name: "var",
   802  				Schema: clusterv1.VariableSchema{
   803  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   804  						Type: "object",
   805  						Example: &apiextensionsv1.JSON{
   806  							Raw: []byte(`{"spec":{"replicas": 100}}`),
   807  						},
   808  						Properties: map[string]clusterv1.JSONSchemaProps{
   809  							"spec": {
   810  								Type: "object",
   811  								Properties: map[string]clusterv1.JSONSchemaProps{
   812  									"replicas": {
   813  										Type:    "integer",
   814  										Minimum: pointer.Int64(1),
   815  									},
   816  								},
   817  							},
   818  						},
   819  					},
   820  				},
   821  			},
   822  		},
   823  		{
   824  			name: "pass on variable with an enum with all variables valid by the given schema",
   825  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   826  				Name: "var",
   827  				Schema: clusterv1.VariableSchema{
   828  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   829  						Type:      "string",
   830  						MaxLength: pointer.Int64(6),
   831  						Enum: []apiextensionsv1.JSON{
   832  							{Raw: []byte(`"short1"`)},
   833  							{Raw: []byte(`"short2"`)},
   834  						},
   835  					},
   836  				},
   837  			},
   838  		},
   839  		{
   840  			name: "fail on variable with an enum with a value that is invalid by the given schema",
   841  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   842  				Name: "var",
   843  				Schema: clusterv1.VariableSchema{
   844  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   845  						Type:      "string",
   846  						MaxLength: pointer.Int64(6),
   847  						Enum: []apiextensionsv1.JSON{
   848  							{Raw: []byte(`"veryLongValueIsInvalid"`)},
   849  							{Raw: []byte(`"short"`)},
   850  						},
   851  					},
   852  				},
   853  			},
   854  			wantErr: true,
   855  		},
   856  		{
   857  			name: "pass if variable is an object with an enum value that is valid by the given schema",
   858  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   859  				Name: "var",
   860  				Schema: clusterv1.VariableSchema{
   861  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   862  						Type: "object",
   863  						Properties: map[string]clusterv1.JSONSchemaProps{
   864  							"spec": {
   865  								Type: "object",
   866  								Properties: map[string]clusterv1.JSONSchemaProps{
   867  									"replicas": {
   868  										Type:    "integer",
   869  										Minimum: pointer.Int64(0),
   870  										Enum: []apiextensionsv1.JSON{
   871  											{Raw: []byte(`100`)},
   872  											{Raw: []byte(`5`)},
   873  										},
   874  									},
   875  								},
   876  							},
   877  						},
   878  					},
   879  				},
   880  			},
   881  		},
   882  		{
   883  			name: "fail if variable is an object with an enum value invalidated by the given schema",
   884  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   885  				Name: "var",
   886  				Schema: clusterv1.VariableSchema{
   887  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   888  						Type: "object",
   889  						Properties: map[string]clusterv1.JSONSchemaProps{
   890  							"spec": {
   891  								Type: "object",
   892  								Properties: map[string]clusterv1.JSONSchemaProps{
   893  									"replicas": {
   894  										Type:    "integer",
   895  										Minimum: pointer.Int64(0),
   896  										Enum: []apiextensionsv1.JSON{
   897  											{Raw: []byte(`100`)},
   898  											{Raw: []byte(`-100`)},
   899  										},
   900  									},
   901  								},
   902  							},
   903  						},
   904  					},
   905  				},
   906  			},
   907  			wantErr: true,
   908  		},
   909  		{
   910  			name: "fail if variable is an object with a top level enum value invalidated by the given schema",
   911  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   912  				Name: "var",
   913  				Schema: clusterv1.VariableSchema{
   914  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   915  						Type: "object",
   916  						Enum: []apiextensionsv1.JSON{
   917  							{
   918  								Raw: []byte(`{"spec":{"replicas": 100}}`),
   919  							},
   920  							{
   921  								Raw: []byte(`{"spec":{"replicas": -100}}`),
   922  							},
   923  						},
   924  						Properties: map[string]clusterv1.JSONSchemaProps{
   925  							"spec": {
   926  								Type: "object",
   927  								Properties: map[string]clusterv1.JSONSchemaProps{
   928  									"replicas": {
   929  										Type:    "integer",
   930  										Minimum: pointer.Int64(1),
   931  									},
   932  								},
   933  							},
   934  						},
   935  					},
   936  				},
   937  			},
   938  			wantErr: true,
   939  		},
   940  		{
   941  			name: "pass if variable is an object with a top level enum value that is valid by the given schema",
   942  			clusterClassVariable: &clusterv1.ClusterClassVariable{
   943  				Name: "var",
   944  				Schema: clusterv1.VariableSchema{
   945  					OpenAPIV3Schema: clusterv1.JSONSchemaProps{
   946  						Type: "object",
   947  						Enum: []apiextensionsv1.JSON{
   948  							{
   949  								Raw: []byte(`{"spec":{"replicas": 100}}`),
   950  							},
   951  							{
   952  								Raw: []byte(`{"spec":{"replicas": 200}}`),
   953  							},
   954  						},
   955  						Properties: map[string]clusterv1.JSONSchemaProps{
   956  							"spec": {
   957  								Type: "object",
   958  								Properties: map[string]clusterv1.JSONSchemaProps{
   959  									"replicas": {
   960  										Type:    "integer",
   961  										Minimum: pointer.Int64(1),
   962  									},
   963  								},
   964  							},
   965  						},
   966  					},
   967  				},
   968  			},
   969  		},
   970  	}
   971  	for _, tt := range tests {
   972  		t.Run(tt.name, func(t *testing.T) {
   973  			g := NewWithT(t)
   974  
   975  			errList := validateClusterClassVariable(context.TODO(),
   976  				tt.clusterClassVariable,
   977  				field.NewPath("spec", "variables").Index(0))
   978  
   979  			if tt.wantErr {
   980  				g.Expect(errList).NotTo(BeEmpty())
   981  				return
   982  			}
   983  			g.Expect(errList).To(BeEmpty())
   984  		})
   985  	}
   986  }