...

Source file src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation_test.go

Documentation: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"context"
    21  	"math"
    22  	"math/rand"
    23  	"reflect"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/google/cel-go/cel"
    28  
    29  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
    30  	apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
    31  	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    32  	structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
    33  	celschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
    34  	apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
    35  	"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
    36  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    37  	"k8s.io/apimachinery/pkg/runtime"
    38  	"k8s.io/apimachinery/pkg/runtime/serializer"
    39  	"k8s.io/apimachinery/pkg/util/json"
    40  	"k8s.io/apimachinery/pkg/util/validation/field"
    41  	"k8s.io/apimachinery/pkg/util/version"
    42  	"k8s.io/apiserver/pkg/cel/environment"
    43  	"k8s.io/apiserver/pkg/cel/library"
    44  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    45  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    46  	"k8s.io/utils/pointer"
    47  	"k8s.io/utils/ptr"
    48  )
    49  
    50  type validationMatch struct {
    51  	path           *field.Path
    52  	errorType      field.ErrorType
    53  	containsString string
    54  }
    55  
    56  func required(path ...string) validationMatch {
    57  	return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeRequired}
    58  }
    59  func invalid(path ...string) validationMatch {
    60  	return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeInvalid}
    61  }
    62  func invalidtypecode(path ...string) validationMatch {
    63  	return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeTypeInvalid}
    64  }
    65  func invalidIndex(index int, path ...string) validationMatch {
    66  	return validationMatch{path: field.NewPath(path[0], path[1:]...).Index(index), errorType: field.ErrorTypeInvalid}
    67  }
    68  func unsupported(path ...string) validationMatch {
    69  	return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeNotSupported}
    70  }
    71  func immutable(path ...string) validationMatch {
    72  	return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeInvalid}
    73  }
    74  func forbidden(path ...string) validationMatch {
    75  	return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeForbidden}
    76  }
    77  func duplicate(path ...string) validationMatch {
    78  	return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeDuplicate}
    79  }
    80  func tooMany(path ...string) validationMatch {
    81  	return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeTooMany}
    82  }
    83  
    84  func (v validationMatch) matches(err *field.Error) bool {
    85  	return err.Type == v.errorType && err.Field == v.path.String() && strings.Contains(err.Error(), v.containsString)
    86  }
    87  
    88  func (v validationMatch) contains(s string) validationMatch {
    89  	v.containsString = s
    90  	return v
    91  }
    92  
    93  func strPtr(s string) *string { return &s }
    94  
    95  func TestValidateCustomResourceDefinition(t *testing.T) {
    96  	singleVersionList := []apiextensions.CustomResourceDefinitionVersion{
    97  		{
    98  			Name:    "version",
    99  			Served:  true,
   100  			Storage: true,
   101  		},
   102  	}
   103  	tests := []struct {
   104  		name     string
   105  		resource *apiextensions.CustomResourceDefinition
   106  		errors   []validationMatch
   107  	}{
   108  		{
   109  			name: "invalid types disallowed",
   110  			resource: &apiextensions.CustomResourceDefinition{
   111  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   112  				Spec: apiextensions.CustomResourceDefinitionSpec{
   113  					Group: "group.com",
   114  					Scope: apiextensions.ResourceScope("Cluster"),
   115  					Names: apiextensions.CustomResourceDefinitionNames{
   116  						Plural:   "plural",
   117  						Singular: "singular",
   118  						Kind:     "Plural",
   119  						ListKind: "PluralList",
   120  					},
   121  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
   122  					Validation: &apiextensions.CustomResourceValidation{
   123  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   124  							Type:       "object",
   125  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "bogus"}},
   126  						},
   127  					},
   128  					PreserveUnknownFields: pointer.BoolPtr(false),
   129  				},
   130  				Status: apiextensions.CustomResourceDefinitionStatus{
   131  					StoredVersions: []string{"version"},
   132  				},
   133  			},
   134  			errors: []validationMatch{
   135  				unsupported("spec.validation.openAPIV3Schema.properties[foo].type"),
   136  			},
   137  		},
   138  		{
   139  			name: "webhookconfig: invalid port 0",
   140  			resource: &apiextensions.CustomResourceDefinition{
   141  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   142  				Spec: apiextensions.CustomResourceDefinitionSpec{
   143  					Group: "group.com",
   144  					Scope: apiextensions.ResourceScope("Cluster"),
   145  					Names: apiextensions.CustomResourceDefinitionNames{
   146  						Plural:   "plural",
   147  						Singular: "singular",
   148  						Kind:     "Plural",
   149  						ListKind: "PluralList",
   150  					},
   151  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   152  						{
   153  							Name:    "version",
   154  							Served:  true,
   155  							Storage: true,
   156  						},
   157  						{
   158  							Name:    "version2",
   159  							Served:  true,
   160  							Storage: false,
   161  						},
   162  					},
   163  					Conversion: &apiextensions.CustomResourceConversion{
   164  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
   165  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
   166  							Service: &apiextensions.ServiceReference{
   167  								Name:      "n",
   168  								Namespace: "ns",
   169  								Port:      0,
   170  							},
   171  						},
   172  					},
   173  					Validation: &apiextensions.CustomResourceValidation{
   174  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   175  							Type: "object",
   176  						},
   177  					},
   178  					PreserveUnknownFields: pointer.BoolPtr(false),
   179  				},
   180  				Status: apiextensions.CustomResourceDefinitionStatus{
   181  					StoredVersions: []string{"version"},
   182  				},
   183  			},
   184  			errors: []validationMatch{
   185  				invalid("spec", "conversion", "webhookClientConfig", "service", "port"),
   186  			},
   187  		},
   188  		{
   189  			name: "webhookconfig: invalid port 65536",
   190  			resource: &apiextensions.CustomResourceDefinition{
   191  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   192  				Spec: apiextensions.CustomResourceDefinitionSpec{
   193  					Group: "group.com",
   194  					Scope: apiextensions.ResourceScope("Cluster"),
   195  					Names: apiextensions.CustomResourceDefinitionNames{
   196  						Plural:   "plural",
   197  						Singular: "singular",
   198  						Kind:     "Plural",
   199  						ListKind: "PluralList",
   200  					},
   201  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   202  						{
   203  							Name:    "version",
   204  							Served:  true,
   205  							Storage: true,
   206  						},
   207  						{
   208  							Name:    "version2",
   209  							Served:  true,
   210  							Storage: false,
   211  						},
   212  					},
   213  					Conversion: &apiextensions.CustomResourceConversion{
   214  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
   215  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
   216  							Service: &apiextensions.ServiceReference{
   217  								Name:      "n",
   218  								Namespace: "ns",
   219  								Port:      65536,
   220  							},
   221  						},
   222  					},
   223  					Validation: &apiextensions.CustomResourceValidation{
   224  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   225  							Type: "object",
   226  						},
   227  					},
   228  					PreserveUnknownFields: pointer.BoolPtr(false),
   229  				},
   230  				Status: apiextensions.CustomResourceDefinitionStatus{
   231  					StoredVersions: []string{"version"},
   232  				},
   233  			},
   234  			errors: []validationMatch{
   235  				invalid("spec", "conversion", "webhookClientConfig", "service", "port"),
   236  			},
   237  		},
   238  		{
   239  			name: "webhookconfig: both service and URL provided",
   240  			resource: &apiextensions.CustomResourceDefinition{
   241  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   242  				Spec: apiextensions.CustomResourceDefinitionSpec{
   243  					Group: "group.com",
   244  					Scope: apiextensions.ResourceScope("Cluster"),
   245  					Names: apiextensions.CustomResourceDefinitionNames{
   246  						Plural:   "plural",
   247  						Singular: "singular",
   248  						Kind:     "Plural",
   249  						ListKind: "PluralList",
   250  					},
   251  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   252  						{
   253  							Name:    "version",
   254  							Served:  true,
   255  							Storage: true,
   256  						},
   257  						{
   258  							Name:    "version2",
   259  							Served:  true,
   260  							Storage: false,
   261  						},
   262  					},
   263  					Conversion: &apiextensions.CustomResourceConversion{
   264  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
   265  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
   266  							URL: strPtr("https://example.com/webhook"),
   267  							Service: &apiextensions.ServiceReference{
   268  								Name:      "n",
   269  								Namespace: "ns",
   270  							},
   271  						},
   272  					},
   273  					Validation: &apiextensions.CustomResourceValidation{
   274  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   275  							Type: "object",
   276  						},
   277  					},
   278  					PreserveUnknownFields: pointer.BoolPtr(false),
   279  				},
   280  				Status: apiextensions.CustomResourceDefinitionStatus{
   281  					StoredVersions: []string{"version"},
   282  				},
   283  			},
   284  			errors: []validationMatch{
   285  				required("spec", "conversion", "webhookClientConfig"),
   286  			},
   287  		},
   288  		{
   289  			name: "webhookconfig: blank URL",
   290  			resource: &apiextensions.CustomResourceDefinition{
   291  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   292  				Spec: apiextensions.CustomResourceDefinitionSpec{
   293  					Group: "group.com",
   294  					Scope: apiextensions.ResourceScope("Cluster"),
   295  					Names: apiextensions.CustomResourceDefinitionNames{
   296  						Plural:   "plural",
   297  						Singular: "singular",
   298  						Kind:     "Plural",
   299  						ListKind: "PluralList",
   300  					},
   301  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   302  						{
   303  							Name:    "version",
   304  							Served:  true,
   305  							Storage: true,
   306  						},
   307  						{
   308  							Name:    "version2",
   309  							Served:  true,
   310  							Storage: false,
   311  						},
   312  					},
   313  					Conversion: &apiextensions.CustomResourceConversion{
   314  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
   315  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
   316  							URL: strPtr(""),
   317  						},
   318  					},
   319  					Validation: &apiextensions.CustomResourceValidation{
   320  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   321  							Type: "object",
   322  						},
   323  					},
   324  					PreserveUnknownFields: pointer.BoolPtr(false),
   325  				},
   326  				Status: apiextensions.CustomResourceDefinitionStatus{
   327  					StoredVersions: []string{"version"},
   328  				},
   329  			},
   330  			errors: []validationMatch{
   331  				invalid("spec", "conversion", "webhookClientConfig", "url"),
   332  				invalid("spec", "conversion", "webhookClientConfig", "url"),
   333  			},
   334  		},
   335  		{
   336  			name: "webhookconfig_should_not_be_set",
   337  			resource: &apiextensions.CustomResourceDefinition{
   338  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   339  				Spec: apiextensions.CustomResourceDefinitionSpec{
   340  					Group: "group.com",
   341  					Scope: apiextensions.ResourceScope("Cluster"),
   342  					Names: apiextensions.CustomResourceDefinitionNames{
   343  						Plural:   "plural",
   344  						Singular: "singular",
   345  						Kind:     "Plural",
   346  						ListKind: "PluralList",
   347  					},
   348  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   349  						{
   350  							Name:    "version",
   351  							Served:  true,
   352  							Storage: true,
   353  						},
   354  						{
   355  							Name:    "version2",
   356  							Served:  true,
   357  							Storage: false,
   358  						},
   359  					},
   360  					Conversion: &apiextensions.CustomResourceConversion{
   361  						Strategy: apiextensions.ConversionStrategyType("None"),
   362  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
   363  							URL: strPtr("https://example.com/webhook"),
   364  						},
   365  					},
   366  					PreserveUnknownFields: pointer.BoolPtr(false),
   367  					Validation: &apiextensions.CustomResourceValidation{
   368  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   369  							Type: "object",
   370  						},
   371  					},
   372  				},
   373  				Status: apiextensions.CustomResourceDefinitionStatus{
   374  					StoredVersions: []string{"version"},
   375  				},
   376  			},
   377  			errors: []validationMatch{
   378  				forbidden("spec", "conversion", "webhookClientConfig"),
   379  			},
   380  		},
   381  		{
   382  			name: "ConversionReviewVersions_should_not_be_set",
   383  			resource: &apiextensions.CustomResourceDefinition{
   384  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   385  				Spec: apiextensions.CustomResourceDefinitionSpec{
   386  					Group: "group.com",
   387  					Scope: apiextensions.ResourceScope("Cluster"),
   388  					Names: apiextensions.CustomResourceDefinitionNames{
   389  						Plural:   "plural",
   390  						Singular: "singular",
   391  						Kind:     "Plural",
   392  						ListKind: "PluralList",
   393  					},
   394  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   395  						{
   396  							Name:    "version",
   397  							Served:  true,
   398  							Storage: true,
   399  						},
   400  						{
   401  							Name:    "version2",
   402  							Served:  true,
   403  							Storage: false,
   404  						},
   405  					},
   406  					Conversion: &apiextensions.CustomResourceConversion{
   407  						Strategy:                 apiextensions.ConversionStrategyType("None"),
   408  						ConversionReviewVersions: []string{"v1beta1"},
   409  					},
   410  					PreserveUnknownFields: pointer.BoolPtr(false),
   411  					Validation: &apiextensions.CustomResourceValidation{
   412  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   413  							Type: "object",
   414  						},
   415  					},
   416  				},
   417  				Status: apiextensions.CustomResourceDefinitionStatus{
   418  					StoredVersions: []string{"version"},
   419  				},
   420  			},
   421  			errors: []validationMatch{
   422  				forbidden("spec", "conversion", "conversionReviewVersions"),
   423  			},
   424  		},
   425  		{
   426  			name: "webhookconfig: invalid ConversionReviewVersion",
   427  			resource: &apiextensions.CustomResourceDefinition{
   428  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   429  				Spec: apiextensions.CustomResourceDefinitionSpec{
   430  					Group: "group.com",
   431  					Scope: apiextensions.ResourceScope("Cluster"),
   432  					Names: apiextensions.CustomResourceDefinitionNames{
   433  						Plural:   "plural",
   434  						Singular: "singular",
   435  						Kind:     "Plural",
   436  						ListKind: "PluralList",
   437  					},
   438  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   439  						{
   440  							Name:    "version",
   441  							Served:  true,
   442  							Storage: true,
   443  						},
   444  						{
   445  							Name:    "version2",
   446  							Served:  true,
   447  							Storage: false,
   448  						},
   449  					},
   450  					Conversion: &apiextensions.CustomResourceConversion{
   451  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
   452  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
   453  							URL: strPtr("https://example.com/webhook"),
   454  						},
   455  						ConversionReviewVersions: []string{"invalid-version"},
   456  					},
   457  					Validation: &apiextensions.CustomResourceValidation{
   458  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   459  							Type: "object",
   460  						},
   461  					},
   462  					PreserveUnknownFields: pointer.BoolPtr(false),
   463  				},
   464  				Status: apiextensions.CustomResourceDefinitionStatus{
   465  					StoredVersions: []string{"version"},
   466  				},
   467  			},
   468  			errors: []validationMatch{
   469  				invalid("spec", "conversion", "conversionReviewVersions"),
   470  			},
   471  		},
   472  		{
   473  			name: "webhookconfig: invalid ConversionReviewVersion version string",
   474  			resource: &apiextensions.CustomResourceDefinition{
   475  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   476  				Spec: apiextensions.CustomResourceDefinitionSpec{
   477  					Group: "group.com",
   478  					Scope: apiextensions.ResourceScope("Cluster"),
   479  					Names: apiextensions.CustomResourceDefinitionNames{
   480  						Plural:   "plural",
   481  						Singular: "singular",
   482  						Kind:     "Plural",
   483  						ListKind: "PluralList",
   484  					},
   485  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   486  						{
   487  							Name:    "version",
   488  							Served:  true,
   489  							Storage: true,
   490  						},
   491  						{
   492  							Name:    "version2",
   493  							Served:  true,
   494  							Storage: false,
   495  						},
   496  					},
   497  					Conversion: &apiextensions.CustomResourceConversion{
   498  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
   499  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
   500  							URL: strPtr("https://example.com/webhook"),
   501  						},
   502  						ConversionReviewVersions: []string{"0v"},
   503  					},
   504  					Validation: &apiextensions.CustomResourceValidation{
   505  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   506  							Type: "object",
   507  						},
   508  					},
   509  					PreserveUnknownFields: pointer.BoolPtr(false),
   510  				},
   511  				Status: apiextensions.CustomResourceDefinitionStatus{
   512  					StoredVersions: []string{"version"},
   513  				},
   514  			},
   515  			errors: []validationMatch{
   516  				invalidIndex(0, "spec", "conversion", "conversionReviewVersions"),
   517  				invalid("spec", "conversion", "conversionReviewVersions"),
   518  			},
   519  		},
   520  		{
   521  			name: "webhookconfig: at least one valid ConversionReviewVersion",
   522  			resource: &apiextensions.CustomResourceDefinition{
   523  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   524  				Spec: apiextensions.CustomResourceDefinitionSpec{
   525  					Group: "group.com",
   526  					Scope: apiextensions.ResourceScope("Cluster"),
   527  					Names: apiextensions.CustomResourceDefinitionNames{
   528  						Plural:   "plural",
   529  						Singular: "singular",
   530  						Kind:     "Plural",
   531  						ListKind: "PluralList",
   532  					},
   533  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   534  						{
   535  							Name:    "version",
   536  							Served:  true,
   537  							Storage: true,
   538  						},
   539  						{
   540  							Name:    "version2",
   541  							Served:  true,
   542  							Storage: false,
   543  						},
   544  					},
   545  					Conversion: &apiextensions.CustomResourceConversion{
   546  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
   547  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
   548  							URL: strPtr("https://example.com/webhook"),
   549  						},
   550  						ConversionReviewVersions: []string{"invalid-version", "v1beta1"},
   551  					},
   552  					Validation: &apiextensions.CustomResourceValidation{
   553  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   554  							Type: "object",
   555  						},
   556  					},
   557  					PreserveUnknownFields: pointer.BoolPtr(false),
   558  				},
   559  				Status: apiextensions.CustomResourceDefinitionStatus{
   560  					StoredVersions: []string{"version"},
   561  				},
   562  			},
   563  			errors: []validationMatch{},
   564  		},
   565  		{
   566  			name: "webhookconfig: duplicate ConversionReviewVersion",
   567  			resource: &apiextensions.CustomResourceDefinition{
   568  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   569  				Spec: apiextensions.CustomResourceDefinitionSpec{
   570  					Group: "group.com",
   571  					Scope: apiextensions.ResourceScope("Cluster"),
   572  					Names: apiextensions.CustomResourceDefinitionNames{
   573  						Plural:   "plural",
   574  						Singular: "singular",
   575  						Kind:     "Plural",
   576  						ListKind: "PluralList",
   577  					},
   578  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   579  						{
   580  							Name:    "version",
   581  							Served:  true,
   582  							Storage: true,
   583  						},
   584  						{
   585  							Name:    "version2",
   586  							Served:  true,
   587  							Storage: false,
   588  						},
   589  					},
   590  					Conversion: &apiextensions.CustomResourceConversion{
   591  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
   592  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
   593  							URL: strPtr("https://example.com/webhook"),
   594  						},
   595  						ConversionReviewVersions: []string{"v1beta1", "v1beta1"},
   596  					},
   597  					Validation: &apiextensions.CustomResourceValidation{
   598  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   599  							Type: "object",
   600  						},
   601  					},
   602  					PreserveUnknownFields: pointer.BoolPtr(false),
   603  				},
   604  				Status: apiextensions.CustomResourceDefinitionStatus{
   605  					StoredVersions: []string{"version"},
   606  				},
   607  			},
   608  			errors: []validationMatch{
   609  				invalidIndex(1, "spec", "conversion", "conversionReviewVersions"),
   610  			},
   611  		},
   612  		{
   613  			name: "missing_webhookconfig",
   614  			resource: &apiextensions.CustomResourceDefinition{
   615  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   616  				Spec: apiextensions.CustomResourceDefinitionSpec{
   617  					Group: "group.com",
   618  					Scope: apiextensions.ResourceScope("Cluster"),
   619  					Names: apiextensions.CustomResourceDefinitionNames{
   620  						Plural:   "plural",
   621  						Singular: "singular",
   622  						Kind:     "Plural",
   623  						ListKind: "PluralList",
   624  					},
   625  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   626  						{
   627  							Name:    "version",
   628  							Served:  true,
   629  							Storage: true,
   630  						},
   631  						{
   632  							Name:    "version2",
   633  							Served:  true,
   634  							Storage: false,
   635  						},
   636  					},
   637  					Conversion: &apiextensions.CustomResourceConversion{
   638  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
   639  					},
   640  					Validation: &apiextensions.CustomResourceValidation{
   641  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   642  							Type: "object",
   643  						},
   644  					},
   645  					PreserveUnknownFields: pointer.BoolPtr(false),
   646  				},
   647  				Status: apiextensions.CustomResourceDefinitionStatus{
   648  					StoredVersions: []string{"version"},
   649  				},
   650  			},
   651  			errors: []validationMatch{
   652  				required("spec", "conversion", "webhookClientConfig"),
   653  			},
   654  		},
   655  		{
   656  			name: "invalid_conversion_strategy",
   657  			resource: &apiextensions.CustomResourceDefinition{
   658  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   659  				Spec: apiextensions.CustomResourceDefinitionSpec{
   660  					Group: "group.com",
   661  					Scope: apiextensions.ResourceScope("Cluster"),
   662  					Names: apiextensions.CustomResourceDefinitionNames{
   663  						Plural:   "plural",
   664  						Singular: "singular",
   665  						Kind:     "Plural",
   666  						ListKind: "PluralList",
   667  					},
   668  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   669  						{
   670  							Name:    "version",
   671  							Served:  true,
   672  							Storage: true,
   673  						},
   674  						{
   675  							Name:    "version2",
   676  							Served:  true,
   677  							Storage: false,
   678  						},
   679  					},
   680  					Conversion: &apiextensions.CustomResourceConversion{
   681  						Strategy: apiextensions.ConversionStrategyType("non_existing_conversion"),
   682  					},
   683  					Validation: &apiextensions.CustomResourceValidation{
   684  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   685  							Type: "object",
   686  						},
   687  					},
   688  					PreserveUnknownFields: pointer.BoolPtr(false),
   689  				},
   690  				Status: apiextensions.CustomResourceDefinitionStatus{
   691  					StoredVersions: []string{"version"},
   692  				},
   693  			},
   694  			errors: []validationMatch{
   695  				unsupported("spec", "conversion", "strategy"),
   696  			},
   697  		},
   698  		{
   699  			name: "none conversion without preserveUnknownFields=false",
   700  			resource: &apiextensions.CustomResourceDefinition{
   701  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   702  				Spec: apiextensions.CustomResourceDefinitionSpec{
   703  					Group: "group.com",
   704  					Scope: apiextensions.ResourceScope("Cluster"),
   705  					Names: apiextensions.CustomResourceDefinitionNames{
   706  						Plural:   "plural",
   707  						Singular: "singular",
   708  						Kind:     "Plural",
   709  						ListKind: "PluralList",
   710  					},
   711  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   712  						{
   713  							Name:    "version1",
   714  							Served:  true,
   715  							Storage: true,
   716  						},
   717  						{
   718  							Name:    "version2",
   719  							Served:  true,
   720  							Storage: false,
   721  						},
   722  					},
   723  					Conversion: &apiextensions.CustomResourceConversion{
   724  						Strategy: apiextensions.ConversionStrategyType("None"),
   725  					},
   726  					PreserveUnknownFields: nil,
   727  					Validation: &apiextensions.CustomResourceValidation{
   728  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   729  							Type: "object",
   730  						},
   731  					},
   732  				},
   733  				Status: apiextensions.CustomResourceDefinitionStatus{
   734  					StoredVersions: []string{"version1"},
   735  				},
   736  			},
   737  			errors: []validationMatch{},
   738  		},
   739  		{
   740  			name: "webhook conversion without preserveUnknownFields=false",
   741  			resource: &apiextensions.CustomResourceDefinition{
   742  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   743  				Spec: apiextensions.CustomResourceDefinitionSpec{
   744  					Group: "group.com",
   745  					Scope: apiextensions.ResourceScope("Cluster"),
   746  					Names: apiextensions.CustomResourceDefinitionNames{
   747  						Plural:   "plural",
   748  						Singular: "singular",
   749  						Kind:     "Plural",
   750  						ListKind: "PluralList",
   751  					},
   752  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   753  						{
   754  							Name:    "version1",
   755  							Served:  true,
   756  							Storage: true,
   757  						},
   758  						{
   759  							Name:    "version2",
   760  							Served:  true,
   761  							Storage: false,
   762  						},
   763  					},
   764  					Conversion: &apiextensions.CustomResourceConversion{
   765  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
   766  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
   767  							URL: strPtr("https://example.com/webhook"),
   768  						},
   769  						ConversionReviewVersions: []string{"v1beta1"},
   770  					},
   771  					PreserveUnknownFields: nil,
   772  					Validation: &apiextensions.CustomResourceValidation{
   773  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   774  							Type: "object",
   775  						},
   776  					},
   777  				},
   778  				Status: apiextensions.CustomResourceDefinitionStatus{
   779  					StoredVersions: []string{"version1"},
   780  				},
   781  			},
   782  			errors: []validationMatch{
   783  				invalid("spec", "conversion", "strategy"),
   784  			},
   785  		},
   786  		{
   787  			name: "webhook conversion with preserveUnknownFields=false, conversionReviewVersions=[v1beta1]",
   788  			resource: &apiextensions.CustomResourceDefinition{
   789  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   790  				Spec: apiextensions.CustomResourceDefinitionSpec{
   791  					Group: "group.com",
   792  					Scope: apiextensions.ResourceScope("Cluster"),
   793  					Names: apiextensions.CustomResourceDefinitionNames{
   794  						Plural:   "plural",
   795  						Singular: "singular",
   796  						Kind:     "Plural",
   797  						ListKind: "PluralList",
   798  					},
   799  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   800  						{
   801  							Name:    "version1",
   802  							Served:  true,
   803  							Storage: true,
   804  						},
   805  						{
   806  							Name:    "version2",
   807  							Served:  true,
   808  							Storage: false,
   809  						},
   810  					},
   811  					Conversion: &apiextensions.CustomResourceConversion{
   812  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
   813  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
   814  							URL: strPtr("https://example.com/webhook"),
   815  						},
   816  						ConversionReviewVersions: []string{"v1beta1"},
   817  					},
   818  					Validation: &apiextensions.CustomResourceValidation{
   819  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   820  							Type: "object",
   821  						},
   822  					},
   823  					PreserveUnknownFields: pointer.BoolPtr(false),
   824  				},
   825  				Status: apiextensions.CustomResourceDefinitionStatus{
   826  					StoredVersions: []string{"version1"},
   827  				},
   828  			},
   829  			errors: []validationMatch{},
   830  		},
   831  		{
   832  			name: "webhook conversion with preserveUnknownFields=false, conversionReviewVersions=[v1]",
   833  			resource: &apiextensions.CustomResourceDefinition{
   834  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   835  				Spec: apiextensions.CustomResourceDefinitionSpec{
   836  					Group: "group.com",
   837  					Scope: apiextensions.ResourceScope("Cluster"),
   838  					Names: apiextensions.CustomResourceDefinitionNames{
   839  						Plural:   "plural",
   840  						Singular: "singular",
   841  						Kind:     "Plural",
   842  						ListKind: "PluralList",
   843  					},
   844  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   845  						{
   846  							Name:    "version1",
   847  							Served:  true,
   848  							Storage: true,
   849  						},
   850  						{
   851  							Name:    "version2",
   852  							Served:  true,
   853  							Storage: false,
   854  						},
   855  					},
   856  					Conversion: &apiextensions.CustomResourceConversion{
   857  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
   858  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
   859  							URL: strPtr("https://example.com/webhook"),
   860  						},
   861  						ConversionReviewVersions: []string{"v1"},
   862  					},
   863  					Validation: &apiextensions.CustomResourceValidation{
   864  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   865  							Type: "object",
   866  						},
   867  					},
   868  					PreserveUnknownFields: pointer.BoolPtr(false),
   869  				},
   870  				Status: apiextensions.CustomResourceDefinitionStatus{
   871  					StoredVersions: []string{"version1"},
   872  				},
   873  			},
   874  			errors: []validationMatch{},
   875  		},
   876  		{
   877  			name: "no_storage_version",
   878  			resource: &apiextensions.CustomResourceDefinition{
   879  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   880  				Spec: apiextensions.CustomResourceDefinitionSpec{
   881  					Group: "group.com",
   882  					Scope: apiextensions.ResourceScope("Cluster"),
   883  					Names: apiextensions.CustomResourceDefinitionNames{
   884  						Plural:   "plural",
   885  						Singular: "singular",
   886  						Kind:     "Plural",
   887  						ListKind: "PluralList",
   888  					},
   889  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   890  						{
   891  							Name:    "version",
   892  							Served:  true,
   893  							Storage: false,
   894  						},
   895  						{
   896  							Name:    "version2",
   897  							Served:  true,
   898  							Storage: false,
   899  						},
   900  					},
   901  					Conversion: &apiextensions.CustomResourceConversion{
   902  						Strategy: apiextensions.ConversionStrategyType("None"),
   903  					},
   904  					PreserveUnknownFields: pointer.BoolPtr(false),
   905  					Validation: &apiextensions.CustomResourceValidation{
   906  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   907  							Type: "object",
   908  						},
   909  					},
   910  				},
   911  				Status: apiextensions.CustomResourceDefinitionStatus{
   912  					StoredVersions: []string{"version"},
   913  				},
   914  			},
   915  			errors: []validationMatch{
   916  				invalid("spec", "versions"),
   917  			},
   918  		},
   919  		{
   920  			name: "multiple_storage_version",
   921  			resource: &apiextensions.CustomResourceDefinition{
   922  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   923  				Spec: apiextensions.CustomResourceDefinitionSpec{
   924  					Group: "group.com",
   925  					Scope: apiextensions.ResourceScope("Cluster"),
   926  					Names: apiextensions.CustomResourceDefinitionNames{
   927  						Plural:   "plural",
   928  						Singular: "singular",
   929  						Kind:     "Plural",
   930  						ListKind: "PluralList",
   931  					},
   932  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   933  						{
   934  							Name:    "version",
   935  							Served:  true,
   936  							Storage: true,
   937  						},
   938  						{
   939  							Name:    "version2",
   940  							Served:  true,
   941  							Storage: true,
   942  						},
   943  					},
   944  					Conversion: &apiextensions.CustomResourceConversion{
   945  						Strategy: apiextensions.ConversionStrategyType("None"),
   946  					},
   947  					PreserveUnknownFields: pointer.BoolPtr(false),
   948  					Validation: &apiextensions.CustomResourceValidation{
   949  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   950  							Type: "object",
   951  						},
   952  					},
   953  				},
   954  				Status: apiextensions.CustomResourceDefinitionStatus{
   955  					StoredVersions: []string{"version"},
   956  				},
   957  			},
   958  			errors: []validationMatch{
   959  				invalid("spec", "versions"),
   960  				invalid("status", "storedVersions"),
   961  			},
   962  		},
   963  		{
   964  			name: "missing_storage_version_in_stored_versions",
   965  			resource: &apiextensions.CustomResourceDefinition{
   966  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
   967  				Spec: apiextensions.CustomResourceDefinitionSpec{
   968  					Group: "group.com",
   969  					Scope: apiextensions.ResourceScope("Cluster"),
   970  					Names: apiextensions.CustomResourceDefinitionNames{
   971  						Plural:   "plural",
   972  						Singular: "singular",
   973  						Kind:     "Plural",
   974  						ListKind: "PluralList",
   975  					},
   976  					Versions: []apiextensions.CustomResourceDefinitionVersion{
   977  						{
   978  							Name:    "version",
   979  							Served:  true,
   980  							Storage: false,
   981  						},
   982  						{
   983  							Name:    "version2",
   984  							Served:  true,
   985  							Storage: true,
   986  						},
   987  					},
   988  					Conversion: &apiextensions.CustomResourceConversion{
   989  						Strategy: apiextensions.ConversionStrategyType("None"),
   990  					},
   991  					PreserveUnknownFields: pointer.BoolPtr(false),
   992  					Validation: &apiextensions.CustomResourceValidation{
   993  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
   994  							Type: "object",
   995  						},
   996  					},
   997  				},
   998  				Status: apiextensions.CustomResourceDefinitionStatus{
   999  					StoredVersions: []string{"version"},
  1000  				},
  1001  			},
  1002  			errors: []validationMatch{
  1003  				invalid("status", "storedVersions"),
  1004  			},
  1005  		},
  1006  		{
  1007  			name: "empty_stored_version",
  1008  			resource: &apiextensions.CustomResourceDefinition{
  1009  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1010  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1011  					Group: "group.com",
  1012  					Scope: apiextensions.ResourceScope("Cluster"),
  1013  					Names: apiextensions.CustomResourceDefinitionNames{
  1014  						Plural:   "plural",
  1015  						Singular: "singular",
  1016  						Kind:     "Plural",
  1017  						ListKind: "PluralList",
  1018  					},
  1019  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  1020  						{
  1021  							Name:    "version",
  1022  							Served:  true,
  1023  							Storage: true,
  1024  						},
  1025  					},
  1026  					Conversion: &apiextensions.CustomResourceConversion{
  1027  						Strategy: apiextensions.ConversionStrategyType("None"),
  1028  					},
  1029  					PreserveUnknownFields: pointer.BoolPtr(false),
  1030  					Validation: &apiextensions.CustomResourceValidation{
  1031  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1032  							Type: "object",
  1033  						},
  1034  					},
  1035  				},
  1036  				Status: apiextensions.CustomResourceDefinitionStatus{
  1037  					StoredVersions: []string{},
  1038  				},
  1039  			},
  1040  			errors: []validationMatch{
  1041  				invalid("status", "storedVersions"),
  1042  			},
  1043  		},
  1044  		{
  1045  			name: "mismatched name",
  1046  			resource: &apiextensions.CustomResourceDefinition{
  1047  				ObjectMeta: metav1.ObjectMeta{Name: "plural.not.group.com"},
  1048  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1049  					Group: "group.com",
  1050  					Names: apiextensions.CustomResourceDefinitionNames{
  1051  						Plural: "plural",
  1052  					},
  1053  					PreserveUnknownFields: pointer.BoolPtr(false),
  1054  					Validation: &apiextensions.CustomResourceValidation{
  1055  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1056  							Type: "object",
  1057  						},
  1058  					},
  1059  				},
  1060  			},
  1061  			errors: []validationMatch{
  1062  				invalid("status", "storedVersions"),
  1063  				invalid("metadata", "name"),
  1064  				invalid("spec", "versions"),
  1065  				required("spec", "scope"),
  1066  				required("spec", "names", "singular"),
  1067  				required("spec", "names", "kind"),
  1068  				required("spec", "names", "listKind"),
  1069  			},
  1070  		},
  1071  		{
  1072  			name: "missing values",
  1073  			resource: &apiextensions.CustomResourceDefinition{
  1074  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1075  			},
  1076  			errors: []validationMatch{
  1077  				invalid("status", "storedVersions"),
  1078  				invalid("metadata", "name"),
  1079  				invalid("spec", "versions"),
  1080  				required("spec", "group"),
  1081  				required("spec", "scope"),
  1082  				required("spec", "names", "plural"),
  1083  				required("spec", "names", "singular"),
  1084  				required("spec", "names", "kind"),
  1085  				required("spec", "names", "listKind"),
  1086  			},
  1087  		},
  1088  		{
  1089  			name: "bad names 01",
  1090  			resource: &apiextensions.CustomResourceDefinition{
  1091  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group"},
  1092  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1093  					Group:   "group",
  1094  					Version: "ve()*rsion",
  1095  					Scope:   apiextensions.ResourceScope("foo"),
  1096  					Names: apiextensions.CustomResourceDefinitionNames{
  1097  						Plural:   "pl()*ural",
  1098  						Singular: "value()*a",
  1099  						Kind:     "value()*a",
  1100  						ListKind: "value()*a",
  1101  					},
  1102  					PreserveUnknownFields: pointer.BoolPtr(false),
  1103  					Validation: &apiextensions.CustomResourceValidation{
  1104  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1105  							Type: "object",
  1106  						},
  1107  					},
  1108  				},
  1109  				Status: apiextensions.CustomResourceDefinitionStatus{
  1110  					AcceptedNames: apiextensions.CustomResourceDefinitionNames{
  1111  						Plural:   "pl()*ural",
  1112  						Singular: "value()*a",
  1113  						Kind:     "value()*a",
  1114  						ListKind: "value()*a",
  1115  					},
  1116  				},
  1117  			},
  1118  			errors: []validationMatch{
  1119  				invalid("status", "storedVersions"),
  1120  				invalid("metadata", "name"),
  1121  				invalid("spec", "group"),
  1122  				unsupported("spec", "scope"),
  1123  				invalid("spec", "names", "plural"),
  1124  				invalid("spec", "names", "singular"),
  1125  				invalid("spec", "names", "kind"),
  1126  				invalid("spec", "names", "listKind"), // invalid format
  1127  				invalid("spec", "names", "listKind"), // kind == listKind
  1128  				invalid("status", "acceptedNames", "plural"),
  1129  				invalid("status", "acceptedNames", "singular"),
  1130  				invalid("status", "acceptedNames", "kind"),
  1131  				invalid("status", "acceptedNames", "listKind"), // invalid format
  1132  				invalid("status", "acceptedNames", "listKind"), // kind == listKind
  1133  				invalid("spec", "versions"),
  1134  				invalid("spec", "version"),
  1135  			},
  1136  		},
  1137  		{
  1138  			name: "bad names 02",
  1139  			resource: &apiextensions.CustomResourceDefinition{
  1140  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group"},
  1141  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1142  					Group:    "group.c(*&om",
  1143  					Version:  "version",
  1144  					Versions: singleVersionList,
  1145  					Conversion: &apiextensions.CustomResourceConversion{
  1146  						Strategy: apiextensions.ConversionStrategyType("None"),
  1147  					},
  1148  					Names: apiextensions.CustomResourceDefinitionNames{
  1149  						Plural:   "plural",
  1150  						Singular: "singular",
  1151  						Kind:     "matching",
  1152  						ListKind: "matching",
  1153  					},
  1154  					PreserveUnknownFields: pointer.BoolPtr(false),
  1155  					Validation: &apiextensions.CustomResourceValidation{
  1156  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1157  							Type: "object",
  1158  						},
  1159  					},
  1160  				},
  1161  				Status: apiextensions.CustomResourceDefinitionStatus{
  1162  					AcceptedNames: apiextensions.CustomResourceDefinitionNames{
  1163  						Plural:   "plural",
  1164  						Singular: "singular",
  1165  						Kind:     "matching",
  1166  						ListKind: "matching",
  1167  					},
  1168  					StoredVersions: []string{"version"},
  1169  				},
  1170  			},
  1171  			errors: []validationMatch{
  1172  				invalid("metadata", "name"),
  1173  				invalid("spec", "group"),
  1174  				required("spec", "scope"),
  1175  				invalid("spec", "names", "listKind"),
  1176  				invalid("status", "acceptedNames", "listKind"),
  1177  			},
  1178  		},
  1179  		{
  1180  			name: "additionalProperties and properties forbidden",
  1181  			resource: &apiextensions.CustomResourceDefinition{
  1182  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1183  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1184  					Group:    "group.com",
  1185  					Version:  "version",
  1186  					Versions: singleVersionList,
  1187  					Conversion: &apiextensions.CustomResourceConversion{
  1188  						Strategy: apiextensions.ConversionStrategyType("None"),
  1189  					},
  1190  					Scope: apiextensions.NamespaceScoped,
  1191  					Names: apiextensions.CustomResourceDefinitionNames{
  1192  						Plural:   "plural",
  1193  						Singular: "singular",
  1194  						Kind:     "Plural",
  1195  						ListKind: "PluralList",
  1196  					},
  1197  					Validation: &apiextensions.CustomResourceValidation{
  1198  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1199  							Type: "object",
  1200  							Properties: map[string]apiextensions.JSONSchemaProps{
  1201  								"foo": {
  1202  									Type: "object",
  1203  									Properties: map[string]apiextensions.JSONSchemaProps{
  1204  										"bar": {Type: "object"},
  1205  									},
  1206  									AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{Allows: false},
  1207  								},
  1208  							},
  1209  						},
  1210  					},
  1211  					PreserveUnknownFields: pointer.BoolPtr(false),
  1212  				},
  1213  				Status: apiextensions.CustomResourceDefinitionStatus{
  1214  					StoredVersions: []string{"version"},
  1215  				},
  1216  			},
  1217  			errors: []validationMatch{
  1218  				forbidden("spec", "validation", "openAPIV3Schema", "properties[foo]", "additionalProperties"),
  1219  			},
  1220  		},
  1221  		{
  1222  			name: "additionalProperties without properties allowed (map[string]string)",
  1223  			resource: &apiextensions.CustomResourceDefinition{
  1224  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1225  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1226  					Group:    "group.com",
  1227  					Version:  "version",
  1228  					Versions: singleVersionList,
  1229  					Conversion: &apiextensions.CustomResourceConversion{
  1230  						Strategy: apiextensions.ConversionStrategyType("None"),
  1231  					},
  1232  					Scope: apiextensions.NamespaceScoped,
  1233  					Names: apiextensions.CustomResourceDefinitionNames{
  1234  						Plural:   "plural",
  1235  						Singular: "singular",
  1236  						Kind:     "Plural",
  1237  						ListKind: "PluralList",
  1238  					},
  1239  					Validation: &apiextensions.CustomResourceValidation{
  1240  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1241  							Type: "object",
  1242  							Properties: map[string]apiextensions.JSONSchemaProps{
  1243  								"foo": {
  1244  									Type: "object",
  1245  									AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  1246  										Allows: true,
  1247  										Schema: &apiextensions.JSONSchemaProps{
  1248  											Type: "string",
  1249  										},
  1250  									},
  1251  								},
  1252  							},
  1253  						},
  1254  					},
  1255  					PreserveUnknownFields: pointer.BoolPtr(false),
  1256  				},
  1257  				Status: apiextensions.CustomResourceDefinitionStatus{
  1258  					StoredVersions: []string{"version"},
  1259  				},
  1260  			},
  1261  			errors: []validationMatch{},
  1262  		},
  1263  		{
  1264  			name: "per-version fields may not all be set to identical values (top-level field should be used instead)",
  1265  			resource: &apiextensions.CustomResourceDefinition{
  1266  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1267  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1268  					Group:   "group.com",
  1269  					Version: "version",
  1270  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  1271  						{
  1272  							Name:    "version",
  1273  							Served:  true,
  1274  							Storage: true,
  1275  							Schema: &apiextensions.CustomResourceValidation{
  1276  								OpenAPIV3Schema: validValidationSchema,
  1277  							},
  1278  							Subresources:             &apiextensions.CustomResourceSubresources{},
  1279  							AdditionalPrinterColumns: []apiextensions.CustomResourceColumnDefinition{{Name: "Alpha", Type: "string", JSONPath: ".spec.alpha"}},
  1280  						},
  1281  						{
  1282  							Name:    "version2",
  1283  							Served:  true,
  1284  							Storage: false,
  1285  							Schema: &apiextensions.CustomResourceValidation{
  1286  								OpenAPIV3Schema: validValidationSchema,
  1287  							},
  1288  							Subresources:             &apiextensions.CustomResourceSubresources{},
  1289  							AdditionalPrinterColumns: []apiextensions.CustomResourceColumnDefinition{{Name: "Alpha", Type: "string", JSONPath: ".spec.alpha"}},
  1290  						},
  1291  					},
  1292  					Scope: apiextensions.NamespaceScoped,
  1293  					Names: apiextensions.CustomResourceDefinitionNames{
  1294  						Plural:   "plural",
  1295  						Singular: "singular",
  1296  						Kind:     "Plural",
  1297  						ListKind: "PluralList",
  1298  					},
  1299  					PreserveUnknownFields: pointer.BoolPtr(false),
  1300  				},
  1301  				Status: apiextensions.CustomResourceDefinitionStatus{
  1302  					StoredVersions: []string{"version"},
  1303  				},
  1304  			},
  1305  			errors: []validationMatch{
  1306  				// Per-version schema/subresources/columns may not all be set to identical values.
  1307  				// Note that the test will fail if we de-duplicate the expected errors below.
  1308  				invalid("spec", "versions"),
  1309  				invalid("spec", "versions"),
  1310  				invalid("spec", "versions"),
  1311  			},
  1312  		},
  1313  		{
  1314  			name: "x-kubernetes-preserve-unknown-field: false",
  1315  			resource: &apiextensions.CustomResourceDefinition{
  1316  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1317  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1318  					Group:   "group.com",
  1319  					Version: "version",
  1320  					Validation: &apiextensions.CustomResourceValidation{
  1321  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1322  							XPreserveUnknownFields: pointer.BoolPtr(false),
  1323  						},
  1324  					},
  1325  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  1326  						{
  1327  							Name:    "version",
  1328  							Served:  true,
  1329  							Storage: true,
  1330  						},
  1331  					},
  1332  					Scope: apiextensions.NamespaceScoped,
  1333  					Names: apiextensions.CustomResourceDefinitionNames{
  1334  						Plural:   "plural",
  1335  						Singular: "singular",
  1336  						Kind:     "Plural",
  1337  						ListKind: "PluralList",
  1338  					},
  1339  					PreserveUnknownFields: pointer.BoolPtr(false),
  1340  				},
  1341  				Status: apiextensions.CustomResourceDefinitionStatus{
  1342  					StoredVersions: []string{"version"},
  1343  				},
  1344  			},
  1345  			errors: []validationMatch{
  1346  				invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-preserve-unknown-fields"),
  1347  			},
  1348  		},
  1349  		{
  1350  			name: "preserveUnknownFields with unstructural global schema",
  1351  			resource: &apiextensions.CustomResourceDefinition{
  1352  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1353  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1354  					Group:   "group.com",
  1355  					Version: "version",
  1356  					Validation: &apiextensions.CustomResourceValidation{
  1357  						OpenAPIV3Schema: validUnstructuralValidationSchema,
  1358  					},
  1359  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  1360  						{
  1361  							Name:    "version",
  1362  							Served:  true,
  1363  							Storage: true,
  1364  						},
  1365  						{
  1366  							Name:    "version2",
  1367  							Served:  true,
  1368  							Storage: false,
  1369  						},
  1370  					},
  1371  					Scope: apiextensions.NamespaceScoped,
  1372  					Names: apiextensions.CustomResourceDefinitionNames{
  1373  						Plural:   "plural",
  1374  						Singular: "singular",
  1375  						Kind:     "Plural",
  1376  						ListKind: "PluralList",
  1377  					},
  1378  					PreserveUnknownFields: pointer.BoolPtr(false),
  1379  				},
  1380  				Status: apiextensions.CustomResourceDefinitionStatus{
  1381  					StoredVersions: []string{"version"},
  1382  				},
  1383  			},
  1384  			errors: []validationMatch{
  1385  				required("spec", "validation", "openAPIV3Schema", "properties[spec]", "type"),
  1386  				required("spec", "validation", "openAPIV3Schema", "properties[status]", "type"),
  1387  				required("spec", "validation", "openAPIV3Schema", "items", "type"),
  1388  			},
  1389  		},
  1390  		{
  1391  			name: "preserveUnknownFields with unstructural schema in one version",
  1392  			resource: &apiextensions.CustomResourceDefinition{
  1393  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1394  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1395  					Group:   "group.com",
  1396  					Version: "version",
  1397  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  1398  						{
  1399  							Name:    "version",
  1400  							Served:  true,
  1401  							Storage: true,
  1402  							Schema: &apiextensions.CustomResourceValidation{
  1403  								OpenAPIV3Schema: validValidationSchema,
  1404  							},
  1405  						},
  1406  						{
  1407  							Name:    "version2",
  1408  							Served:  true,
  1409  							Storage: false,
  1410  							Schema: &apiextensions.CustomResourceValidation{
  1411  								OpenAPIV3Schema: validUnstructuralValidationSchema,
  1412  							},
  1413  						},
  1414  					},
  1415  					Scope: apiextensions.NamespaceScoped,
  1416  					Names: apiextensions.CustomResourceDefinitionNames{
  1417  						Plural:   "plural",
  1418  						Singular: "singular",
  1419  						Kind:     "Plural",
  1420  						ListKind: "PluralList",
  1421  					},
  1422  					PreserveUnknownFields: pointer.BoolPtr(false),
  1423  				},
  1424  				Status: apiextensions.CustomResourceDefinitionStatus{
  1425  					StoredVersions: []string{"version"},
  1426  				},
  1427  			},
  1428  			errors: []validationMatch{
  1429  				required("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[spec]", "type"),
  1430  				required("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[status]", "type"),
  1431  				required("spec", "versions[1]", "schema", "openAPIV3Schema", "items", "type"),
  1432  			},
  1433  		},
  1434  		{
  1435  			name: "preserveUnknownFields with no schema in one version",
  1436  			resource: &apiextensions.CustomResourceDefinition{
  1437  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1438  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1439  					Group:   "group.com",
  1440  					Version: "version",
  1441  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  1442  						{
  1443  							Name:    "version",
  1444  							Served:  true,
  1445  							Storage: true,
  1446  							Schema: &apiextensions.CustomResourceValidation{
  1447  								OpenAPIV3Schema: validValidationSchema,
  1448  							},
  1449  						},
  1450  						{
  1451  							Name:    "version2",
  1452  							Served:  true,
  1453  							Storage: false,
  1454  							Schema: &apiextensions.CustomResourceValidation{
  1455  								OpenAPIV3Schema: nil,
  1456  							},
  1457  						},
  1458  					},
  1459  					Scope: apiextensions.NamespaceScoped,
  1460  					Names: apiextensions.CustomResourceDefinitionNames{
  1461  						Plural:   "plural",
  1462  						Singular: "singular",
  1463  						Kind:     "Plural",
  1464  						ListKind: "PluralList",
  1465  					},
  1466  					PreserveUnknownFields: pointer.BoolPtr(false),
  1467  				},
  1468  				Status: apiextensions.CustomResourceDefinitionStatus{
  1469  					StoredVersions: []string{"version"},
  1470  				},
  1471  			},
  1472  			errors: []validationMatch{
  1473  				required("spec", "versions[1]", "schema", "openAPIV3Schema"),
  1474  			},
  1475  		},
  1476  		{
  1477  			name: "preserveUnknownFields with no schema at all",
  1478  			resource: &apiextensions.CustomResourceDefinition{
  1479  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1480  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1481  					Group:   "group.com",
  1482  					Version: "version",
  1483  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  1484  						{
  1485  							Name:    "version",
  1486  							Served:  true,
  1487  							Storage: true,
  1488  							Schema:  nil,
  1489  						},
  1490  						{
  1491  							Name:    "version2",
  1492  							Served:  true,
  1493  							Storage: false,
  1494  							Schema: &apiextensions.CustomResourceValidation{
  1495  								OpenAPIV3Schema: nil,
  1496  							},
  1497  						},
  1498  					},
  1499  					Scope: apiextensions.NamespaceScoped,
  1500  					Names: apiextensions.CustomResourceDefinitionNames{
  1501  						Plural:   "plural",
  1502  						Singular: "singular",
  1503  						Kind:     "Plural",
  1504  						ListKind: "PluralList",
  1505  					},
  1506  					PreserveUnknownFields: pointer.BoolPtr(false),
  1507  				},
  1508  				Status: apiextensions.CustomResourceDefinitionStatus{
  1509  					StoredVersions: []string{"version"},
  1510  				},
  1511  			},
  1512  			errors: []validationMatch{
  1513  				required("spec", "versions[0]", "schema", "openAPIV3Schema"),
  1514  				required("spec", "versions[1]", "schema", "openAPIV3Schema"),
  1515  			},
  1516  		},
  1517  		{
  1518  			name: "schema is required",
  1519  			resource: &apiextensions.CustomResourceDefinition{
  1520  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1521  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1522  					Group:   "group.com",
  1523  					Version: "version",
  1524  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  1525  						{
  1526  							Name:    "version",
  1527  							Served:  true,
  1528  							Storage: true,
  1529  							Schema:  nil,
  1530  						},
  1531  						{
  1532  							Name:    "version2",
  1533  							Served:  true,
  1534  							Storage: false,
  1535  							Schema: &apiextensions.CustomResourceValidation{
  1536  								OpenAPIV3Schema: nil,
  1537  							},
  1538  						},
  1539  					},
  1540  					Scope: apiextensions.NamespaceScoped,
  1541  					Names: apiextensions.CustomResourceDefinitionNames{
  1542  						Plural:   "plural",
  1543  						Singular: "singular",
  1544  						Kind:     "Plural",
  1545  						ListKind: "PluralList",
  1546  					},
  1547  					PreserveUnknownFields: pointer.BoolPtr(false),
  1548  				},
  1549  				Status: apiextensions.CustomResourceDefinitionStatus{
  1550  					StoredVersions: []string{"version"},
  1551  				},
  1552  			},
  1553  			errors: []validationMatch{
  1554  				required("spec", "versions[0]", "schema", "openAPIV3Schema"),
  1555  				required("spec", "versions[1]", "schema", "openAPIV3Schema"),
  1556  			},
  1557  		},
  1558  		{
  1559  			name: "preserveUnknownFields: true",
  1560  			resource: &apiextensions.CustomResourceDefinition{
  1561  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1562  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1563  					Group:                 "group.com",
  1564  					Version:               "version",
  1565  					Versions:              []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  1566  					Validation:            &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}},
  1567  					Scope:                 apiextensions.NamespaceScoped,
  1568  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  1569  					PreserveUnknownFields: pointer.BoolPtr(true),
  1570  				},
  1571  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  1572  			},
  1573  			errors: []validationMatch{invalid("spec.preserveUnknownFields")},
  1574  		},
  1575  		{
  1576  			name: "labelSelectorPath outside of .spec and .status",
  1577  			resource: &apiextensions.CustomResourceDefinition{
  1578  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1579  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1580  					Group:   "group.com",
  1581  					Version: "version0",
  1582  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  1583  						{
  1584  							// null labelSelectorPath
  1585  							Name:    "version0",
  1586  							Served:  true,
  1587  							Storage: true,
  1588  							Subresources: &apiextensions.CustomResourceSubresources{
  1589  								Scale: &apiextensions.CustomResourceSubresourceScale{
  1590  									SpecReplicasPath:   ".spec.replicas",
  1591  									StatusReplicasPath: ".status.replicas",
  1592  								},
  1593  							},
  1594  						},
  1595  						{
  1596  							// labelSelectorPath under .status
  1597  							Name:    "version1",
  1598  							Served:  true,
  1599  							Storage: false,
  1600  							Subresources: &apiextensions.CustomResourceSubresources{
  1601  								Scale: &apiextensions.CustomResourceSubresourceScale{
  1602  									SpecReplicasPath:   ".spec.replicas",
  1603  									StatusReplicasPath: ".status.replicas",
  1604  									LabelSelectorPath:  strPtr(".status.labelSelector"),
  1605  								},
  1606  							},
  1607  						},
  1608  						{
  1609  							// labelSelectorPath under .spec
  1610  							Name:    "version2",
  1611  							Served:  true,
  1612  							Storage: false,
  1613  							Subresources: &apiextensions.CustomResourceSubresources{
  1614  								Scale: &apiextensions.CustomResourceSubresourceScale{
  1615  									SpecReplicasPath:   ".spec.replicas",
  1616  									StatusReplicasPath: ".status.replicas",
  1617  									LabelSelectorPath:  strPtr(".spec.labelSelector"),
  1618  								},
  1619  							},
  1620  						},
  1621  						{
  1622  							// labelSelectorPath outside of .spec and .status
  1623  							Name:    "version3",
  1624  							Served:  true,
  1625  							Storage: false,
  1626  							Subresources: &apiextensions.CustomResourceSubresources{
  1627  								Scale: &apiextensions.CustomResourceSubresourceScale{
  1628  									SpecReplicasPath:   ".spec.replicas",
  1629  									StatusReplicasPath: ".status.replicas",
  1630  									LabelSelectorPath:  strPtr(".labelSelector"),
  1631  								},
  1632  							},
  1633  						},
  1634  					},
  1635  					Scope: apiextensions.NamespaceScoped,
  1636  					Names: apiextensions.CustomResourceDefinitionNames{
  1637  						Plural:   "plural",
  1638  						Singular: "singular",
  1639  						Kind:     "Plural",
  1640  						ListKind: "PluralList",
  1641  					},
  1642  					PreserveUnknownFields: pointer.BoolPtr(false),
  1643  					Validation: &apiextensions.CustomResourceValidation{
  1644  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1645  							Type: "object",
  1646  						},
  1647  					},
  1648  				},
  1649  				Status: apiextensions.CustomResourceDefinitionStatus{
  1650  					StoredVersions: []string{"version0"},
  1651  				},
  1652  			},
  1653  			errors: []validationMatch{
  1654  				invalid("spec", "versions[3]", "subresources", "scale", "labelSelectorPath"),
  1655  			},
  1656  		},
  1657  		{
  1658  			name: "defaults with enabled feature gate",
  1659  			resource: &apiextensions.CustomResourceDefinition{
  1660  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1661  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1662  					Group:    "group.com",
  1663  					Version:  "version",
  1664  					Versions: singleVersionList,
  1665  					Scope:    apiextensions.NamespaceScoped,
  1666  					Names: apiextensions.CustomResourceDefinitionNames{
  1667  						Plural:   "plural",
  1668  						Singular: "singular",
  1669  						Kind:     "Plural",
  1670  						ListKind: "PluralList",
  1671  					},
  1672  					Validation: &apiextensions.CustomResourceValidation{
  1673  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1674  							Type: "object",
  1675  							Properties: map[string]apiextensions.JSONSchemaProps{
  1676  								"a": {
  1677  									Type:    "number",
  1678  									Default: jsonPtr(42.0),
  1679  								},
  1680  							},
  1681  						},
  1682  					},
  1683  					PreserveUnknownFields: pointer.BoolPtr(false),
  1684  				},
  1685  				Status: apiextensions.CustomResourceDefinitionStatus{
  1686  					StoredVersions: []string{"version"},
  1687  				},
  1688  			},
  1689  		},
  1690  		{
  1691  			name: "x-kubernetes-embedded-resource with pruning and empty properties",
  1692  			resource: &apiextensions.CustomResourceDefinition{
  1693  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1694  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1695  					Group:    "group.com",
  1696  					Version:  "version",
  1697  					Versions: singleVersionList,
  1698  					Scope:    apiextensions.NamespaceScoped,
  1699  					Names: apiextensions.CustomResourceDefinitionNames{
  1700  						Plural:   "plural",
  1701  						Singular: "singular",
  1702  						Kind:     "Plural",
  1703  						ListKind: "PluralList",
  1704  					},
  1705  					Validation: &apiextensions.CustomResourceValidation{
  1706  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1707  							Type:                   "object",
  1708  							XPreserveUnknownFields: pointer.BoolPtr(true),
  1709  							Properties: map[string]apiextensions.JSONSchemaProps{
  1710  								"nil": {
  1711  									Type:              "object",
  1712  									XEmbeddedResource: true,
  1713  									Properties:        nil,
  1714  								},
  1715  								"empty": {
  1716  									Type:              "object",
  1717  									XEmbeddedResource: true,
  1718  									Properties:        map[string]apiextensions.JSONSchemaProps{},
  1719  								},
  1720  							},
  1721  						},
  1722  					},
  1723  				},
  1724  				Status: apiextensions.CustomResourceDefinitionStatus{
  1725  					StoredVersions: []string{"version"},
  1726  				},
  1727  			},
  1728  			errors: []validationMatch{
  1729  				required("spec", "validation", "openAPIV3Schema", "properties[nil]", "properties"),
  1730  				required("spec", "validation", "openAPIV3Schema", "properties[empty]", "properties"),
  1731  			},
  1732  		},
  1733  		{
  1734  			name: "x-kubernetes-embedded-resource inside resource meta",
  1735  			resource: &apiextensions.CustomResourceDefinition{
  1736  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1737  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1738  					Group:    "group.com",
  1739  					Version:  "version",
  1740  					Versions: singleVersionList,
  1741  					Scope:    apiextensions.NamespaceScoped,
  1742  					Names: apiextensions.CustomResourceDefinitionNames{
  1743  						Plural:   "plural",
  1744  						Singular: "singular",
  1745  						Kind:     "Plural",
  1746  						ListKind: "PluralList",
  1747  					},
  1748  					Validation: &apiextensions.CustomResourceValidation{
  1749  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1750  							Type: "object",
  1751  							Properties: map[string]apiextensions.JSONSchemaProps{
  1752  								"embedded": {
  1753  									Type:              "object",
  1754  									XEmbeddedResource: true,
  1755  									Properties: map[string]apiextensions.JSONSchemaProps{
  1756  										"metadata": {
  1757  											Type:                   "object",
  1758  											XEmbeddedResource:      true,
  1759  											XPreserveUnknownFields: pointer.BoolPtr(true),
  1760  										},
  1761  										"apiVersion": {
  1762  											Type: "string",
  1763  											Properties: map[string]apiextensions.JSONSchemaProps{
  1764  												"foo": {
  1765  													Type:                   "object",
  1766  													XEmbeddedResource:      true,
  1767  													XPreserveUnknownFields: pointer.BoolPtr(true),
  1768  												},
  1769  											},
  1770  										},
  1771  										"kind": {
  1772  											Type: "string",
  1773  											Properties: map[string]apiextensions.JSONSchemaProps{
  1774  												"foo": {
  1775  													Type:                   "object",
  1776  													XEmbeddedResource:      true,
  1777  													XPreserveUnknownFields: pointer.BoolPtr(true),
  1778  												},
  1779  											},
  1780  										},
  1781  									},
  1782  								},
  1783  							},
  1784  						},
  1785  					},
  1786  				},
  1787  				Status: apiextensions.CustomResourceDefinitionStatus{
  1788  					StoredVersions: []string{"version"},
  1789  				},
  1790  			},
  1791  			errors: []validationMatch{
  1792  				forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "x-kubernetes-embedded-resource"),
  1793  				forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[apiVersion]", "properties[foo]", "x-kubernetes-embedded-resource"),
  1794  				forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[kind]", "properties[foo]", "x-kubernetes-embedded-resource"),
  1795  			},
  1796  		},
  1797  		{
  1798  			name: "x-kubernetes-validations access metadata name",
  1799  			resource: &apiextensions.CustomResourceDefinition{
  1800  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1801  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1802  					Group:    "group.com",
  1803  					Versions: singleVersionList,
  1804  					Subresources: &apiextensions.CustomResourceSubresources{
  1805  						Status: &apiextensions.CustomResourceSubresourceStatus{},
  1806  					},
  1807  					Scope: apiextensions.NamespaceScoped,
  1808  					Names: apiextensions.CustomResourceDefinitionNames{
  1809  						Plural:   "plural",
  1810  						Singular: "singular",
  1811  						Kind:     "Plural",
  1812  						ListKind: "PluralList",
  1813  					},
  1814  					PreserveUnknownFields: pointer.BoolPtr(false),
  1815  					Validation: &apiextensions.CustomResourceValidation{
  1816  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1817  							Type:                   "object",
  1818  							XPreserveUnknownFields: pointer.BoolPtr(true),
  1819  							XValidations: apiextensions.ValidationRules{
  1820  								{
  1821  									Rule: "size(self.metadata.name) > 3",
  1822  								},
  1823  							},
  1824  							Properties: map[string]apiextensions.JSONSchemaProps{
  1825  								"metadata": {
  1826  									Type: "object",
  1827  									Properties: map[string]apiextensions.JSONSchemaProps{
  1828  										"name": {
  1829  											Type: "string",
  1830  										},
  1831  									},
  1832  								},
  1833  							},
  1834  						},
  1835  					},
  1836  				},
  1837  				Status: apiextensions.CustomResourceDefinitionStatus{
  1838  					StoredVersions: []string{"version"},
  1839  				},
  1840  			},
  1841  		},
  1842  		{
  1843  			name: "defaults with enabled feature gate, unstructural schema",
  1844  			resource: &apiextensions.CustomResourceDefinition{
  1845  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1846  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1847  					Group:    "group.com",
  1848  					Version:  "version",
  1849  					Versions: singleVersionList,
  1850  					Scope:    apiextensions.NamespaceScoped,
  1851  					Names: apiextensions.CustomResourceDefinitionNames{
  1852  						Plural:   "plural",
  1853  						Singular: "singular",
  1854  						Kind:     "Plural",
  1855  						ListKind: "PluralList",
  1856  					},
  1857  					Validation: &apiextensions.CustomResourceValidation{
  1858  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1859  							Properties: map[string]apiextensions.JSONSchemaProps{
  1860  								"a": {Default: jsonPtr(42.0)},
  1861  							},
  1862  						},
  1863  					},
  1864  					PreserveUnknownFields: pointer.BoolPtr(false),
  1865  				},
  1866  				Status: apiextensions.CustomResourceDefinitionStatus{
  1867  					StoredVersions: []string{"version"},
  1868  				},
  1869  			},
  1870  			errors: []validationMatch{
  1871  				required("spec", "validation", "openAPIV3Schema", "properties[a]", "type"),
  1872  				required("spec", "validation", "openAPIV3Schema", "type"),
  1873  			},
  1874  		},
  1875  		{
  1876  			name: "defaults with enabled feature gate, structural schema",
  1877  			resource: &apiextensions.CustomResourceDefinition{
  1878  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1879  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1880  					Group:    "group.com",
  1881  					Version:  "version",
  1882  					Versions: singleVersionList,
  1883  					Scope:    apiextensions.NamespaceScoped,
  1884  					Names: apiextensions.CustomResourceDefinitionNames{
  1885  						Plural:   "plural",
  1886  						Singular: "singular",
  1887  						Kind:     "Plural",
  1888  						ListKind: "PluralList",
  1889  					},
  1890  					Validation: &apiextensions.CustomResourceValidation{
  1891  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1892  							Type: "object",
  1893  							Properties: map[string]apiextensions.JSONSchemaProps{
  1894  								"a": {
  1895  									Type:    "number",
  1896  									Default: jsonPtr(42.0),
  1897  								},
  1898  							},
  1899  						},
  1900  					},
  1901  					PreserveUnknownFields: pointer.BoolPtr(false),
  1902  				},
  1903  				Status: apiextensions.CustomResourceDefinitionStatus{
  1904  					StoredVersions: []string{"version"},
  1905  				},
  1906  			},
  1907  			errors: []validationMatch{},
  1908  		},
  1909  		{
  1910  			name: "defaults in value validation with enabled feature gate, structural schema",
  1911  			resource: &apiextensions.CustomResourceDefinition{
  1912  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1913  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1914  					Group:    "group.com",
  1915  					Version:  "version",
  1916  					Versions: singleVersionList,
  1917  					Scope:    apiextensions.NamespaceScoped,
  1918  					Names: apiextensions.CustomResourceDefinitionNames{
  1919  						Plural:   "plural",
  1920  						Singular: "singular",
  1921  						Kind:     "Plural",
  1922  						ListKind: "PluralList",
  1923  					},
  1924  					Validation: &apiextensions.CustomResourceValidation{
  1925  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1926  							Type: "object",
  1927  							Properties: map[string]apiextensions.JSONSchemaProps{
  1928  								"a": {
  1929  									Type: "number",
  1930  									Not: &apiextensions.JSONSchemaProps{
  1931  										Default: jsonPtr(42.0),
  1932  									},
  1933  									AnyOf: []apiextensions.JSONSchemaProps{
  1934  										{
  1935  											Default: jsonPtr(42.0),
  1936  										},
  1937  									},
  1938  									AllOf: []apiextensions.JSONSchemaProps{
  1939  										{
  1940  											Default: jsonPtr(42.0),
  1941  										},
  1942  									},
  1943  									OneOf: []apiextensions.JSONSchemaProps{
  1944  										{
  1945  											Default: jsonPtr(42.0),
  1946  										},
  1947  									},
  1948  								},
  1949  							},
  1950  						},
  1951  					},
  1952  					PreserveUnknownFields: pointer.BoolPtr(false),
  1953  				},
  1954  				Status: apiextensions.CustomResourceDefinitionStatus{
  1955  					StoredVersions: []string{"version"},
  1956  				},
  1957  			},
  1958  			errors: []validationMatch{
  1959  				forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "not", "default"),
  1960  				forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "allOf[0]", "default"),
  1961  				forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "anyOf[0]", "default"),
  1962  				forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "oneOf[0]", "default"),
  1963  			},
  1964  		},
  1965  		{
  1966  			name: "invalid defaults with enabled feature gate, structural schema",
  1967  			resource: &apiextensions.CustomResourceDefinition{
  1968  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  1969  				Spec: apiextensions.CustomResourceDefinitionSpec{
  1970  					Group:    "group.com",
  1971  					Version:  "version",
  1972  					Versions: singleVersionList,
  1973  					Scope:    apiextensions.NamespaceScoped,
  1974  					Names: apiextensions.CustomResourceDefinitionNames{
  1975  						Plural:   "plural",
  1976  						Singular: "singular",
  1977  						Kind:     "Plural",
  1978  						ListKind: "PluralList",
  1979  					},
  1980  					Validation: &apiextensions.CustomResourceValidation{
  1981  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  1982  							Type: "object",
  1983  							Properties: map[string]apiextensions.JSONSchemaProps{
  1984  								"a": {
  1985  									Type: "object",
  1986  									Properties: map[string]apiextensions.JSONSchemaProps{
  1987  										"foo": {
  1988  											Type: "string",
  1989  										},
  1990  									},
  1991  									Default: jsonPtr(map[string]interface{}{
  1992  										"foo": "abc",
  1993  										"bar": int64(42.0),
  1994  									}),
  1995  								},
  1996  								"b": {
  1997  									Type: "object",
  1998  									Properties: map[string]apiextensions.JSONSchemaProps{
  1999  										"foo": {
  2000  											Type: "string",
  2001  										},
  2002  									},
  2003  									Default: jsonPtr(map[string]interface{}{
  2004  										"foo": "abc",
  2005  									}),
  2006  								},
  2007  								"c": {
  2008  									Type: "object",
  2009  									Properties: map[string]apiextensions.JSONSchemaProps{
  2010  										"foo": {
  2011  											Type: "string",
  2012  										},
  2013  									},
  2014  									Default: jsonPtr(map[string]interface{}{
  2015  										"foo": int64(42),
  2016  									}),
  2017  								},
  2018  								"d": {
  2019  									Type: "object",
  2020  									Properties: map[string]apiextensions.JSONSchemaProps{
  2021  										"good": {
  2022  											Type:    "string",
  2023  											Pattern: "a",
  2024  										},
  2025  										"bad": {
  2026  											Type:    "string",
  2027  											Pattern: "b",
  2028  										},
  2029  									},
  2030  									Default: jsonPtr(map[string]interface{}{
  2031  										"good": "a",
  2032  										"bad":  "a",
  2033  									}),
  2034  								},
  2035  								"e": {
  2036  									Type: "object",
  2037  									Properties: map[string]apiextensions.JSONSchemaProps{
  2038  										"preserveUnknownFields": {
  2039  											Type: "object",
  2040  											Default: jsonPtr(map[string]interface{}{
  2041  												"foo": "abc",
  2042  												// this is under x-kubernetes-preserve-unknown-fields
  2043  												"bar": int64(42.0),
  2044  											}),
  2045  										},
  2046  										"nestedProperties": {
  2047  											Type: "object",
  2048  											Properties: map[string]apiextensions.JSONSchemaProps{
  2049  												"foo": {
  2050  													Type: "string",
  2051  												},
  2052  											},
  2053  											Default: jsonPtr(map[string]interface{}{
  2054  												"foo": "abc",
  2055  												"bar": int64(42.0),
  2056  											}),
  2057  										},
  2058  									},
  2059  									XPreserveUnknownFields: pointer.BoolPtr(true),
  2060  								},
  2061  								// x-kubernetes-embedded-resource: true
  2062  								"embedded-fine": {
  2063  									Type:              "object",
  2064  									XEmbeddedResource: true,
  2065  									Properties: map[string]apiextensions.JSONSchemaProps{
  2066  										"foo": {
  2067  											Type: "string",
  2068  										},
  2069  									},
  2070  									Default: jsonPtr(map[string]interface{}{
  2071  										"foo":        "abc",
  2072  										"apiVersion": "foo/v1",
  2073  										"kind":       "v1",
  2074  										"metadata": map[string]interface{}{
  2075  											"name": "foo",
  2076  										},
  2077  									}),
  2078  								},
  2079  								"embedded-preserve": {
  2080  									Type:                   "object",
  2081  									XEmbeddedResource:      true,
  2082  									XPreserveUnknownFields: pointer.BoolPtr(true),
  2083  									Properties: map[string]apiextensions.JSONSchemaProps{
  2084  										"foo": {
  2085  											Type: "string",
  2086  										},
  2087  									},
  2088  									Default: jsonPtr(map[string]interface{}{
  2089  										"foo":        "abc",
  2090  										"apiVersion": "foo/v1",
  2091  										"kind":       "v1",
  2092  										"metadata": map[string]interface{}{
  2093  											"name": "foo",
  2094  										},
  2095  										"bar": int64(42),
  2096  									}),
  2097  								},
  2098  								"embedded-preserve-unpruned-objectmeta": {
  2099  									Type:                   "object",
  2100  									XEmbeddedResource:      true,
  2101  									XPreserveUnknownFields: pointer.BoolPtr(true),
  2102  									Properties: map[string]apiextensions.JSONSchemaProps{
  2103  										"foo": {
  2104  											Type: "string",
  2105  										},
  2106  									},
  2107  									Default: jsonPtr(map[string]interface{}{
  2108  										"foo":        "abc",
  2109  										"apiVersion": "foo/v1",
  2110  										"kind":       "v1",
  2111  										"metadata": map[string]interface{}{
  2112  											"name": "foo",
  2113  											// allow: unknown fields under metadata are not rejected during CRD validation, but only pruned in storage creation
  2114  											"unspecified": "bar",
  2115  										},
  2116  										"bar": int64(42),
  2117  									}),
  2118  								},
  2119  							},
  2120  						},
  2121  					},
  2122  					PreserveUnknownFields: pointer.BoolPtr(false),
  2123  				},
  2124  				Status: apiextensions.CustomResourceDefinitionStatus{
  2125  					StoredVersions: []string{"version"},
  2126  				},
  2127  			},
  2128  			errors: []validationMatch{
  2129  				invalid("spec", "validation", "openAPIV3Schema", "properties[a]", "default"),
  2130  				invalidtypecode("spec", "validation", "openAPIV3Schema", "properties[c]", "default", "foo"),
  2131  				invalid("spec", "validation", "openAPIV3Schema", "properties[d]", "default", "bad"),
  2132  				// we also expected unpruned and valid defaults under x-kubernetes-preserve-unknown-fields. We could be more
  2133  				// strict here, but want to encourage proper specifications by forbidding other defaults.
  2134  				invalid("spec", "validation", "openAPIV3Schema", "properties[e]", "properties[preserveUnknownFields]", "default"),
  2135  				invalid("spec", "validation", "openAPIV3Schema", "properties[e]", "properties[nestedProperties]", "default"),
  2136  			},
  2137  		},
  2138  		{
  2139  			name: "additionalProperties at resource root",
  2140  			resource: &apiextensions.CustomResourceDefinition{
  2141  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  2142  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2143  					Group:    "group.com",
  2144  					Version:  "version",
  2145  					Versions: singleVersionList,
  2146  					Scope:    apiextensions.NamespaceScoped,
  2147  					Names: apiextensions.CustomResourceDefinitionNames{
  2148  						Plural:   "plural",
  2149  						Singular: "singular",
  2150  						Kind:     "Plural",
  2151  						ListKind: "PluralList",
  2152  					},
  2153  					Validation: &apiextensions.CustomResourceValidation{
  2154  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2155  							Type: "object",
  2156  							Properties: map[string]apiextensions.JSONSchemaProps{
  2157  								"embedded1": {
  2158  									Type:              "object",
  2159  									XEmbeddedResource: true,
  2160  									AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  2161  										Schema: &apiextensions.JSONSchemaProps{Type: "string"},
  2162  									},
  2163  								},
  2164  								"embedded2": {
  2165  									Type:                   "object",
  2166  									XEmbeddedResource:      true,
  2167  									XPreserveUnknownFields: pointer.BoolPtr(true),
  2168  									AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  2169  										Schema: &apiextensions.JSONSchemaProps{Type: "string"},
  2170  									},
  2171  								},
  2172  							},
  2173  						},
  2174  					},
  2175  					PreserveUnknownFields: pointer.BoolPtr(false),
  2176  				},
  2177  				Status: apiextensions.CustomResourceDefinitionStatus{
  2178  					StoredVersions: []string{"version"},
  2179  				},
  2180  			},
  2181  			errors: []validationMatch{
  2182  				forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded1]", "additionalProperties"),
  2183  				required("spec", "validation", "openAPIV3Schema", "properties[embedded1]", "properties"),
  2184  				forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded2]", "additionalProperties"),
  2185  			},
  2186  		},
  2187  		{
  2188  			// TODO: remove in a follow-up. This blocks is here for easy review.
  2189  			name: "v1.15 era tests for metadata defaults",
  2190  			resource: &apiextensions.CustomResourceDefinition{
  2191  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  2192  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2193  					Group:   "group.com",
  2194  					Version: "v1",
  2195  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2196  						{
  2197  							Name:    "v1",
  2198  							Served:  true,
  2199  							Storage: true,
  2200  							Schema: &apiextensions.CustomResourceValidation{
  2201  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2202  									Type: "object",
  2203  									Properties: map[string]apiextensions.JSONSchemaProps{
  2204  										"metadata": {
  2205  											Type: "object",
  2206  											// forbidden: no default for top-level metadata
  2207  											Default: jsonPtr(map[string]interface{}{
  2208  												"name": "foo",
  2209  											}),
  2210  										},
  2211  										"embedded": {
  2212  											Type:              "object",
  2213  											XEmbeddedResource: true,
  2214  											Properties: map[string]apiextensions.JSONSchemaProps{
  2215  												"metadata": {
  2216  													Type: "object",
  2217  													Default: jsonPtr(map[string]interface{}{
  2218  														"name": "foo",
  2219  														// allow: unknown fields under metadata are not rejected during CRD validation, but only pruned in storage creation
  2220  														"unknown": int64(42),
  2221  													}),
  2222  												},
  2223  											},
  2224  										},
  2225  									},
  2226  								},
  2227  							},
  2228  						},
  2229  						{
  2230  							Name:    "v2",
  2231  							Served:  true,
  2232  							Storage: false,
  2233  							Schema: &apiextensions.CustomResourceValidation{
  2234  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2235  									Type: "object",
  2236  									Properties: map[string]apiextensions.JSONSchemaProps{
  2237  										"metadata": {
  2238  											Type: "object",
  2239  											Properties: map[string]apiextensions.JSONSchemaProps{
  2240  												"name": {
  2241  													Type: "string",
  2242  													// forbidden: no default in top-level metadata
  2243  													Default: jsonPtr("foo"),
  2244  												},
  2245  											},
  2246  										},
  2247  										"embedded": {
  2248  											Type:              "object",
  2249  											XEmbeddedResource: true,
  2250  											Properties: map[string]apiextensions.JSONSchemaProps{
  2251  												"apiVersion": {
  2252  													Type:    "string",
  2253  													Default: jsonPtr("v1"),
  2254  												},
  2255  												"kind": {
  2256  													Type:    "string",
  2257  													Default: jsonPtr("Pod"),
  2258  												},
  2259  												"metadata": {
  2260  													Type: "object",
  2261  													Properties: map[string]apiextensions.JSONSchemaProps{
  2262  														"name": {
  2263  															Type:    "string",
  2264  															Default: jsonPtr("foo"),
  2265  														},
  2266  													},
  2267  												},
  2268  											},
  2269  										},
  2270  									},
  2271  								},
  2272  							},
  2273  						},
  2274  						{
  2275  							Name:    "v3",
  2276  							Served:  true,
  2277  							Storage: false,
  2278  							Schema: &apiextensions.CustomResourceValidation{
  2279  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2280  									Type: "object",
  2281  									Properties: map[string]apiextensions.JSONSchemaProps{
  2282  										"embedded": {
  2283  											Type:              "object",
  2284  											XEmbeddedResource: true,
  2285  											Properties: map[string]apiextensions.JSONSchemaProps{
  2286  												"apiVersion": {
  2287  													Type:    "string",
  2288  													Default: jsonPtr("v1"),
  2289  												},
  2290  												"kind": {
  2291  													Type: "string",
  2292  													// invalid: non-validating value in TypeMeta
  2293  													Default: jsonPtr("%"),
  2294  												},
  2295  												"metadata": {
  2296  													Type: "object",
  2297  													Default: jsonPtr(map[string]interface{}{
  2298  														"labels": map[string]interface{}{
  2299  															// invalid: non-validating nested field in ObjectMeta
  2300  															"bar": "x y",
  2301  														},
  2302  													}),
  2303  												},
  2304  											},
  2305  										},
  2306  									},
  2307  								},
  2308  							},
  2309  						},
  2310  						{
  2311  							Name:    "v4",
  2312  							Served:  true,
  2313  							Storage: false,
  2314  							Schema: &apiextensions.CustomResourceValidation{
  2315  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2316  									Type: "object",
  2317  									Properties: map[string]apiextensions.JSONSchemaProps{
  2318  										"embedded": {
  2319  											Type:              "object",
  2320  											XEmbeddedResource: true,
  2321  											Properties: map[string]apiextensions.JSONSchemaProps{
  2322  												"metadata": {
  2323  													Type: "object",
  2324  													Properties: map[string]apiextensions.JSONSchemaProps{
  2325  														"name": {
  2326  															Type: "string",
  2327  															// invalid: wrongly typed nested fields in ObjectMeta
  2328  															Default: jsonPtr(int64(42)),
  2329  														},
  2330  														"labels": {
  2331  															Type: "object",
  2332  															Properties: map[string]apiextensions.JSONSchemaProps{
  2333  																"bar": {
  2334  																	Type: "string",
  2335  																	// invalid: wrong typed nested fields in ObjectMeta
  2336  																	Default: jsonPtr(int64(42)),
  2337  																},
  2338  															},
  2339  														},
  2340  													},
  2341  												},
  2342  											},
  2343  										},
  2344  									},
  2345  								},
  2346  							},
  2347  						},
  2348  					},
  2349  					Scope: apiextensions.NamespaceScoped,
  2350  					Names: apiextensions.CustomResourceDefinitionNames{
  2351  						Plural:   "plural",
  2352  						Singular: "singular",
  2353  						Kind:     "Plural",
  2354  						ListKind: "PluralList",
  2355  					},
  2356  					PreserveUnknownFields: pointer.BoolPtr(false),
  2357  				},
  2358  				Status: apiextensions.CustomResourceDefinitionStatus{
  2359  					StoredVersions: []string{"v1"},
  2360  				},
  2361  			},
  2362  			errors: []validationMatch{
  2363  				// Forbidden: must not be set in top-level metadata
  2364  				forbidden("spec", "versions[0]", "schema", "openAPIV3Schema", "properties[metadata]", "default"),
  2365  
  2366  				// Forbidden: must not be set in top-level metadata
  2367  				forbidden("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[metadata]", "properties[name]", "default"),
  2368  
  2369  				// Invalid value: "x y"
  2370  				invalid("spec", "versions[2]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "default"),
  2371  				// Invalid value: "%": kind: Invalid value: "%"
  2372  				invalid("spec", "versions[2]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[kind]", "default"),
  2373  
  2374  				// Invalid value: wrongly typed
  2375  				invalid("spec", "versions[3]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "properties[labels]", "properties[bar]", "default"),
  2376  				// Invalid value: wrongly typed
  2377  				invalid("spec", "versions[3]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "properties[name]", "default"),
  2378  			},
  2379  		},
  2380  		{
  2381  			name: "default inside additionalSchema",
  2382  			resource: &apiextensions.CustomResourceDefinition{
  2383  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  2384  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2385  					Group:   "group.com",
  2386  					Version: "v1",
  2387  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2388  						{
  2389  							Name:    "v1",
  2390  							Served:  true,
  2391  							Storage: true,
  2392  						},
  2393  					},
  2394  					Validation: &apiextensions.CustomResourceValidation{
  2395  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2396  							Type: "object",
  2397  							Properties: map[string]apiextensions.JSONSchemaProps{
  2398  								"embedded": {
  2399  									Type:              "object",
  2400  									XEmbeddedResource: true,
  2401  									Properties: map[string]apiextensions.JSONSchemaProps{
  2402  										"metadata": {
  2403  											Type: "object",
  2404  											Properties: map[string]apiextensions.JSONSchemaProps{
  2405  												"annotations": {
  2406  													Type: "object",
  2407  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  2408  														Schema: &apiextensions.JSONSchemaProps{
  2409  															Type: "string",
  2410  															// forbidden: no default under additionalProperties inside of metadata
  2411  															Default: jsonPtr("abc"),
  2412  														},
  2413  													},
  2414  												},
  2415  											},
  2416  										},
  2417  									},
  2418  								},
  2419  							},
  2420  						},
  2421  					},
  2422  					Scope: apiextensions.NamespaceScoped,
  2423  					Names: apiextensions.CustomResourceDefinitionNames{
  2424  						Plural:   "plural",
  2425  						Singular: "singular",
  2426  						Kind:     "Plural",
  2427  						ListKind: "PluralList",
  2428  					},
  2429  					PreserveUnknownFields: pointer.BoolPtr(false),
  2430  				},
  2431  				Status: apiextensions.CustomResourceDefinitionStatus{
  2432  					StoredVersions: []string{"v1"},
  2433  				},
  2434  			},
  2435  			errors: []validationMatch{
  2436  				// Forbidden: must not be set inside additionalProperties applying to object metadata
  2437  				forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "properties[annotations]", "additionalProperties", "default"),
  2438  			},
  2439  		},
  2440  		{
  2441  			name: "top-level metadata default",
  2442  			resource: &apiextensions.CustomResourceDefinition{
  2443  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  2444  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2445  					Group:   "group.com",
  2446  					Version: "v1",
  2447  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2448  						{
  2449  							Name:    "v1",
  2450  							Served:  true,
  2451  							Storage: true,
  2452  						},
  2453  					},
  2454  					Validation: &apiextensions.CustomResourceValidation{
  2455  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2456  							Type: "object",
  2457  							Properties: map[string]apiextensions.JSONSchemaProps{
  2458  								"metadata": {
  2459  									Type: "object",
  2460  									// forbidden: no default for top-level metadata
  2461  									Default: jsonPtr(map[string]interface{}{
  2462  										"name": "foo",
  2463  									}),
  2464  								},
  2465  							},
  2466  						},
  2467  					},
  2468  					Scope: apiextensions.NamespaceScoped,
  2469  					Names: apiextensions.CustomResourceDefinitionNames{
  2470  						Plural:   "plural",
  2471  						Singular: "singular",
  2472  						Kind:     "Plural",
  2473  						ListKind: "PluralList",
  2474  					},
  2475  					PreserveUnknownFields: pointer.BoolPtr(false),
  2476  				},
  2477  				Status: apiextensions.CustomResourceDefinitionStatus{
  2478  					StoredVersions: []string{"v1"},
  2479  				},
  2480  			},
  2481  			errors: []validationMatch{
  2482  				forbidden("spec", "validation", "openAPIV3Schema", "properties[metadata]", "default"),
  2483  			},
  2484  		},
  2485  		{
  2486  			name: "embedded metadata defaults",
  2487  			resource: &apiextensions.CustomResourceDefinition{
  2488  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  2489  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2490  					Group:   "group.com",
  2491  					Version: "v1",
  2492  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2493  						{
  2494  							Name:    "v1",
  2495  							Served:  true,
  2496  							Storage: true,
  2497  						},
  2498  					},
  2499  					Validation: &apiextensions.CustomResourceValidation{
  2500  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2501  							Type: "object",
  2502  							Properties: map[string]apiextensions.JSONSchemaProps{
  2503  								"embedded": {
  2504  									Type:              "object",
  2505  									XEmbeddedResource: true,
  2506  									Properties: map[string]apiextensions.JSONSchemaProps{
  2507  										"metadata": {
  2508  											Type: "object",
  2509  											Default: jsonPtr(map[string]interface{}{
  2510  												"name": "foo",
  2511  											}),
  2512  										},
  2513  									},
  2514  								},
  2515  
  2516  								"allowed-in-object-defaults": {
  2517  									Type:              "object",
  2518  									XEmbeddedResource: true,
  2519  									Properties: map[string]apiextensions.JSONSchemaProps{
  2520  										"apiVersion": {
  2521  											Type:    "string",
  2522  											Default: jsonPtr("v1"),
  2523  										},
  2524  										"kind": {
  2525  											Type:    "string",
  2526  											Default: jsonPtr("Pod"),
  2527  										},
  2528  										"metadata": {
  2529  											Type: "object",
  2530  											Properties: map[string]apiextensions.JSONSchemaProps{
  2531  												"name": {
  2532  													Type:    "string",
  2533  													Default: jsonPtr("foo"),
  2534  												},
  2535  											},
  2536  											// allowed: unknown fields outside metadata
  2537  											Default: jsonPtr(map[string]interface{}{
  2538  												"unknown": int64(42),
  2539  											}),
  2540  										},
  2541  									},
  2542  								},
  2543  								"allowed-object-defaults": {
  2544  									Type: "object",
  2545  									Properties: map[string]apiextensions.JSONSchemaProps{
  2546  										"something": {
  2547  											Type: "string",
  2548  										},
  2549  									},
  2550  									XEmbeddedResource: true,
  2551  									Default: jsonPtr(map[string]interface{}{
  2552  										"apiVersion": "v1",
  2553  										"kind":       "Pod",
  2554  										"metadata": map[string]interface{}{
  2555  											"name":    "foo",
  2556  											"unknown": int64(42),
  2557  										},
  2558  									}),
  2559  								},
  2560  								"allowed-spanning-object-defaults": {
  2561  									Type: "object",
  2562  									Properties: map[string]apiextensions.JSONSchemaProps{
  2563  										"embedded": {
  2564  											Type:              "object",
  2565  											XEmbeddedResource: true,
  2566  											Properties: map[string]apiextensions.JSONSchemaProps{
  2567  												"something": {
  2568  													Type: "string",
  2569  												},
  2570  											},
  2571  										},
  2572  									},
  2573  									Default: jsonPtr(map[string]interface{}{
  2574  										"embedded": map[string]interface{}{
  2575  											"apiVersion": "v1",
  2576  											"kind":       "Pod",
  2577  											"metadata": map[string]interface{}{
  2578  												"name":    "foo",
  2579  												"unknown": int64(42),
  2580  											},
  2581  										},
  2582  									}),
  2583  								},
  2584  
  2585  								"unknown-field-object-defaults": {
  2586  									Type:              "object",
  2587  									XEmbeddedResource: true,
  2588  									Properties: map[string]apiextensions.JSONSchemaProps{
  2589  										"something": {
  2590  											Type: "string",
  2591  										},
  2592  									},
  2593  									Default: jsonPtr(map[string]interface{}{
  2594  										"apiVersion": "v1",
  2595  										"kind":       "Pod",
  2596  										"metadata": map[string]interface{}{
  2597  											"name": "foo",
  2598  											// allowed: unspecified field in ObjectMeta
  2599  											"unknown": int64(42),
  2600  										},
  2601  										// forbidden: unspecified field
  2602  										"unknown": int64(42),
  2603  									}),
  2604  								},
  2605  								"unknown-field-spanning-object-defaults": {
  2606  									Type: "object",
  2607  									Properties: map[string]apiextensions.JSONSchemaProps{
  2608  										"embedded": {
  2609  											Type:              "object",
  2610  											XEmbeddedResource: true,
  2611  											Properties: map[string]apiextensions.JSONSchemaProps{
  2612  												"something": {
  2613  													Type: "string",
  2614  												},
  2615  											},
  2616  										},
  2617  									},
  2618  									Default: jsonPtr(map[string]interface{}{
  2619  										"embedded": map[string]interface{}{
  2620  											"apiVersion": "v1",
  2621  											"kind":       "Pod",
  2622  											"metadata": map[string]interface{}{
  2623  												"name": "foo",
  2624  												// allowed: unspecified field in ObjectMeta
  2625  												"unknown": int64(42),
  2626  											},
  2627  											// forbidden: unspecified field
  2628  											"unknown": int64(42),
  2629  										},
  2630  										// forbidden: unspecified field
  2631  										"unknown": int64(42),
  2632  									}),
  2633  								},
  2634  
  2635  								"x-preserve-unknown-fields-unknown-field-object-defaults": {
  2636  									Type:                   "object",
  2637  									XEmbeddedResource:      true,
  2638  									XPreserveUnknownFields: pointer.BoolPtr(true),
  2639  									Properties:             map[string]apiextensions.JSONSchemaProps{},
  2640  									Default: jsonPtr(map[string]interface{}{
  2641  										"apiVersion": "v1",
  2642  										"kind":       "Pod",
  2643  										"metadata": map[string]interface{}{
  2644  											"name": "foo",
  2645  											// allowed: unspecified field in ObjectMeta
  2646  											"unknown": int64(42),
  2647  										},
  2648  										// allowed: because x-kubernetes-preserve-unknown-fields: true
  2649  										"unknown": int64(42),
  2650  									}),
  2651  								},
  2652  								"x-preserve-unknown-fields-unknown-field-spanning-object-defaults": {
  2653  									Type: "object",
  2654  									Properties: map[string]apiextensions.JSONSchemaProps{
  2655  										"embedded": {
  2656  											Type:                   "object",
  2657  											XEmbeddedResource:      true,
  2658  											XPreserveUnknownFields: pointer.BoolPtr(true),
  2659  											Properties:             map[string]apiextensions.JSONSchemaProps{},
  2660  										},
  2661  									},
  2662  									Default: jsonPtr(map[string]interface{}{
  2663  										"embedded": map[string]interface{}{
  2664  											"apiVersion": "v1",
  2665  											"kind":       "Pod",
  2666  											"metadata": map[string]interface{}{
  2667  												"name": "foo",
  2668  												// allowed: unspecified field in ObjectMeta
  2669  												"unknown": int64(42),
  2670  											},
  2671  											// allowed: because x-kubernetes-preserve-unknown-fields: true
  2672  											"unknown": int64(42),
  2673  										},
  2674  									}),
  2675  								},
  2676  								"x-preserve-unknown-fields-unknown-field-outside": {
  2677  									Type: "object",
  2678  									Properties: map[string]apiextensions.JSONSchemaProps{
  2679  										"embedded": {
  2680  											Type:                   "object",
  2681  											XEmbeddedResource:      true,
  2682  											XPreserveUnknownFields: pointer.BoolPtr(true),
  2683  											Properties:             map[string]apiextensions.JSONSchemaProps{},
  2684  										},
  2685  									},
  2686  									Default: jsonPtr(map[string]interface{}{
  2687  										"embedded": map[string]interface{}{
  2688  											"apiVersion": "v1",
  2689  											"kind":       "Pod",
  2690  											"metadata": map[string]interface{}{
  2691  												"name": "foo",
  2692  												// allowed: unspecified field in ObjectMeta
  2693  												"unknown": int64(42),
  2694  											},
  2695  											// allowed: because x-kubernetes-preserve-unknown-fields: true
  2696  											"unknown": int64(42),
  2697  										},
  2698  										// forbidden: unspecified field
  2699  										"unknown": int64(42),
  2700  									}),
  2701  								},
  2702  
  2703  								"wrongly-typed-in-object-defaults": {
  2704  									Type:              "object",
  2705  									XEmbeddedResource: true,
  2706  									Properties: map[string]apiextensions.JSONSchemaProps{
  2707  										"apiVersion": {
  2708  											Type: "string",
  2709  											// invalid: wrong type
  2710  											Default: jsonPtr(int64(42)),
  2711  										},
  2712  										"kind": {
  2713  											Type: "string",
  2714  											// invalid: wrong type
  2715  											Default: jsonPtr(int64(42)),
  2716  										},
  2717  										"metadata": {
  2718  											Type: "object",
  2719  											Properties: map[string]apiextensions.JSONSchemaProps{
  2720  												"name": {
  2721  													Type: "string",
  2722  													// invalid: wrong type
  2723  													Default: jsonPtr(int64(42)),
  2724  												},
  2725  												"annotations": {
  2726  													Type: "object",
  2727  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  2728  														Schema: &apiextensions.JSONSchemaProps{
  2729  															Type: "string",
  2730  														},
  2731  													},
  2732  													// invalid: wrong type
  2733  													Default: jsonPtr(int64(42)),
  2734  												},
  2735  											},
  2736  										},
  2737  									},
  2738  								},
  2739  								"wrongly-typed-object-defaults-apiVersion": {
  2740  									Type:              "object",
  2741  									XEmbeddedResource: true,
  2742  									Properties: map[string]apiextensions.JSONSchemaProps{
  2743  										"something": {
  2744  											Type: "string",
  2745  										},
  2746  									},
  2747  									Default: jsonPtr(map[string]interface{}{
  2748  										// invalid: wrong type
  2749  										"apiVersion": int64(42),
  2750  									}),
  2751  								},
  2752  								"wrongly-typed-object-defaults-kind": {
  2753  									Type:              "object",
  2754  									XEmbeddedResource: true,
  2755  									Properties: map[string]apiextensions.JSONSchemaProps{
  2756  										"something": {
  2757  											Type: "string",
  2758  										},
  2759  									},
  2760  									Default: jsonPtr(map[string]interface{}{
  2761  										// invalid: wrong type
  2762  										"kind": int64(42),
  2763  									}),
  2764  								},
  2765  								"wrongly-typed-object-defaults-name": {
  2766  									Type:              "object",
  2767  									XEmbeddedResource: true,
  2768  									Properties: map[string]apiextensions.JSONSchemaProps{
  2769  										"something": {
  2770  											Type: "string",
  2771  										},
  2772  									},
  2773  									Default: jsonPtr(map[string]interface{}{
  2774  										"metadata": map[string]interface{}{
  2775  											// invalid: wrong type
  2776  											"name": int64(42),
  2777  										},
  2778  									}),
  2779  								},
  2780  								"wrongly-typed-object-defaults-labels": {
  2781  									Type:              "object",
  2782  									XEmbeddedResource: true,
  2783  									Properties: map[string]apiextensions.JSONSchemaProps{
  2784  										"something": {
  2785  											Type: "string",
  2786  										},
  2787  									},
  2788  									Default: jsonPtr(map[string]interface{}{
  2789  										"metadata": map[string]interface{}{
  2790  											"labels": map[string]interface{}{
  2791  												// invalid: wrong type
  2792  												"foo": int64(42),
  2793  											},
  2794  										},
  2795  									}),
  2796  								},
  2797  								"wrongly-typed-object-defaults-annotations": {
  2798  									Type:              "object",
  2799  									XEmbeddedResource: true,
  2800  									Properties: map[string]apiextensions.JSONSchemaProps{
  2801  										"something": {
  2802  											Type: "string",
  2803  										},
  2804  									},
  2805  									Default: jsonPtr(map[string]interface{}{
  2806  										"metadata": map[string]interface{}{
  2807  											// invalid: wrong type
  2808  											"annotations": int64(42),
  2809  										},
  2810  									}),
  2811  								},
  2812  								"wrongly-typed-object-defaults-metadata": {
  2813  									Type:              "object",
  2814  									XEmbeddedResource: true,
  2815  									Properties: map[string]apiextensions.JSONSchemaProps{
  2816  										"something": {
  2817  											Type: "string",
  2818  										},
  2819  									},
  2820  									Default: jsonPtr(map[string]interface{}{
  2821  										// invalid: wrong type
  2822  										"metadata": int64(42),
  2823  									}),
  2824  								},
  2825  
  2826  								"wrongly-typed-spanning-object-defaults-apiVersion": {
  2827  									Type: "object",
  2828  									Properties: map[string]apiextensions.JSONSchemaProps{
  2829  										"embedded": {
  2830  											Type:              "object",
  2831  											XEmbeddedResource: true,
  2832  											Properties: map[string]apiextensions.JSONSchemaProps{
  2833  												"something": {
  2834  													Type: "string",
  2835  												},
  2836  											},
  2837  										},
  2838  									},
  2839  									Default: jsonPtr(map[string]interface{}{
  2840  										"embedded": map[string]interface{}{
  2841  											// invalid: wrong type
  2842  											"apiVersion": int64(42),
  2843  										},
  2844  									}),
  2845  								},
  2846  								"wrongly-typed-spanning-object-defaults-kind": {
  2847  									Type: "object",
  2848  									Properties: map[string]apiextensions.JSONSchemaProps{
  2849  										"embedded": {
  2850  											Type:              "object",
  2851  											XEmbeddedResource: true,
  2852  											Properties: map[string]apiextensions.JSONSchemaProps{
  2853  												"something": {
  2854  													Type: "string",
  2855  												},
  2856  											},
  2857  										},
  2858  									},
  2859  									Default: jsonPtr(map[string]interface{}{
  2860  										"embedded": map[string]interface{}{
  2861  											// invalid: wrong type
  2862  											"kind": int64(42),
  2863  										},
  2864  									}),
  2865  								},
  2866  								"wrongly-typed-spanning-object-defaults-name": {
  2867  									Type: "object",
  2868  									Properties: map[string]apiextensions.JSONSchemaProps{
  2869  										"embedded": {
  2870  											Type:              "object",
  2871  											XEmbeddedResource: true,
  2872  											Properties: map[string]apiextensions.JSONSchemaProps{
  2873  												"something": {
  2874  													Type: "string",
  2875  												},
  2876  											},
  2877  										},
  2878  									},
  2879  									Default: jsonPtr(map[string]interface{}{
  2880  										"embedded": map[string]interface{}{
  2881  											"metadata": map[string]interface{}{
  2882  												"name": int64(42),
  2883  											},
  2884  										},
  2885  									}),
  2886  								},
  2887  								"wrongly-typed-spanning-object-defaults-labels": {
  2888  									Type: "object",
  2889  									Properties: map[string]apiextensions.JSONSchemaProps{
  2890  										"embedded": {
  2891  											Type:              "object",
  2892  											XEmbeddedResource: true,
  2893  											Properties: map[string]apiextensions.JSONSchemaProps{
  2894  												"something": {
  2895  													Type: "string",
  2896  												},
  2897  											},
  2898  										},
  2899  									},
  2900  									Default: jsonPtr(map[string]interface{}{
  2901  										"embedded": map[string]interface{}{
  2902  											"metadata": map[string]interface{}{
  2903  												"labels": map[string]interface{}{
  2904  													// invalid: wrong type
  2905  													"foo": int64(42),
  2906  												},
  2907  											},
  2908  										},
  2909  									}),
  2910  								},
  2911  								"wrongly-typed-spanning-object-defaults-annotations": {
  2912  									Type: "object",
  2913  									Properties: map[string]apiextensions.JSONSchemaProps{
  2914  										"embedded": {
  2915  											Type:              "object",
  2916  											XEmbeddedResource: true,
  2917  											Properties: map[string]apiextensions.JSONSchemaProps{
  2918  												"something": {
  2919  													Type: "string",
  2920  												},
  2921  											},
  2922  										},
  2923  									},
  2924  									Default: jsonPtr(map[string]interface{}{
  2925  										"embedded": map[string]interface{}{
  2926  											"metadata": map[string]interface{}{
  2927  												// invalid: wrong type
  2928  												"annotations": int64(42),
  2929  											},
  2930  										},
  2931  									}),
  2932  								},
  2933  								"wrongly-typed-spanning-object-defaults-metadata": {
  2934  									Type: "object",
  2935  									Properties: map[string]apiextensions.JSONSchemaProps{
  2936  										"embedded": {
  2937  											Type:              "object",
  2938  											XEmbeddedResource: true,
  2939  											Properties: map[string]apiextensions.JSONSchemaProps{
  2940  												"something": {
  2941  													Type: "string",
  2942  												},
  2943  											},
  2944  										},
  2945  									},
  2946  									Default: jsonPtr(map[string]interface{}{
  2947  										"embedded": map[string]interface{}{
  2948  											"metadata": int64(42),
  2949  										},
  2950  									}),
  2951  								},
  2952  
  2953  								"invalid-in-object-defaults": {
  2954  									Type:              "object",
  2955  									XEmbeddedResource: true,
  2956  									Properties: map[string]apiextensions.JSONSchemaProps{
  2957  										"kind": {
  2958  											Type: "string",
  2959  											// invalid
  2960  											Default: jsonPtr("%"),
  2961  										},
  2962  										"metadata": {
  2963  											Type: "object",
  2964  											Properties: map[string]apiextensions.JSONSchemaProps{
  2965  												"name": {
  2966  													Type: "string",
  2967  													// invalid
  2968  													Default: jsonPtr("%"),
  2969  												},
  2970  												"labels": {
  2971  													Type: "object",
  2972  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  2973  														Schema: &apiextensions.JSONSchemaProps{
  2974  															Type: "string",
  2975  														},
  2976  													},
  2977  													// invalid
  2978  													Default: jsonPtr(map[string]interface{}{
  2979  														"foo": "x y",
  2980  													}),
  2981  												},
  2982  											},
  2983  										},
  2984  									},
  2985  								},
  2986  								"invalid-object-defaults-kind": {
  2987  									Type:              "object",
  2988  									XEmbeddedResource: true,
  2989  									Properties: map[string]apiextensions.JSONSchemaProps{
  2990  										"something": {
  2991  											Type: "string",
  2992  										},
  2993  									},
  2994  									Default: jsonPtr(map[string]interface{}{
  2995  										"apiVersion": "foo/v1",
  2996  										// invalid: wrongly typed
  2997  										"kind": "%",
  2998  									}),
  2999  								},
  3000  								"invalid-object-defaults-name": {
  3001  									Type:              "object",
  3002  									XEmbeddedResource: true,
  3003  									Properties: map[string]apiextensions.JSONSchemaProps{
  3004  										"something": {
  3005  											Type: "string",
  3006  										},
  3007  									},
  3008  									Default: jsonPtr(map[string]interface{}{
  3009  										"apiVersion": "foo/v1",
  3010  										"kind":       "Foo",
  3011  										"metadata": map[string]interface{}{
  3012  											// invalid: wrongly typed
  3013  											"name": "%",
  3014  										},
  3015  									}),
  3016  								},
  3017  								"invalid-object-defaults-labels": {
  3018  									Type:              "object",
  3019  									XEmbeddedResource: true,
  3020  									Properties: map[string]apiextensions.JSONSchemaProps{
  3021  										"something": {
  3022  											Type: "string",
  3023  										},
  3024  									},
  3025  									Default: jsonPtr(map[string]interface{}{
  3026  										"apiVersion": "foo/v1",
  3027  										"kind":       "Foo",
  3028  										"metadata": map[string]interface{}{
  3029  											"labels": map[string]interface{}{
  3030  												// invalid: wrongly typed
  3031  												"foo": "x y",
  3032  											},
  3033  										},
  3034  									}),
  3035  								},
  3036  								"invalid-spanning-object-defaults-kind": {
  3037  									Type: "object",
  3038  									Properties: map[string]apiextensions.JSONSchemaProps{
  3039  										"embedded": {
  3040  											Type:              "object",
  3041  											XEmbeddedResource: true,
  3042  											Properties: map[string]apiextensions.JSONSchemaProps{
  3043  												"something": {
  3044  													Type: "string",
  3045  												},
  3046  											},
  3047  										},
  3048  									},
  3049  									Default: jsonPtr(map[string]interface{}{
  3050  										"embedded": map[string]interface{}{
  3051  											"apiVersion": "foo/v1",
  3052  											// invalid: wrongly typed
  3053  											"kind": "%",
  3054  										},
  3055  									}),
  3056  								},
  3057  								"invalid-spanning-object-defaults-name": {
  3058  									Type: "object",
  3059  									Properties: map[string]apiextensions.JSONSchemaProps{
  3060  										"embedded": {
  3061  											Type:              "object",
  3062  											XEmbeddedResource: true,
  3063  											Properties: map[string]apiextensions.JSONSchemaProps{
  3064  												"something": {
  3065  													Type: "string",
  3066  												},
  3067  											},
  3068  										},
  3069  									},
  3070  									Default: jsonPtr(map[string]interface{}{
  3071  										"embedded": map[string]interface{}{
  3072  											"apiVersion": "foo/v1",
  3073  											"kind":       "Foo",
  3074  											"metadata": map[string]interface{}{
  3075  												// invalid: wrongly typed
  3076  												"name": "%",
  3077  											},
  3078  										},
  3079  									}),
  3080  								},
  3081  								"invalid-spanning-object-defaults-labels": {
  3082  									Type: "object",
  3083  									Properties: map[string]apiextensions.JSONSchemaProps{
  3084  										"embedded": {
  3085  											Type:              "object",
  3086  											XEmbeddedResource: true,
  3087  											Properties: map[string]apiextensions.JSONSchemaProps{
  3088  												"something": {
  3089  													Type: "string",
  3090  												},
  3091  											},
  3092  										},
  3093  									},
  3094  									Default: jsonPtr(map[string]interface{}{
  3095  										"embedded": map[string]interface{}{
  3096  											"apiVersion": "foo/v1",
  3097  											"kind":       "Foo",
  3098  											"metadata": map[string]interface{}{
  3099  												"labels": map[string]interface{}{
  3100  													// invalid: wrongly typed
  3101  													"foo": "x y",
  3102  												},
  3103  											},
  3104  										},
  3105  									}),
  3106  								},
  3107  
  3108  								"in-object-defaults-with-valid-constraints": {
  3109  									Type:              "object",
  3110  									XEmbeddedResource: true,
  3111  									Properties: map[string]apiextensions.JSONSchemaProps{
  3112  										"apiVersion": {
  3113  											Type: "string",
  3114  											// valid
  3115  											Default: jsonPtr("foo/v1"),
  3116  											Enum:    jsonSlice("foo/v1"),
  3117  										},
  3118  										"kind": {
  3119  											Type: "string",
  3120  											// valid
  3121  											Default: jsonPtr("Foo"),
  3122  											Enum:    jsonSlice("Foo"),
  3123  										},
  3124  										"metadata": {
  3125  											Type: "object",
  3126  											Properties: map[string]apiextensions.JSONSchemaProps{
  3127  												"name": {
  3128  													Type: "string",
  3129  													// valid
  3130  													Default: jsonPtr("foo"),
  3131  													Enum:    jsonSlice("foo"),
  3132  												},
  3133  												"labels": {
  3134  													Type: "object",
  3135  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3136  														Schema: &apiextensions.JSONSchemaProps{
  3137  															Type: "string",
  3138  															Enum: jsonSlice("foo"),
  3139  														},
  3140  													},
  3141  													// valid
  3142  													Default: jsonPtr(map[string]interface{}{
  3143  														"foo": "foo",
  3144  													}),
  3145  												},
  3146  											},
  3147  										},
  3148  									},
  3149  								},
  3150  								"metadata-defaults-with-valid-constraints": {
  3151  									Type:              "object",
  3152  									XEmbeddedResource: true,
  3153  									Properties: map[string]apiextensions.JSONSchemaProps{
  3154  										"metadata": {
  3155  											Type: "object",
  3156  											Properties: map[string]apiextensions.JSONSchemaProps{
  3157  												"name": {
  3158  													Type: "string",
  3159  													Enum: jsonSlice("foo"),
  3160  												},
  3161  												"labels": {
  3162  													Type: "object",
  3163  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3164  														Schema: &apiextensions.JSONSchemaProps{
  3165  															Type: "string",
  3166  															Enum: jsonSlice("foo"),
  3167  														},
  3168  													},
  3169  												},
  3170  											},
  3171  											// valid
  3172  											Default: jsonPtr(map[string]interface{}{
  3173  												"name": "foo",
  3174  												"labels": map[string]interface{}{
  3175  													"foo": "foo",
  3176  												},
  3177  											}),
  3178  										},
  3179  									},
  3180  								},
  3181  								"object-defaults-with-valid-constraints": {
  3182  									Type:              "object",
  3183  									XEmbeddedResource: true,
  3184  									Properties: map[string]apiextensions.JSONSchemaProps{
  3185  										"apiVersion": {
  3186  											Type: "string",
  3187  											Enum: jsonSlice("foo/v1"),
  3188  										},
  3189  										"kind": {
  3190  											Type: "string",
  3191  											Enum: jsonSlice("Foo"),
  3192  										},
  3193  										"metadata": {
  3194  											Type: "object",
  3195  											Properties: map[string]apiextensions.JSONSchemaProps{
  3196  												"name": {
  3197  													Type: "string",
  3198  													Enum: jsonSlice("foo"),
  3199  												},
  3200  												"labels": {
  3201  													Type: "object",
  3202  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3203  														Schema: &apiextensions.JSONSchemaProps{
  3204  															Type: "string",
  3205  															Enum: jsonSlice("foo"),
  3206  														},
  3207  													},
  3208  												},
  3209  											},
  3210  										},
  3211  									},
  3212  									// valid
  3213  									Default: jsonPtr(map[string]interface{}{
  3214  										"apiVersion": "foo/v1",
  3215  										"kind":       "Foo",
  3216  										"metadata": map[string]interface{}{
  3217  											"name": "foo",
  3218  											"labels": map[string]interface{}{
  3219  												"foo": "foo",
  3220  											},
  3221  										},
  3222  									}),
  3223  								},
  3224  								"spanning-defaults-with-valid-constraints": {
  3225  									Type: "object",
  3226  									Properties: map[string]apiextensions.JSONSchemaProps{
  3227  										"embedded": {
  3228  											Type:              "object",
  3229  											XEmbeddedResource: true,
  3230  											Properties: map[string]apiextensions.JSONSchemaProps{
  3231  												"apiVersion": {
  3232  													Type: "string",
  3233  													Enum: jsonSlice("foo/v1"),
  3234  												},
  3235  												"kind": {
  3236  													Type: "string",
  3237  													Enum: jsonSlice("Foo"),
  3238  												},
  3239  												"metadata": {
  3240  													Type: "object",
  3241  													Properties: map[string]apiextensions.JSONSchemaProps{
  3242  														"name": {
  3243  															Type: "string",
  3244  															Enum: jsonSlice("foo"),
  3245  														},
  3246  														"labels": {
  3247  															Type: "object",
  3248  															AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3249  																Schema: &apiextensions.JSONSchemaProps{
  3250  																	Type: "string",
  3251  																	Enum: jsonSlice("foo"),
  3252  																},
  3253  															},
  3254  														},
  3255  													},
  3256  												},
  3257  											},
  3258  										},
  3259  									},
  3260  									// valid
  3261  									Default: jsonPtr(map[string]interface{}{
  3262  										"embedded": map[string]interface{}{
  3263  											"apiVersion": "foo/v1",
  3264  											"kind":       "Foo",
  3265  											"metadata": map[string]interface{}{
  3266  												"name": "foo",
  3267  												"labels": map[string]interface{}{
  3268  													"foo": "foo",
  3269  												},
  3270  											},
  3271  										},
  3272  									}),
  3273  								},
  3274  
  3275  								"in-object-defaults-with-invalid-constraints": {
  3276  									Type:              "object",
  3277  									XEmbeddedResource: true,
  3278  									Properties: map[string]apiextensions.JSONSchemaProps{
  3279  										"apiVersion": {
  3280  											Type:        "string",
  3281  											Description: "BREAK",
  3282  											// invalid
  3283  											Default: jsonPtr("bar/v1"),
  3284  											Enum:    jsonSlice("foo/v1"),
  3285  										},
  3286  										"kind": {
  3287  											Type: "string",
  3288  											// invalid
  3289  											Default: jsonPtr("Bar"),
  3290  											Enum:    jsonSlice("Foo"),
  3291  										},
  3292  										"metadata": {
  3293  											Type: "object",
  3294  											Properties: map[string]apiextensions.JSONSchemaProps{
  3295  												"name": {
  3296  													Type: "string",
  3297  													// invalid
  3298  													Default: jsonPtr("bar"),
  3299  													Enum:    jsonSlice("foo"),
  3300  												},
  3301  												"labels": {
  3302  													Type: "object",
  3303  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3304  														Schema: &apiextensions.JSONSchemaProps{
  3305  															Type: "string",
  3306  															Enum: jsonSlice("foo"),
  3307  														},
  3308  													},
  3309  													// invalid
  3310  													Default: jsonPtr(map[string]interface{}{
  3311  														"foo": "bar",
  3312  													}),
  3313  												},
  3314  											},
  3315  										},
  3316  									},
  3317  								},
  3318  								"metadata-defaults-with-invalid-constraints-name": {
  3319  									Type:              "object",
  3320  									XEmbeddedResource: true,
  3321  									Properties: map[string]apiextensions.JSONSchemaProps{
  3322  										"metadata": {
  3323  											Type: "object",
  3324  											Properties: map[string]apiextensions.JSONSchemaProps{
  3325  												"name": {
  3326  													Type: "string",
  3327  													Enum: jsonSlice("foo"),
  3328  												},
  3329  												"labels": {
  3330  													Type: "object",
  3331  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3332  														Schema: &apiextensions.JSONSchemaProps{
  3333  															Type: "string",
  3334  															Enum: jsonSlice("foo"),
  3335  														},
  3336  													},
  3337  												},
  3338  											},
  3339  											// invalid name
  3340  											Default: jsonPtr(map[string]interface{}{
  3341  												"name": "bar",
  3342  											}),
  3343  										},
  3344  									},
  3345  								},
  3346  								"metadata-defaults-with-invalid-constraints-labels": {
  3347  									Type:              "object",
  3348  									XEmbeddedResource: true,
  3349  									Properties: map[string]apiextensions.JSONSchemaProps{
  3350  										"metadata": {
  3351  											Type: "object",
  3352  											Properties: map[string]apiextensions.JSONSchemaProps{
  3353  												"name": {
  3354  													Type: "string",
  3355  													Enum: jsonSlice("foo"),
  3356  												},
  3357  												"labels": {
  3358  													Type: "object",
  3359  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3360  														Schema: &apiextensions.JSONSchemaProps{
  3361  															Type: "string",
  3362  															Enum: jsonSlice("foo"),
  3363  														},
  3364  													},
  3365  												},
  3366  											},
  3367  											// invalid labels
  3368  											Default: jsonPtr(map[string]interface{}{
  3369  												"name": "foo",
  3370  												"labels": map[string]interface{}{
  3371  													"foo": "bar",
  3372  												},
  3373  											}),
  3374  										},
  3375  									},
  3376  								},
  3377  								"object-defaults-with-invalid-constraints-name": {
  3378  									Type:              "object",
  3379  									XEmbeddedResource: true,
  3380  									Properties: map[string]apiextensions.JSONSchemaProps{
  3381  										"apiVersion": {
  3382  											Type: "string",
  3383  											Enum: jsonSlice("foo/v1"),
  3384  										},
  3385  										"kind": {
  3386  											Type: "string",
  3387  											Enum: jsonSlice("Foo"),
  3388  										},
  3389  										"metadata": {
  3390  											Type: "object",
  3391  											Properties: map[string]apiextensions.JSONSchemaProps{
  3392  												"name": {
  3393  													Type: "string",
  3394  													Enum: jsonSlice("foo"),
  3395  												},
  3396  												"labels": {
  3397  													Type: "object",
  3398  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3399  														Schema: &apiextensions.JSONSchemaProps{
  3400  															Type: "string",
  3401  															Enum: jsonSlice("foo"),
  3402  														},
  3403  													},
  3404  												},
  3405  											},
  3406  										},
  3407  									},
  3408  									// invalid
  3409  									Default: jsonPtr(map[string]interface{}{
  3410  										"apiVersion": "foo/v1",
  3411  										"kind":       "Foo",
  3412  										"metadata": map[string]interface{}{
  3413  											"name": "bar",
  3414  										},
  3415  									}),
  3416  								},
  3417  								"object-defaults-with-invalid-constraints-labels": {
  3418  									Type:              "object",
  3419  									XEmbeddedResource: true,
  3420  									Properties: map[string]apiextensions.JSONSchemaProps{
  3421  										"apiVersion": {
  3422  											Type: "string",
  3423  											Enum: jsonSlice("foo/v1"),
  3424  										},
  3425  										"kind": {
  3426  											Type: "string",
  3427  											Enum: jsonSlice("Foo"),
  3428  										},
  3429  										"metadata": {
  3430  											Type: "object",
  3431  											Properties: map[string]apiextensions.JSONSchemaProps{
  3432  												"name": {
  3433  													Type: "string",
  3434  													Enum: jsonSlice("foo"),
  3435  												},
  3436  												"labels": {
  3437  													Type: "object",
  3438  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3439  														Schema: &apiextensions.JSONSchemaProps{
  3440  															Type: "string",
  3441  															Enum: jsonSlice("foo"),
  3442  														},
  3443  													},
  3444  												},
  3445  											},
  3446  										},
  3447  									},
  3448  									// invalid
  3449  									Default: jsonPtr(map[string]interface{}{
  3450  										"apiVersion": "foo/v1",
  3451  										"kind":       "Foo",
  3452  										"metadata": map[string]interface{}{
  3453  											"name": "foo",
  3454  											"labels": map[string]interface{}{
  3455  												"foo": "bar",
  3456  											},
  3457  										},
  3458  									}),
  3459  								},
  3460  								"object-defaults-with-invalid-constraints-apiVersion": {
  3461  									Type:              "object",
  3462  									XEmbeddedResource: true,
  3463  									Properties: map[string]apiextensions.JSONSchemaProps{
  3464  										"apiVersion": {
  3465  											Type: "string",
  3466  											Enum: jsonSlice("foo/v1"),
  3467  										},
  3468  										"kind": {
  3469  											Type: "string",
  3470  											Enum: jsonSlice("Foo"),
  3471  										},
  3472  										"metadata": {
  3473  											Type: "object",
  3474  											Properties: map[string]apiextensions.JSONSchemaProps{
  3475  												"name": {
  3476  													Type: "string",
  3477  													Enum: jsonSlice("foo"),
  3478  												},
  3479  												"labels": {
  3480  													Type: "object",
  3481  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3482  														Schema: &apiextensions.JSONSchemaProps{
  3483  															Type: "string",
  3484  															Enum: jsonSlice("foo"),
  3485  														},
  3486  													},
  3487  												},
  3488  											},
  3489  										},
  3490  									},
  3491  									// invalid
  3492  									Default: jsonPtr(map[string]interface{}{
  3493  										"apiVersion": "bar/v1",
  3494  										"kind":       "Foo",
  3495  										"metadata": map[string]interface{}{
  3496  											"name": "foo",
  3497  										},
  3498  									}),
  3499  								},
  3500  								"object-defaults-with-invalid-constraints-kind": {
  3501  									Type:              "object",
  3502  									XEmbeddedResource: true,
  3503  									Properties: map[string]apiextensions.JSONSchemaProps{
  3504  										"apiVersion": {
  3505  											Type: "string",
  3506  											Enum: jsonSlice("foo/v1"),
  3507  										},
  3508  										"kind": {
  3509  											Type: "string",
  3510  											Enum: jsonSlice("Foo"),
  3511  										},
  3512  										"metadata": {
  3513  											Type: "object",
  3514  											Properties: map[string]apiextensions.JSONSchemaProps{
  3515  												"name": {
  3516  													Type: "string",
  3517  													Enum: jsonSlice("foo"),
  3518  												},
  3519  												"labels": {
  3520  													Type: "object",
  3521  													AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3522  														Schema: &apiextensions.JSONSchemaProps{
  3523  															Type: "string",
  3524  															Enum: jsonSlice("foo"),
  3525  														},
  3526  													},
  3527  												},
  3528  											},
  3529  										},
  3530  									},
  3531  									// invalid
  3532  									Default: jsonPtr(map[string]interface{}{
  3533  										"apiVersion": "foo/v1",
  3534  										"kind":       "Bar",
  3535  										"metadata": map[string]interface{}{
  3536  											"name": "foo",
  3537  										},
  3538  									}),
  3539  								},
  3540  								"spanning-defaults-with-invalid-constraints-name": {
  3541  									Type: "object",
  3542  									Properties: map[string]apiextensions.JSONSchemaProps{
  3543  										"embedded": {
  3544  											Type:              "object",
  3545  											XEmbeddedResource: true,
  3546  											Properties: map[string]apiextensions.JSONSchemaProps{
  3547  												"apiVersion": {
  3548  													Type: "string",
  3549  													Enum: jsonSlice("foo/v1"),
  3550  												},
  3551  												"kind": {
  3552  													Type: "string",
  3553  													Enum: jsonSlice("Foo"),
  3554  												},
  3555  												"metadata": {
  3556  													Type: "object",
  3557  													Properties: map[string]apiextensions.JSONSchemaProps{
  3558  														"name": {
  3559  															Type: "string",
  3560  															Enum: jsonSlice("foo"),
  3561  														},
  3562  														"labels": {
  3563  															Type: "object",
  3564  															AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3565  																Schema: &apiextensions.JSONSchemaProps{
  3566  																	Type: "string",
  3567  																	Enum: jsonSlice("foo"),
  3568  																},
  3569  															},
  3570  														},
  3571  													},
  3572  												},
  3573  											},
  3574  										},
  3575  									},
  3576  									// invalid
  3577  									Default: jsonPtr(map[string]interface{}{
  3578  										"embedded": map[string]interface{}{
  3579  											"apiVersion": "foo/v1",
  3580  											"kind":       "Foo",
  3581  											"metadata": map[string]interface{}{
  3582  												"name": "bar",
  3583  												"labels": map[string]interface{}{
  3584  													"foo": "foo",
  3585  												},
  3586  											},
  3587  										},
  3588  									}),
  3589  								},
  3590  								"spanning-defaults-with-invalid-constraints-labels": {
  3591  									Type: "object",
  3592  									Properties: map[string]apiextensions.JSONSchemaProps{
  3593  										"embedded": {
  3594  											Type:              "object",
  3595  											XEmbeddedResource: true,
  3596  											Properties: map[string]apiextensions.JSONSchemaProps{
  3597  												"apiVersion": {
  3598  													Type: "string",
  3599  													Enum: jsonSlice("foo/v1"),
  3600  												},
  3601  												"kind": {
  3602  													Type: "string",
  3603  													Enum: jsonSlice("Foo"),
  3604  												},
  3605  												"metadata": {
  3606  													Type: "object",
  3607  													Properties: map[string]apiextensions.JSONSchemaProps{
  3608  														"name": {
  3609  															Type: "string",
  3610  															Enum: jsonSlice("foo"),
  3611  														},
  3612  														"labels": {
  3613  															Type: "object",
  3614  															AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3615  																Schema: &apiextensions.JSONSchemaProps{
  3616  																	Type: "string",
  3617  																	Enum: jsonSlice("foo"),
  3618  																},
  3619  															},
  3620  														},
  3621  													},
  3622  												},
  3623  											},
  3624  										},
  3625  									},
  3626  									// invalid
  3627  									Default: jsonPtr(map[string]interface{}{
  3628  										"embedded": map[string]interface{}{
  3629  											"apiVersion": "foo/v1",
  3630  											"kind":       "Foo",
  3631  											"metadata": map[string]interface{}{
  3632  												"name": "foo",
  3633  												"labels": map[string]interface{}{
  3634  													"foo": "bar",
  3635  												},
  3636  											},
  3637  										},
  3638  									}),
  3639  								},
  3640  								"spanning-defaults-with-invalid-constraints-apiVersion": {
  3641  									Type: "object",
  3642  									Properties: map[string]apiextensions.JSONSchemaProps{
  3643  										"embedded": {
  3644  											Type:              "object",
  3645  											XEmbeddedResource: true,
  3646  											Properties: map[string]apiextensions.JSONSchemaProps{
  3647  												"apiVersion": {
  3648  													Type: "string",
  3649  													Enum: jsonSlice("foo/v1"),
  3650  												},
  3651  												"kind": {
  3652  													Type: "string",
  3653  													Enum: jsonSlice("Foo"),
  3654  												},
  3655  												"metadata": {
  3656  													Type: "object",
  3657  													Properties: map[string]apiextensions.JSONSchemaProps{
  3658  														"name": {
  3659  															Type: "string",
  3660  															Enum: jsonSlice("foo"),
  3661  														},
  3662  														"labels": {
  3663  															Type: "object",
  3664  															AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3665  																Schema: &apiextensions.JSONSchemaProps{
  3666  																	Type: "string",
  3667  																	Enum: jsonSlice("foo"),
  3668  																},
  3669  															},
  3670  														},
  3671  													},
  3672  												},
  3673  											},
  3674  										},
  3675  									},
  3676  									// invalid
  3677  									Default: jsonPtr(map[string]interface{}{
  3678  										"embedded": map[string]interface{}{
  3679  											"apiVersion": "bar/v1",
  3680  											"kind":       "Foo",
  3681  											"metadata": map[string]interface{}{
  3682  												"name": "foo",
  3683  												"labels": map[string]interface{}{
  3684  													"foo": "foo",
  3685  												},
  3686  											},
  3687  										},
  3688  									}),
  3689  								},
  3690  								"spanning-defaults-with-invalid-constraints-kind": {
  3691  									Type: "object",
  3692  									Properties: map[string]apiextensions.JSONSchemaProps{
  3693  										"embedded": {
  3694  											Type:              "object",
  3695  											XEmbeddedResource: true,
  3696  											Properties: map[string]apiextensions.JSONSchemaProps{
  3697  												"apiVersion": {
  3698  													Type: "string",
  3699  													Enum: jsonSlice("foo/v1"),
  3700  												},
  3701  												"kind": {
  3702  													Type: "string",
  3703  													Enum: jsonSlice("Foo"),
  3704  												},
  3705  												"metadata": {
  3706  													Type: "object",
  3707  													Properties: map[string]apiextensions.JSONSchemaProps{
  3708  														"name": {
  3709  															Type: "string",
  3710  															Enum: jsonSlice("foo"),
  3711  														},
  3712  														"labels": {
  3713  															Type: "object",
  3714  															AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  3715  																Schema: &apiextensions.JSONSchemaProps{
  3716  																	Type: "string",
  3717  																	Enum: jsonSlice("foo"),
  3718  																},
  3719  															},
  3720  														},
  3721  													},
  3722  												},
  3723  											},
  3724  										},
  3725  									},
  3726  									// invalid
  3727  									Default: jsonPtr(map[string]interface{}{
  3728  										"embedded": map[string]interface{}{
  3729  											"apiVersion": "foo/v1",
  3730  											"kind":       "Bar",
  3731  											"metadata": map[string]interface{}{
  3732  												"name": "foo",
  3733  												"labels": map[string]interface{}{
  3734  													"foo": "foo",
  3735  												},
  3736  											},
  3737  										},
  3738  									}),
  3739  								},
  3740  
  3741  								"object-defaults-with-missing-typemeta": {
  3742  									Type:              "object",
  3743  									XEmbeddedResource: true,
  3744  									Properties: map[string]apiextensions.JSONSchemaProps{
  3745  										"apiVersion": {
  3746  											Type: "string",
  3747  											Enum: jsonSlice("foo/v1"),
  3748  										},
  3749  										"kind": {
  3750  											Type: "string",
  3751  											Enum: jsonSlice("Foo"),
  3752  										},
  3753  									},
  3754  									// invalid: kind and apiVersion are missing
  3755  									Default: jsonPtr(map[string]interface{}{
  3756  										"metadata": map[string]interface{}{
  3757  											"name": "bar",
  3758  										},
  3759  									}),
  3760  								},
  3761  								"spanning-defaults-with-missing-typemeta": {
  3762  									Type: "object",
  3763  									Properties: map[string]apiextensions.JSONSchemaProps{
  3764  										"embedded": {
  3765  											Type:              "object",
  3766  											XEmbeddedResource: true,
  3767  											Properties: map[string]apiextensions.JSONSchemaProps{
  3768  												"apiVersion": {
  3769  													Type: "string",
  3770  													Enum: jsonSlice("foo/v1"),
  3771  												},
  3772  												"kind": {
  3773  													Type: "string",
  3774  													Enum: jsonSlice("Foo"),
  3775  												},
  3776  											},
  3777  										},
  3778  									},
  3779  									// invalid: kind and apiVersion are missing
  3780  									Default: jsonPtr(map[string]interface{}{
  3781  										"embedded": map[string]interface{}{
  3782  											"metadata": map[string]interface{}{
  3783  												"name": "bar",
  3784  											},
  3785  										},
  3786  									}),
  3787  								},
  3788  							},
  3789  						},
  3790  					},
  3791  					Scope: apiextensions.NamespaceScoped,
  3792  					Names: apiextensions.CustomResourceDefinitionNames{
  3793  						Plural:   "plural",
  3794  						Singular: "singular",
  3795  						Kind:     "Plural",
  3796  						ListKind: "PluralList",
  3797  					},
  3798  					PreserveUnknownFields: pointer.BoolPtr(false),
  3799  				},
  3800  				Status: apiextensions.CustomResourceDefinitionStatus{
  3801  					StoredVersions: []string{"v1"},
  3802  				},
  3803  			},
  3804  			errors: []validationMatch{
  3805  				invalid("spec", "validation", "openAPIV3Schema", "properties[unknown-field-object-defaults]", "default"),
  3806  				invalid("spec", "validation", "openAPIV3Schema", "properties[unknown-field-spanning-object-defaults]", "default"),
  3807  
  3808  				invalid("spec", "validation", "openAPIV3Schema", "properties[x-preserve-unknown-fields-unknown-field-outside]", "default"),
  3809  
  3810  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-in-object-defaults]", "properties[kind]", "default"),
  3811  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-in-object-defaults]", "properties[apiVersion]", "default"),
  3812  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-in-object-defaults]", "properties[metadata]", "properties[name]", "default"),
  3813  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-in-object-defaults]", "properties[metadata]", "properties[annotations]", "default"),
  3814  
  3815  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-metadata]", "default", "metadata"),
  3816  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-apiVersion]", "default", "apiVersion"),
  3817  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-kind]", "default", "kind"),
  3818  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-name]", "default", "metadata"),
  3819  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-labels]", "default", "metadata"),
  3820  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-annotations]", "default", "metadata"),
  3821  
  3822  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-metadata]", "default", "embedded", "metadata"),
  3823  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-apiVersion]", "default", "embedded", "apiVersion"),
  3824  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-kind]", "default", "embedded", "kind"),
  3825  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-name]", "default", "embedded", "metadata"),
  3826  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-labels]", "default", "embedded", "metadata"),
  3827  				invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-annotations]", "default", "embedded", "metadata"),
  3828  
  3829  				invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-in-object-defaults]", "properties[metadata]", "properties[name]", "default"),
  3830  				invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-in-object-defaults]", "properties[metadata]", "properties[labels]", "default"),
  3831  				invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-in-object-defaults]", "properties[kind]", "default"),
  3832  
  3833  				invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-object-defaults-kind]", "default", "kind"),
  3834  				invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-object-defaults-name]", "default", "metadata", "name"),
  3835  				invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-object-defaults-labels]", "default", "metadata", "labels"),
  3836  
  3837  				invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-spanning-object-defaults-kind]", "default", "embedded", "kind"),
  3838  				invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-spanning-object-defaults-name]", "default", "embedded", "metadata", "name"),
  3839  				invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-spanning-object-defaults-labels]", "default", "embedded", "metadata", "labels"),
  3840  
  3841  				unsupported("spec", "validation", "openAPIV3Schema", "properties[in-object-defaults-with-invalid-constraints]", "properties[apiVersion]", "default"),
  3842  				unsupported("spec", "validation", "openAPIV3Schema", "properties[in-object-defaults-with-invalid-constraints]", "properties[kind]", "default"),
  3843  				unsupported("spec", "validation", "openAPIV3Schema", "properties[in-object-defaults-with-invalid-constraints]", "properties[metadata]", "properties[name]", "default"),
  3844  				unsupported("spec", "validation", "openAPIV3Schema", "properties[in-object-defaults-with-invalid-constraints]", "properties[metadata]", "properties[labels]", "default", "foo"),
  3845  
  3846  				unsupported("spec", "validation", "openAPIV3Schema", "properties[metadata-defaults-with-invalid-constraints-name]", "properties[metadata]", "default", "name"),
  3847  				unsupported("spec", "validation", "openAPIV3Schema", "properties[metadata-defaults-with-invalid-constraints-labels]", "properties[metadata]", "default", "labels", "foo"),
  3848  
  3849  				unsupported("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-invalid-constraints-name]", "default", "metadata", "name"),
  3850  				unsupported("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-invalid-constraints-labels]", "default", "metadata", "labels", "foo"),
  3851  				unsupported("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-invalid-constraints-apiVersion]", "default", "apiVersion"),
  3852  				unsupported("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-invalid-constraints-kind]", "default", "kind"),
  3853  
  3854  				unsupported("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-invalid-constraints-kind]", "default", "embedded", "kind"),
  3855  				unsupported("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-invalid-constraints-labels]", "default", "embedded", "metadata", "labels", "foo"),
  3856  				unsupported("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-invalid-constraints-apiVersion]", "default", "embedded", "apiVersion"),
  3857  				unsupported("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-invalid-constraints-name]", "default", "embedded", "metadata", "name"),
  3858  
  3859  				required("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-missing-typemeta]", "default", "apiVersion"),
  3860  				required("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-missing-typemeta]", "default", "kind"),
  3861  
  3862  				required("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-missing-typemeta]", "default", "embedded", "apiVersion"),
  3863  				required("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-missing-typemeta]", "default", "embedded", "kind"),
  3864  			},
  3865  		},
  3866  		{
  3867  			name: "contradicting meta field types",
  3868  			resource: &apiextensions.CustomResourceDefinition{
  3869  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  3870  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3871  					Group:    "group.com",
  3872  					Version:  "version",
  3873  					Versions: singleVersionList,
  3874  					Scope:    apiextensions.NamespaceScoped,
  3875  					Names: apiextensions.CustomResourceDefinitionNames{
  3876  						Plural:   "plural",
  3877  						Singular: "singular",
  3878  						Kind:     "Plural",
  3879  						ListKind: "PluralList",
  3880  					},
  3881  					Validation: &apiextensions.CustomResourceValidation{
  3882  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3883  							Type: "object",
  3884  							Properties: map[string]apiextensions.JSONSchemaProps{
  3885  								"apiVersion": {Type: "number"},
  3886  								"kind":       {Type: "number"},
  3887  								"metadata": {
  3888  									Type: "number",
  3889  									Properties: map[string]apiextensions.JSONSchemaProps{
  3890  										"name": {
  3891  											Type:    "string",
  3892  											Pattern: "abc",
  3893  										},
  3894  										"generateName": {
  3895  											Type:    "string",
  3896  											Pattern: "abc",
  3897  										},
  3898  										"generation": {
  3899  											Type: "integer",
  3900  										},
  3901  									},
  3902  								},
  3903  								"valid": {
  3904  									Type:              "object",
  3905  									XEmbeddedResource: true,
  3906  									Properties: map[string]apiextensions.JSONSchemaProps{
  3907  										"apiVersion": {Type: "string"},
  3908  										"kind":       {Type: "string"},
  3909  										"metadata": {
  3910  											Type: "object",
  3911  											Properties: map[string]apiextensions.JSONSchemaProps{
  3912  												"name": {
  3913  													Type:    "string",
  3914  													Pattern: "abc",
  3915  												},
  3916  												"generateName": {
  3917  													Type:    "string",
  3918  													Pattern: "abc",
  3919  												},
  3920  												"generation": {
  3921  													Type:    "integer",
  3922  													Minimum: float64Ptr(42.0), // does not make sense, but is allowed for nested ObjectMeta
  3923  												},
  3924  											},
  3925  										},
  3926  									},
  3927  								},
  3928  								"invalid": {
  3929  									Type:              "object",
  3930  									XEmbeddedResource: true,
  3931  									Properties: map[string]apiextensions.JSONSchemaProps{
  3932  										"apiVersion": {Type: "number"},
  3933  										"kind":       {Type: "number"},
  3934  										"metadata": {
  3935  											Type: "number",
  3936  											Properties: map[string]apiextensions.JSONSchemaProps{
  3937  												"name": {
  3938  													Type:    "string",
  3939  													Pattern: "abc",
  3940  												},
  3941  												"generateName": {
  3942  													Type:    "string",
  3943  													Pattern: "abc",
  3944  												},
  3945  												"generation": {
  3946  													Type:    "integer",
  3947  													Minimum: float64Ptr(42.0), // does not make sense, but is allowed for nested ObjectMeta
  3948  												},
  3949  											},
  3950  										},
  3951  									},
  3952  								},
  3953  								"nested": {
  3954  									Type:              "object",
  3955  									XEmbeddedResource: true,
  3956  									Properties: map[string]apiextensions.JSONSchemaProps{
  3957  										"invalid": {
  3958  											Type:              "object",
  3959  											XEmbeddedResource: true,
  3960  											Properties: map[string]apiextensions.JSONSchemaProps{
  3961  												"apiVersion": {Type: "number"},
  3962  												"kind":       {Type: "number"},
  3963  												"metadata": {
  3964  													Type: "number",
  3965  													Properties: map[string]apiextensions.JSONSchemaProps{
  3966  														"name": {
  3967  															Type:    "string",
  3968  															Pattern: "abc",
  3969  														},
  3970  														"generateName": {
  3971  															Type:    "string",
  3972  															Pattern: "abc",
  3973  														},
  3974  														"generation": {
  3975  															Type:    "integer",
  3976  															Minimum: float64Ptr(42.0), // does not make sense, but is allowed for nested ObjectMeta
  3977  														},
  3978  													},
  3979  												},
  3980  											},
  3981  										},
  3982  									},
  3983  								},
  3984  								"noEmbeddedObject": {
  3985  									Type: "object",
  3986  									Properties: map[string]apiextensions.JSONSchemaProps{
  3987  										"apiVersion": {Type: "number"},
  3988  										"kind":       {Type: "number"},
  3989  										"metadata":   {Type: "number"},
  3990  									},
  3991  								},
  3992  							},
  3993  						},
  3994  					},
  3995  					PreserveUnknownFields: pointer.BoolPtr(false),
  3996  				},
  3997  				Status: apiextensions.CustomResourceDefinitionStatus{
  3998  					StoredVersions: []string{"version"},
  3999  				},
  4000  			},
  4001  			errors: []validationMatch{
  4002  				forbidden("spec", "validation", "openAPIV3Schema", "properties[metadata]"),
  4003  				invalid("spec", "validation", "openAPIV3Schema", "properties[apiVersion]", "type"),
  4004  				invalid("spec", "validation", "openAPIV3Schema", "properties[kind]", "type"),
  4005  				invalid("spec", "validation", "openAPIV3Schema", "properties[metadata]", "type"),
  4006  				invalid("spec", "validation", "openAPIV3Schema", "properties[invalid]", "properties[apiVersion]", "type"),
  4007  				invalid("spec", "validation", "openAPIV3Schema", "properties[invalid]", "properties[kind]", "type"),
  4008  				invalid("spec", "validation", "openAPIV3Schema", "properties[invalid]", "properties[metadata]", "type"),
  4009  				invalid("spec", "validation", "openAPIV3Schema", "properties[nested]", "properties[invalid]", "properties[apiVersion]", "type"),
  4010  				invalid("spec", "validation", "openAPIV3Schema", "properties[nested]", "properties[invalid]", "properties[kind]", "type"),
  4011  				invalid("spec", "validation", "openAPIV3Schema", "properties[nested]", "properties[invalid]", "properties[metadata]", "type"),
  4012  			},
  4013  		},
  4014  		{
  4015  			name: "x-kubernetes-validations should be forbidden under oneOf/anyOf/allOf/not, structural schema",
  4016  			resource: &apiextensions.CustomResourceDefinition{
  4017  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  4018  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4019  					Group:    "group.com",
  4020  					Version:  "version",
  4021  					Versions: singleVersionList,
  4022  					Scope:    apiextensions.NamespaceScoped,
  4023  					Names: apiextensions.CustomResourceDefinitionNames{
  4024  						Plural:   "plural",
  4025  						Singular: "singular",
  4026  						Kind:     "Plural",
  4027  						ListKind: "PluralList",
  4028  					},
  4029  					Validation: &apiextensions.CustomResourceValidation{
  4030  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4031  							Type: "object",
  4032  							Properties: map[string]apiextensions.JSONSchemaProps{
  4033  								"a": {
  4034  									Type: "number",
  4035  									Not: &apiextensions.JSONSchemaProps{
  4036  										XValidations: apiextensions.ValidationRules{
  4037  											{
  4038  												Rule: "should be forbidden",
  4039  											},
  4040  										},
  4041  									},
  4042  									AnyOf: []apiextensions.JSONSchemaProps{
  4043  										{
  4044  											XValidations: apiextensions.ValidationRules{
  4045  												{
  4046  													Rule: "should be forbidden",
  4047  												},
  4048  											},
  4049  										},
  4050  									},
  4051  									AllOf: []apiextensions.JSONSchemaProps{
  4052  										{
  4053  											XValidations: apiextensions.ValidationRules{
  4054  												{
  4055  													Rule: "should be forbidden",
  4056  												},
  4057  											},
  4058  										},
  4059  									},
  4060  									OneOf: []apiextensions.JSONSchemaProps{
  4061  										{
  4062  											XValidations: apiextensions.ValidationRules{
  4063  												{
  4064  													Rule: "should be forbidden",
  4065  												},
  4066  											},
  4067  										},
  4068  									},
  4069  								},
  4070  							},
  4071  						},
  4072  					},
  4073  					PreserveUnknownFields: pointer.BoolPtr(false),
  4074  				},
  4075  				Status: apiextensions.CustomResourceDefinitionStatus{
  4076  					StoredVersions: []string{"version"},
  4077  				},
  4078  			},
  4079  			errors: []validationMatch{
  4080  				forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "not", "x-kubernetes-validations"),
  4081  				forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "allOf[0]", "x-kubernetes-validations"),
  4082  				forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "anyOf[0]", "x-kubernetes-validations"),
  4083  				forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "oneOf[0]", "x-kubernetes-validations"),
  4084  			},
  4085  		},
  4086  		{
  4087  			name: "x-kubernetes-validations should have valid reason and fieldPath",
  4088  			resource: &apiextensions.CustomResourceDefinition{
  4089  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  4090  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4091  					Group:    "group.com",
  4092  					Version:  "version",
  4093  					Versions: singleVersionList,
  4094  					Scope:    apiextensions.NamespaceScoped,
  4095  					Names: apiextensions.CustomResourceDefinitionNames{
  4096  						Plural:   "plural",
  4097  						Singular: "singular",
  4098  						Kind:     "Plural",
  4099  						ListKind: "PluralList",
  4100  					},
  4101  					Validation: &apiextensions.CustomResourceValidation{
  4102  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4103  							Type: "object",
  4104  							XValidations: apiextensions.ValidationRules{
  4105  								{
  4106  									Rule: "self.a > 0",
  4107  									Reason: func() *apiextensions.FieldValueErrorReason {
  4108  										r := apiextensions.FieldValueErrorReason("InternalError")
  4109  										return &r
  4110  									}(),
  4111  									FieldPath: ".a",
  4112  								},
  4113  							},
  4114  							Properties: map[string]apiextensions.JSONSchemaProps{
  4115  								"a": {
  4116  									Type: "number",
  4117  									XValidations: apiextensions.ValidationRules{
  4118  										{
  4119  											Rule: "true",
  4120  											Reason: func() *apiextensions.FieldValueErrorReason {
  4121  												r := apiextensions.FieldValueRequired
  4122  												return &r
  4123  											}(),
  4124  										},
  4125  										{
  4126  											Rule: "true",
  4127  											Reason: func() *apiextensions.FieldValueErrorReason {
  4128  												r := apiextensions.FieldValueInvalid
  4129  												return &r
  4130  											}(),
  4131  										},
  4132  										{
  4133  											Rule: "true",
  4134  											Reason: func() *apiextensions.FieldValueErrorReason {
  4135  												r := apiextensions.FieldValueDuplicate
  4136  												return &r
  4137  											}(),
  4138  										},
  4139  										{
  4140  											Rule: "true",
  4141  											Reason: func() *apiextensions.FieldValueErrorReason {
  4142  												r := apiextensions.FieldValueForbidden
  4143  												return &r
  4144  											}(),
  4145  										},
  4146  									},
  4147  								},
  4148  							},
  4149  						},
  4150  					},
  4151  				},
  4152  				Status: apiextensions.CustomResourceDefinitionStatus{
  4153  					StoredVersions: []string{"version"},
  4154  				},
  4155  			},
  4156  			errors: []validationMatch{
  4157  				unsupported("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[0]", "reason"),
  4158  			},
  4159  		},
  4160  		{
  4161  			name: "x-kubernetes-validations should have valid fieldPath for array",
  4162  			resource: &apiextensions.CustomResourceDefinition{
  4163  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  4164  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4165  					Group:    "group.com",
  4166  					Version:  "version",
  4167  					Versions: singleVersionList,
  4168  					Scope:    apiextensions.NamespaceScoped,
  4169  					Names: apiextensions.CustomResourceDefinitionNames{
  4170  						Plural:   "plural",
  4171  						Singular: "singular",
  4172  						Kind:     "Plural",
  4173  						ListKind: "PluralList",
  4174  					},
  4175  					Validation: &apiextensions.CustomResourceValidation{
  4176  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4177  							Type: "object",
  4178  							XValidations: apiextensions.ValidationRules{
  4179  								{
  4180  									Rule:      "true",
  4181  									FieldPath: ".foo['b.c']['c\\a']",
  4182  								},
  4183  								{
  4184  									Rule:      "true",
  4185  									FieldPath: "['a.c']",
  4186  								},
  4187  								{
  4188  									Rule:      "true",
  4189  									FieldPath: ".a.c",
  4190  								},
  4191  								{
  4192  									Rule:      "true",
  4193  									FieldPath: ".list[0]",
  4194  								},
  4195  								{
  4196  									Rule:      "true",
  4197  									FieldPath: "   ",
  4198  								},
  4199  								{
  4200  									Rule:      "true",
  4201  									FieldPath: ".",
  4202  								},
  4203  								{
  4204  									Rule:      "true",
  4205  									FieldPath: "..",
  4206  								},
  4207  							},
  4208  							Properties: map[string]apiextensions.JSONSchemaProps{
  4209  								"a.c": {
  4210  									Type: "number",
  4211  								},
  4212  								"foo": {
  4213  									Type: "object",
  4214  									Properties: map[string]apiextensions.JSONSchemaProps{
  4215  										"b.c": {
  4216  											Type: "object",
  4217  											Properties: map[string]apiextensions.JSONSchemaProps{
  4218  												"c\a": {
  4219  													Type: "number",
  4220  												},
  4221  											},
  4222  										},
  4223  									},
  4224  								},
  4225  								"list": {
  4226  									Type: "array",
  4227  									Items: &apiextensions.JSONSchemaPropsOrArray{
  4228  										Schema: &apiextensions.JSONSchemaProps{
  4229  											Type: "object",
  4230  											Properties: map[string]apiextensions.JSONSchemaProps{
  4231  												"a": {
  4232  													Type: "number",
  4233  												},
  4234  											},
  4235  										},
  4236  									},
  4237  								},
  4238  							},
  4239  						},
  4240  					},
  4241  				},
  4242  				Status: apiextensions.CustomResourceDefinitionStatus{
  4243  					StoredVersions: []string{"version"},
  4244  				},
  4245  			},
  4246  			errors: []validationMatch{
  4247  				invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[2]", "fieldPath"),
  4248  				invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[3]", "fieldPath"),
  4249  				invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[4]", "fieldPath"),
  4250  				invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[4]", "fieldPath"),
  4251  				invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[5]", "fieldPath"),
  4252  				invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[6]", "fieldPath"),
  4253  			},
  4254  		},
  4255  		{
  4256  			name: "x-kubernetes-validations have invalid fieldPath",
  4257  			resource: &apiextensions.CustomResourceDefinition{
  4258  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  4259  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4260  					Group:    "group.com",
  4261  					Version:  "version",
  4262  					Versions: singleVersionList,
  4263  					Scope:    apiextensions.NamespaceScoped,
  4264  					Names: apiextensions.CustomResourceDefinitionNames{
  4265  						Plural:   "plural",
  4266  						Singular: "singular",
  4267  						Kind:     "Plural",
  4268  						ListKind: "PluralList",
  4269  					},
  4270  					Validation: &apiextensions.CustomResourceValidation{
  4271  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4272  							Type: "object",
  4273  							XValidations: apiextensions.ValidationRules{
  4274  								{
  4275  									Rule:      "self.a.b.c > 0.0",
  4276  									FieldPath: ".list[0].b",
  4277  								},
  4278  								{
  4279  									Rule:      "self.a.b.c > 0.0",
  4280  									FieldPath: ".list[0.b",
  4281  								},
  4282  								{
  4283  									Rule:      "self.a.b.c > 0.0",
  4284  									FieldPath: ".list0].b",
  4285  								},
  4286  								{
  4287  									Rule:      "self.a.b.c > 0.0",
  4288  									FieldPath: ".a.c",
  4289  								},
  4290  								{
  4291  									Rule:      "self.a.b.c > 0.0",
  4292  									FieldPath: ".a.b.d",
  4293  								},
  4294  							},
  4295  							Properties: map[string]apiextensions.JSONSchemaProps{
  4296  								"a": {
  4297  									Type: "object",
  4298  									Properties: map[string]apiextensions.JSONSchemaProps{
  4299  										"b": {
  4300  											Type: "object",
  4301  											Properties: map[string]apiextensions.JSONSchemaProps{
  4302  												"c": {
  4303  													Type: "number",
  4304  												},
  4305  											},
  4306  										},
  4307  									},
  4308  								},
  4309  								"list": {
  4310  									Type: "array",
  4311  									Items: &apiextensions.JSONSchemaPropsOrArray{
  4312  										Schema: &apiextensions.JSONSchemaProps{
  4313  											Type: "object",
  4314  											Properties: map[string]apiextensions.JSONSchemaProps{
  4315  												"a": {
  4316  													Type: "number",
  4317  												},
  4318  											},
  4319  										},
  4320  									},
  4321  								},
  4322  							},
  4323  						},
  4324  					},
  4325  				},
  4326  				Status: apiextensions.CustomResourceDefinitionStatus{
  4327  					StoredVersions: []string{"version"},
  4328  				},
  4329  			},
  4330  			errors: []validationMatch{
  4331  				invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[0]", "fieldPath"),
  4332  				invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[1]", "fieldPath"),
  4333  				invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[2]", "fieldPath"),
  4334  				invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[3]", "fieldPath"),
  4335  				invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[4]", "fieldPath"),
  4336  			},
  4337  		},
  4338  	}
  4339  
  4340  	for _, tc := range tests {
  4341  		t.Run(tc.name, func(t *testing.T) {
  4342  
  4343  			// duplicate defaulting behaviour
  4344  			if tc.resource.Spec.Conversion != nil && tc.resource.Spec.Conversion.Strategy == apiextensions.WebhookConverter && len(tc.resource.Spec.Conversion.ConversionReviewVersions) == 0 {
  4345  				tc.resource.Spec.Conversion.ConversionReviewVersions = []string{"v1beta1"}
  4346  			}
  4347  			ctx := context.TODO()
  4348  			errs := ValidateCustomResourceDefinition(ctx, tc.resource)
  4349  			seenErrs := make([]bool, len(errs))
  4350  
  4351  			for _, expectedError := range tc.errors {
  4352  				found := false
  4353  				for i, err := range errs {
  4354  					if expectedError.matches(err) && !seenErrs[i] {
  4355  						found = true
  4356  						seenErrs[i] = true
  4357  						break
  4358  					}
  4359  				}
  4360  
  4361  				if !found {
  4362  					t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
  4363  				}
  4364  			}
  4365  
  4366  			for i, seen := range seenErrs {
  4367  				if !seen {
  4368  					t.Errorf("unexpected error: %v", errs[i])
  4369  				}
  4370  			}
  4371  		})
  4372  	}
  4373  }
  4374  
  4375  func TestSelectableFields(t *testing.T) {
  4376  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceFieldSelectors, true)()
  4377  	singleVersionList := []apiextensions.CustomResourceDefinitionVersion{
  4378  		{
  4379  			Name:    "version",
  4380  			Served:  true,
  4381  			Storage: true,
  4382  		},
  4383  	}
  4384  	tests := []struct {
  4385  		name     string
  4386  		resource *apiextensions.CustomResourceDefinition
  4387  		errors   []validationMatch
  4388  	}{
  4389  		{
  4390  			name: "selectableFields with jsonPaths that do not refer to a field in the schema are invalid",
  4391  			resource: &apiextensions.CustomResourceDefinition{
  4392  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  4393  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4394  					Group:   "group.com",
  4395  					Version: "version",
  4396  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  4397  						{Name: "version", Served: true, Storage: true,
  4398  							Schema: &apiextensions.CustomResourceValidation{
  4399  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4400  									Type:       "object",
  4401  									Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "string"}},
  4402  									Required:   []string{"foo"},
  4403  								},
  4404  							},
  4405  							SelectableFields: []apiextensions.SelectableField{{JSONPath: ".foo"}, {JSONPath: ".xyz"}},
  4406  						},
  4407  						{Name: "version2", Served: true, Storage: false,
  4408  							Schema: &apiextensions.CustomResourceValidation{
  4409  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4410  									Type:       "object",
  4411  									Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "integer"}},
  4412  									Required:   []string{"foo"},
  4413  								},
  4414  							},
  4415  							SelectableFields: []apiextensions.SelectableField{{JSONPath: ".xyz"}, {JSONPath: ".foo"}, {JSONPath: ".abc"}},
  4416  						},
  4417  					},
  4418  					Scope: apiextensions.NamespaceScoped,
  4419  					Names: apiextensions.CustomResourceDefinitionNames{
  4420  						Plural:   "plural",
  4421  						Singular: "singular",
  4422  						Kind:     "Plural",
  4423  						ListKind: "PluralList",
  4424  					},
  4425  					PreserveUnknownFields: ptr.To(false),
  4426  				},
  4427  				Status: apiextensions.CustomResourceDefinitionStatus{
  4428  					StoredVersions: []string{"version"},
  4429  				},
  4430  			},
  4431  			errors: []validationMatch{
  4432  				invalid("spec", "versions[0]", "selectableFields[1].jsonPath"),
  4433  				invalid("spec", "versions[1]", "selectableFields[0].jsonPath"),
  4434  				invalid("spec", "versions[1]", "selectableFields[2].jsonPath"),
  4435  			},
  4436  		},
  4437  		{
  4438  			name: "in top level schema, selectableFields with jsonPaths that do not refer to a field in the schema are invalid",
  4439  			resource: &apiextensions.CustomResourceDefinition{
  4440  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  4441  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4442  					Group:    "group.com",
  4443  					Version:  "version",
  4444  					Versions: singleVersionList,
  4445  					Validation: &apiextensions.CustomResourceValidation{
  4446  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4447  							Type: "object",
  4448  							Properties: map[string]apiextensions.JSONSchemaProps{
  4449  								"spec": {
  4450  									Type:       "object",
  4451  									Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "string"}},
  4452  									Required:   []string{"foo"},
  4453  								},
  4454  								"status": {
  4455  									Type:       "object",
  4456  									Properties: map[string]apiextensions.JSONSchemaProps{"phase": {Type: "string"}},
  4457  									Required:   []string{"phase"},
  4458  								},
  4459  							},
  4460  						},
  4461  					},
  4462  					SelectableFields: []apiextensions.SelectableField{{JSONPath: ".spec.foo"}, {JSONPath: ".spec.xyz"}, {JSONPath: ".status.phase"}},
  4463  					Scope:            apiextensions.NamespaceScoped,
  4464  					Names: apiextensions.CustomResourceDefinitionNames{
  4465  						Plural:   "plural",
  4466  						Singular: "singular",
  4467  						Kind:     "Plural",
  4468  						ListKind: "PluralList",
  4469  					},
  4470  					PreserveUnknownFields: ptr.To(false),
  4471  				},
  4472  				Status: apiextensions.CustomResourceDefinitionStatus{
  4473  					StoredVersions: []string{"version"},
  4474  				},
  4475  			},
  4476  			errors: []validationMatch{
  4477  				invalid("spec", "selectableFields[1].jsonPath"),
  4478  			},
  4479  		},
  4480  		{
  4481  			name: "selectableFields with jsonPaths that do not refer to fields that are not strings, booleans or integers are invalid",
  4482  			resource: &apiextensions.CustomResourceDefinition{
  4483  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  4484  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4485  					Group:   "group.com",
  4486  					Version: "version",
  4487  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  4488  						{Name: "version", Served: true, Storage: true,
  4489  							Schema: &apiextensions.CustomResourceValidation{
  4490  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4491  									Type:       "object",
  4492  									Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "string"}, "obj": {Type: "object"}},
  4493  									Required:   []string{"foo", "obj"},
  4494  								},
  4495  							},
  4496  							SelectableFields: []apiextensions.SelectableField{{JSONPath: ".foo"}, {JSONPath: ".obj"}},
  4497  						},
  4498  						{Name: "version2", Served: true, Storage: false,
  4499  							Schema: &apiextensions.CustomResourceValidation{
  4500  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4501  									Type:       "object",
  4502  									Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "integer"}, "obj": {Type: "object"}, "bool": {Type: "boolean"}},
  4503  									Required:   []string{"foo", "obj", "bool"},
  4504  								},
  4505  							},
  4506  							SelectableFields: []apiextensions.SelectableField{{JSONPath: ".obj"}, {JSONPath: ".foo"}, {JSONPath: ".bool"}},
  4507  						},
  4508  					},
  4509  					Scope: apiextensions.NamespaceScoped,
  4510  					Names: apiextensions.CustomResourceDefinitionNames{
  4511  						Plural:   "plural",
  4512  						Singular: "singular",
  4513  						Kind:     "Plural",
  4514  						ListKind: "PluralList",
  4515  					},
  4516  					PreserveUnknownFields: ptr.To(false),
  4517  				},
  4518  				Status: apiextensions.CustomResourceDefinitionStatus{
  4519  					StoredVersions: []string{"version"},
  4520  				},
  4521  			},
  4522  			errors: []validationMatch{
  4523  				invalid("spec", "versions[0]", "selectableFields[1].jsonPath"),
  4524  				invalid("spec", "versions[1]", "selectableFields[0].jsonPath"),
  4525  			},
  4526  		},
  4527  		{
  4528  			name: "selectableFields with duplicate jsonPaths are invalid",
  4529  			resource: &apiextensions.CustomResourceDefinition{
  4530  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  4531  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4532  					Group:   "group.com",
  4533  					Version: "version",
  4534  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  4535  						{Name: "version", Served: true, Storage: true,
  4536  							Schema: &apiextensions.CustomResourceValidation{
  4537  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4538  									Type:       "object",
  4539  									Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "string"}},
  4540  									Required:   []string{"foo"},
  4541  								},
  4542  							},
  4543  							SelectableFields: []apiextensions.SelectableField{{JSONPath: ".foo"}, {JSONPath: ".foo"}},
  4544  						},
  4545  						{Name: "version2", Served: true, Storage: false,
  4546  							Schema: &apiextensions.CustomResourceValidation{
  4547  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4548  									Type:       "object",
  4549  									Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "integer"}},
  4550  									Required:   []string{"foo"},
  4551  								},
  4552  							},
  4553  							SelectableFields: []apiextensions.SelectableField{{JSONPath: ".foo"}, {JSONPath: ".foo"}},
  4554  						},
  4555  					},
  4556  					Scope: apiextensions.NamespaceScoped,
  4557  					Names: apiextensions.CustomResourceDefinitionNames{
  4558  						Plural:   "plural",
  4559  						Singular: "singular",
  4560  						Kind:     "Plural",
  4561  						ListKind: "PluralList",
  4562  					},
  4563  					PreserveUnknownFields: ptr.To(false),
  4564  				},
  4565  				Status: apiextensions.CustomResourceDefinitionStatus{
  4566  					StoredVersions: []string{"version"},
  4567  				},
  4568  			},
  4569  			errors: []validationMatch{
  4570  				duplicate("spec", "versions[0]", "selectableFields[1].jsonPath"),
  4571  				duplicate("spec", "versions[1]", "selectableFields[1].jsonPath"),
  4572  			},
  4573  		},
  4574  		{
  4575  			name: "too many selectableFields are not allowed",
  4576  			resource: &apiextensions.CustomResourceDefinition{
  4577  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
  4578  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4579  					Group:   "group.com",
  4580  					Version: "version",
  4581  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  4582  						{Name: "version", Served: true, Storage: true,
  4583  							Schema: &apiextensions.CustomResourceValidation{
  4584  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4585  									Type: "object",
  4586  									Properties: map[string]apiextensions.JSONSchemaProps{
  4587  										"a1": {Type: "string"}, "a2": {Type: "string"}, "a3": {Type: "string"},
  4588  										"a4": {Type: "string"}, "a5": {Type: "string"}, "a6": {Type: "string"},
  4589  										"a7": {Type: "string"}, "a8": {Type: "string"}, "a9": {Type: "string"},
  4590  									},
  4591  									Required: []string{"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"},
  4592  								},
  4593  							},
  4594  							SelectableFields: []apiextensions.SelectableField{
  4595  								{JSONPath: ".a1"}, {JSONPath: ".a2"}, {JSONPath: ".a3"},
  4596  								{JSONPath: ".a4"}, {JSONPath: ".a5"}, {JSONPath: ".a6"},
  4597  								{JSONPath: ".a7"}, {JSONPath: ".a8"}, {JSONPath: ".a9"},
  4598  							},
  4599  						},
  4600  						{Name: "version2", Served: true, Storage: false,
  4601  							Schema: &apiextensions.CustomResourceValidation{
  4602  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4603  									Type:       "object",
  4604  									Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "integer"}},
  4605  								},
  4606  							},
  4607  						},
  4608  					},
  4609  					Scope: apiextensions.NamespaceScoped,
  4610  					Names: apiextensions.CustomResourceDefinitionNames{
  4611  						Plural:   "plural",
  4612  						Singular: "singular",
  4613  						Kind:     "Plural",
  4614  						ListKind: "PluralList",
  4615  					},
  4616  					PreserveUnknownFields: ptr.To(false),
  4617  				},
  4618  				Status: apiextensions.CustomResourceDefinitionStatus{
  4619  					StoredVersions: []string{"version"},
  4620  				},
  4621  			},
  4622  			errors: []validationMatch{
  4623  				tooMany("spec", "versions[0]", "selectableFields"),
  4624  			},
  4625  		},
  4626  	}
  4627  
  4628  	for _, tc := range tests {
  4629  		t.Run(tc.name, func(t *testing.T) {
  4630  
  4631  			// duplicate defaulting behaviour
  4632  			if tc.resource.Spec.Conversion != nil && tc.resource.Spec.Conversion.Strategy == apiextensions.WebhookConverter && len(tc.resource.Spec.Conversion.ConversionReviewVersions) == 0 {
  4633  				tc.resource.Spec.Conversion.ConversionReviewVersions = []string{"v1beta1"}
  4634  			}
  4635  			ctx := context.TODO()
  4636  			errs := ValidateCustomResourceDefinition(ctx, tc.resource)
  4637  			seenErrs := make([]bool, len(errs))
  4638  
  4639  			for _, expectedError := range tc.errors {
  4640  				found := false
  4641  				for i, err := range errs {
  4642  					if expectedError.matches(err) && !seenErrs[i] {
  4643  						found = true
  4644  						seenErrs[i] = true
  4645  						break
  4646  					}
  4647  				}
  4648  
  4649  				if !found {
  4650  					t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
  4651  				}
  4652  			}
  4653  
  4654  			for i, seen := range seenErrs {
  4655  				if !seen {
  4656  					t.Errorf("unexpected error: %v", errs[i])
  4657  				}
  4658  			}
  4659  		})
  4660  	}
  4661  }
  4662  
  4663  func TestValidateFieldPath(t *testing.T) {
  4664  	schema := apiextensions.JSONSchemaProps{
  4665  		Type: "object",
  4666  		Properties: map[string]apiextensions.JSONSchemaProps{
  4667  			"foo": {
  4668  				Type: "object",
  4669  				AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  4670  					Schema: &apiextensions.JSONSchemaProps{
  4671  						Type: "object",
  4672  						Properties: map[string]apiextensions.JSONSchemaProps{
  4673  							"f1": {
  4674  								Type: "number",
  4675  							},
  4676  						},
  4677  					},
  4678  				},
  4679  			},
  4680  			"a": {
  4681  				Type: "object",
  4682  				Properties: map[string]apiextensions.JSONSchemaProps{
  4683  					"bbb": {
  4684  						Type: "object",
  4685  						Properties: map[string]apiextensions.JSONSchemaProps{
  4686  							"c": {
  4687  								Type: "number",
  4688  							},
  4689  							"34": {
  4690  								Type: "number",
  4691  							},
  4692  						},
  4693  					},
  4694  					"bbb.c": {
  4695  						Type: "object",
  4696  						Properties: map[string]apiextensions.JSONSchemaProps{
  4697  							"a-b34": {
  4698  								Type: "number",
  4699  							},
  4700  						},
  4701  					},
  4702  				},
  4703  			},
  4704  			"list": {
  4705  				Type: "array",
  4706  				Items: &apiextensions.JSONSchemaPropsOrArray{
  4707  					Schema: &apiextensions.JSONSchemaProps{
  4708  						Type: "object",
  4709  						Properties: map[string]apiextensions.JSONSchemaProps{
  4710  							"a": {
  4711  								Type: "number",
  4712  							},
  4713  							"a-b.34": {
  4714  								Type: "number",
  4715  							},
  4716  						},
  4717  					},
  4718  				},
  4719  			},
  4720  		},
  4721  	}
  4722  
  4723  	path := field.NewPath("")
  4724  
  4725  	tests := []struct {
  4726  		name            string
  4727  		fieldPath       string
  4728  		pathOfFieldPath *field.Path
  4729  		schema          *apiextensions.JSONSchemaProps
  4730  		errMsg          string
  4731  	}{
  4732  		{
  4733  			name:            "Valid .a",
  4734  			fieldPath:       ".a",
  4735  			pathOfFieldPath: path,
  4736  			schema:          &schema,
  4737  		},
  4738  		{
  4739  			name:            "Valid .a.b",
  4740  			fieldPath:       ".a.bbb",
  4741  			pathOfFieldPath: path,
  4742  			schema:          &schema,
  4743  		},
  4744  		{
  4745  			name:            "Valid .foo.f1",
  4746  			fieldPath:       ".foo.f1",
  4747  			pathOfFieldPath: path,
  4748  			schema:          &schema,
  4749  		},
  4750  		{
  4751  			name:            "Invalid map syntax .a.b",
  4752  			fieldPath:       ".a['bbb']",
  4753  			pathOfFieldPath: path,
  4754  			schema:          &schema,
  4755  		},
  4756  		{
  4757  			name:            "Valid .a['bbb.c']",
  4758  			fieldPath:       ".a['bbb.c']",
  4759  			pathOfFieldPath: path,
  4760  			schema:          &schema,
  4761  		},
  4762  		{
  4763  			name:            "Valid .a['bbb.c'].a-b34",
  4764  			fieldPath:       ".a['bbb.c'].a-b34",
  4765  			pathOfFieldPath: path,
  4766  			schema:          &schema,
  4767  		},
  4768  		{
  4769  			name:            "Valid .a['bbb.c']['a-b34']",
  4770  			fieldPath:       ".a['bbb.c']['a-b34']",
  4771  			pathOfFieldPath: path,
  4772  			schema:          &schema,
  4773  		},
  4774  		{
  4775  			name:            "Valid .a.bbb.c",
  4776  			fieldPath:       ".a.bbb.c",
  4777  			pathOfFieldPath: path,
  4778  			schema:          &schema,
  4779  		},
  4780  		{
  4781  			name:            "Valid .a.bbb.34",
  4782  			fieldPath:       ".a.bbb['34']",
  4783  			pathOfFieldPath: path,
  4784  			schema:          &schema,
  4785  		},
  4786  		{
  4787  			name:            "Invalid map key",
  4788  			fieldPath:       ".a.foo",
  4789  			pathOfFieldPath: path,
  4790  			schema:          &schema,
  4791  			errMsg:          "does not refer to a valid field",
  4792  		},
  4793  		{
  4794  			name:            "Malformed map key",
  4795  			fieldPath:       ".a.bbb[0]",
  4796  			pathOfFieldPath: path,
  4797  			schema:          &schema,
  4798  			errMsg:          "expected single quoted string but got 0",
  4799  		},
  4800  		{
  4801  			name:            "number in field names",
  4802  			fieldPath:       ".a.bbb.34",
  4803  			pathOfFieldPath: path,
  4804  			schema:          &schema,
  4805  		},
  4806  		{
  4807  			name:            "Special field names",
  4808  			fieldPath:       ".a.bbb['34']",
  4809  			pathOfFieldPath: path,
  4810  			schema:          &schema,
  4811  		},
  4812  		{
  4813  			name:            "Valid .list",
  4814  			fieldPath:       ".list",
  4815  			pathOfFieldPath: path,
  4816  			schema:          &schema,
  4817  		},
  4818  		{
  4819  			name:            "Invalid .list[1]",
  4820  			fieldPath:       ".list[1]",
  4821  			pathOfFieldPath: path,
  4822  			schema:          &schema,
  4823  			errMsg:          "expected single quoted string but got 1",
  4824  		},
  4825  		{
  4826  			name:            "Unsopported .list.a",
  4827  			fieldPath:       ".list.a",
  4828  			pathOfFieldPath: path,
  4829  			schema:          &schema,
  4830  			errMsg:          "does not refer to a valid field",
  4831  		},
  4832  		{
  4833  			name:            "Unsupported .list['a-b.34']",
  4834  			fieldPath:       ".list['a-b.34']",
  4835  			pathOfFieldPath: path,
  4836  			schema:          &schema,
  4837  			errMsg:          "does not refer to a valid field",
  4838  		},
  4839  		{
  4840  			name:            "Invalid .list.a-b.34",
  4841  			fieldPath:       ".list.a-b.34",
  4842  			pathOfFieldPath: path,
  4843  			schema:          &schema,
  4844  			errMsg:          "does not refer to a valid field",
  4845  		},
  4846  		{
  4847  			name:            "Missing leading dot",
  4848  			fieldPath:       "a",
  4849  			pathOfFieldPath: path,
  4850  			schema:          &schema,
  4851  			errMsg:          "expected [ or . but got: a",
  4852  		},
  4853  		{
  4854  			name:            "Nonexistent field",
  4855  			fieldPath:       ".c",
  4856  			pathOfFieldPath: path,
  4857  			schema:          &schema,
  4858  			errMsg:          "does not refer to a valid field",
  4859  		},
  4860  		{
  4861  			name:            "Duplicate dots",
  4862  			fieldPath:       ".a..b",
  4863  			pathOfFieldPath: path,
  4864  			schema:          &schema,
  4865  			errMsg:          "does not refer to a valid field",
  4866  		},
  4867  		{
  4868  			name:            "Negative array index",
  4869  			fieldPath:       ".list[-1]",
  4870  			pathOfFieldPath: path,
  4871  			schema:          &schema,
  4872  			errMsg:          "expected single quoted string but got -1",
  4873  		},
  4874  		{
  4875  			name:            "Floating-point array index",
  4876  			fieldPath:       ".list[1.0]",
  4877  			pathOfFieldPath: path,
  4878  			schema:          &schema,
  4879  			errMsg:          "expected single quoted string but got 1",
  4880  		},
  4881  	}
  4882  
  4883  	for _, tc := range tests {
  4884  		t.Run(tc.name, func(t *testing.T) {
  4885  			ss, err := structuralschema.NewStructural(tc.schema)
  4886  			if err != nil {
  4887  				t.Fatalf("error when converting schema to structural schema: %v", err)
  4888  			}
  4889  			_, _, err = celschema.ValidFieldPath(tc.fieldPath, ss)
  4890  			if err == nil && tc.errMsg != "" {
  4891  				t.Errorf("expected err contains: %v but get nil", tc.errMsg)
  4892  			}
  4893  			if err != nil && tc.errMsg == "" {
  4894  				t.Errorf("unexpected error: %v", err)
  4895  			}
  4896  			if err != nil && !strings.Contains(err.Error(), tc.errMsg) {
  4897  				t.Errorf("expected error to contain: %v, but get: %v", tc.errMsg, err)
  4898  			}
  4899  		})
  4900  	}
  4901  }
  4902  
  4903  func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
  4904  	tests := []struct {
  4905  		name     string
  4906  		old      *apiextensions.CustomResourceDefinition
  4907  		resource *apiextensions.CustomResourceDefinition
  4908  		errors   []validationMatch
  4909  	}{
  4910  		{
  4911  			name: "invalid types updates disallowed",
  4912  			old: &apiextensions.CustomResourceDefinition{
  4913  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  4914  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4915  					Group: "group.com",
  4916  					Scope: apiextensions.ResourceScope("Cluster"),
  4917  					Names: apiextensions.CustomResourceDefinitionNames{
  4918  						Plural:   "plural",
  4919  						Singular: "singular",
  4920  						Kind:     "Plural",
  4921  						ListKind: "PluralList",
  4922  					},
  4923  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  4924  					Validation: &apiextensions.CustomResourceValidation{
  4925  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4926  							Type: "object",
  4927  						},
  4928  					},
  4929  					PreserveUnknownFields: pointer.BoolPtr(false),
  4930  				},
  4931  				Status: apiextensions.CustomResourceDefinitionStatus{
  4932  					StoredVersions: []string{"version"},
  4933  				},
  4934  			},
  4935  			resource: &apiextensions.CustomResourceDefinition{
  4936  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  4937  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4938  					Group: "group.com",
  4939  					Scope: apiextensions.ResourceScope("Cluster"),
  4940  					Names: apiextensions.CustomResourceDefinitionNames{
  4941  						Plural:   "plural",
  4942  						Singular: "singular",
  4943  						Kind:     "Plural",
  4944  						ListKind: "PluralList",
  4945  					},
  4946  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  4947  					Validation: &apiextensions.CustomResourceValidation{
  4948  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4949  							Type:       "object",
  4950  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "bogus"}},
  4951  						},
  4952  					},
  4953  					PreserveUnknownFields: pointer.BoolPtr(false),
  4954  				},
  4955  				Status: apiextensions.CustomResourceDefinitionStatus{
  4956  					StoredVersions: []string{"version"},
  4957  				},
  4958  			},
  4959  			errors: []validationMatch{
  4960  				unsupported("spec.validation.openAPIV3Schema.properties[foo].type"),
  4961  			},
  4962  		},
  4963  		{
  4964  			name: "invalid types updates allowed if old object has invalid types",
  4965  			old: &apiextensions.CustomResourceDefinition{
  4966  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  4967  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4968  					Group: "group.com",
  4969  					Scope: apiextensions.ResourceScope("Cluster"),
  4970  					Names: apiextensions.CustomResourceDefinitionNames{
  4971  						Plural:   "plural",
  4972  						Singular: "singular",
  4973  						Kind:     "Plural",
  4974  						ListKind: "PluralList",
  4975  					},
  4976  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  4977  					Validation: &apiextensions.CustomResourceValidation{
  4978  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  4979  							Type:       "object",
  4980  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "bogus"}},
  4981  						},
  4982  					},
  4983  					PreserveUnknownFields: pointer.BoolPtr(false),
  4984  				},
  4985  				Status: apiextensions.CustomResourceDefinitionStatus{
  4986  					StoredVersions: []string{"version"},
  4987  				},
  4988  			},
  4989  			resource: &apiextensions.CustomResourceDefinition{
  4990  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  4991  				Spec: apiextensions.CustomResourceDefinitionSpec{
  4992  					Group: "group.com",
  4993  					Scope: apiextensions.ResourceScope("Cluster"),
  4994  					Names: apiextensions.CustomResourceDefinitionNames{
  4995  						Plural:   "plural",
  4996  						Singular: "singular",
  4997  						Kind:     "Plural",
  4998  						ListKind: "PluralList",
  4999  					},
  5000  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  5001  					Validation: &apiextensions.CustomResourceValidation{
  5002  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5003  							Type:       "object",
  5004  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "bogus2"}},
  5005  						},
  5006  					},
  5007  					PreserveUnknownFields: pointer.BoolPtr(false),
  5008  				},
  5009  				Status: apiextensions.CustomResourceDefinitionStatus{
  5010  					StoredVersions: []string{"version"},
  5011  				},
  5012  			},
  5013  		},
  5014  		{
  5015  			name: "non-atomic items in lists of type set allowed if pre-existing",
  5016  			old: &apiextensions.CustomResourceDefinition{
  5017  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  5018  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5019  					Group: "group.com",
  5020  					Scope: apiextensions.ResourceScope("Cluster"),
  5021  					Names: apiextensions.CustomResourceDefinitionNames{
  5022  						Plural:   "plural",
  5023  						Singular: "singular",
  5024  						Kind:     "Plural",
  5025  						ListKind: "PluralList",
  5026  					},
  5027  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  5028  					Validation: &apiextensions.CustomResourceValidation{
  5029  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5030  							Type: "object",
  5031  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
  5032  								Type:      "array",
  5033  								XListType: strPtr("set"),
  5034  								Items: &apiextensions.JSONSchemaPropsOrArray{
  5035  									Schema: &apiextensions.JSONSchemaProps{
  5036  										Type:       "object", // non-atomic
  5037  										Properties: map[string]apiextensions.JSONSchemaProps{},
  5038  									},
  5039  								},
  5040  							}},
  5041  						},
  5042  					},
  5043  					PreserveUnknownFields: pointer.BoolPtr(false),
  5044  				},
  5045  				Status: apiextensions.CustomResourceDefinitionStatus{
  5046  					StoredVersions: []string{"version"},
  5047  				},
  5048  			},
  5049  			resource: &apiextensions.CustomResourceDefinition{
  5050  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  5051  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5052  					Group: "group.com",
  5053  					Scope: apiextensions.ResourceScope("Cluster"),
  5054  					Names: apiextensions.CustomResourceDefinitionNames{
  5055  						Plural:   "plural",
  5056  						Singular: "singular",
  5057  						Kind:     "Plural",
  5058  						ListKind: "PluralList",
  5059  					},
  5060  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  5061  					Validation: &apiextensions.CustomResourceValidation{
  5062  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5063  							Type: "object",
  5064  							Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
  5065  								Type:      "array",
  5066  								XListType: strPtr("set"),
  5067  								Items: &apiextensions.JSONSchemaPropsOrArray{
  5068  									Schema: &apiextensions.JSONSchemaProps{
  5069  										Type:       "object", // non-atomic
  5070  										Properties: map[string]apiextensions.JSONSchemaProps{},
  5071  									},
  5072  								},
  5073  							}},
  5074  						},
  5075  					},
  5076  					PreserveUnknownFields: pointer.BoolPtr(false),
  5077  				},
  5078  				Status: apiextensions.CustomResourceDefinitionStatus{
  5079  					StoredVersions: []string{"version"},
  5080  				},
  5081  			},
  5082  		},
  5083  		{
  5084  			name: "reject non-atomic items in lists of type set if not pre-existing",
  5085  			old: &apiextensions.CustomResourceDefinition{
  5086  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  5087  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5088  					Group: "group.com",
  5089  					Scope: apiextensions.ResourceScope("Cluster"),
  5090  					Names: apiextensions.CustomResourceDefinitionNames{
  5091  						Plural:   "plural",
  5092  						Singular: "singular",
  5093  						Kind:     "Plural",
  5094  						ListKind: "PluralList",
  5095  					},
  5096  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  5097  					Validation: &apiextensions.CustomResourceValidation{
  5098  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5099  							Type:       "object",
  5100  							Properties: map[string]apiextensions.JSONSchemaProps{},
  5101  						},
  5102  					},
  5103  					PreserveUnknownFields: pointer.BoolPtr(false),
  5104  				},
  5105  				Status: apiextensions.CustomResourceDefinitionStatus{
  5106  					StoredVersions: []string{"version"},
  5107  				},
  5108  			},
  5109  			resource: &apiextensions.CustomResourceDefinition{
  5110  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  5111  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5112  					Group: "group.com",
  5113  					Scope: apiextensions.ResourceScope("Cluster"),
  5114  					Names: apiextensions.CustomResourceDefinitionNames{
  5115  						Plural:   "plural",
  5116  						Singular: "singular",
  5117  						Kind:     "Plural",
  5118  						ListKind: "PluralList",
  5119  					},
  5120  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  5121  					Validation: &apiextensions.CustomResourceValidation{
  5122  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5123  							Type: "object",
  5124  							Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
  5125  								Type:      "array",
  5126  								XListType: strPtr("set"),
  5127  								Items: &apiextensions.JSONSchemaPropsOrArray{
  5128  									Schema: &apiextensions.JSONSchemaProps{
  5129  										Type:       "object", // non-atomic
  5130  										Properties: map[string]apiextensions.JSONSchemaProps{},
  5131  									},
  5132  								},
  5133  							}},
  5134  						},
  5135  					},
  5136  					PreserveUnknownFields: pointer.BoolPtr(false),
  5137  				},
  5138  				Status: apiextensions.CustomResourceDefinitionStatus{
  5139  					StoredVersions: []string{"version"},
  5140  				},
  5141  			},
  5142  			errors: []validationMatch{
  5143  				invalid("spec", "validation", "openAPIV3Schema", "properties[bar]", "items", "x-kubernetes-map-type"),
  5144  			},
  5145  		},
  5146  		{
  5147  			name: "structural to non-structural updates not allowed",
  5148  			old: &apiextensions.CustomResourceDefinition{
  5149  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  5150  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5151  					Group:    "group.com",
  5152  					Scope:    apiextensions.ResourceScope("Cluster"),
  5153  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  5154  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  5155  					Validation: &apiextensions.CustomResourceValidation{
  5156  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5157  							Type:       "object",
  5158  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "integer"}},
  5159  						},
  5160  					},
  5161  					PreserveUnknownFields: pointer.BoolPtr(true),
  5162  				},
  5163  				Status: apiextensions.CustomResourceDefinitionStatus{
  5164  					StoredVersions: []string{"version"},
  5165  				},
  5166  			},
  5167  			resource: &apiextensions.CustomResourceDefinition{
  5168  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  5169  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5170  					Group:    "group.com",
  5171  					Scope:    apiextensions.ResourceScope("Cluster"),
  5172  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  5173  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  5174  					Validation: &apiextensions.CustomResourceValidation{
  5175  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5176  							Type:       "object",
  5177  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {}}, // untyped object
  5178  						},
  5179  					},
  5180  					PreserveUnknownFields: pointer.BoolPtr(true),
  5181  				},
  5182  				Status: apiextensions.CustomResourceDefinitionStatus{
  5183  					StoredVersions: []string{"version"},
  5184  				},
  5185  			},
  5186  			errors: []validationMatch{
  5187  				required("spec.validation.openAPIV3Schema.properties[foo].type"),
  5188  			},
  5189  		},
  5190  		{
  5191  			name: "absent schema to non-structural updates not allowed",
  5192  			old: &apiextensions.CustomResourceDefinition{
  5193  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  5194  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5195  					Group:                 "group.com",
  5196  					Scope:                 apiextensions.ResourceScope("Cluster"),
  5197  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  5198  					Versions:              []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  5199  					Validation:            &apiextensions.CustomResourceValidation{},
  5200  					PreserveUnknownFields: pointer.BoolPtr(true),
  5201  				},
  5202  				Status: apiextensions.CustomResourceDefinitionStatus{
  5203  					StoredVersions: []string{"version"},
  5204  				},
  5205  			},
  5206  			resource: &apiextensions.CustomResourceDefinition{
  5207  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  5208  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5209  					Group:    "group.com",
  5210  					Scope:    apiextensions.ResourceScope("Cluster"),
  5211  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  5212  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  5213  					Validation: &apiextensions.CustomResourceValidation{
  5214  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5215  							Type:       "object",
  5216  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {}}, // untyped object
  5217  						},
  5218  					},
  5219  					PreserveUnknownFields: pointer.BoolPtr(true),
  5220  				},
  5221  				Status: apiextensions.CustomResourceDefinitionStatus{
  5222  					StoredVersions: []string{"version"},
  5223  				},
  5224  			},
  5225  			errors: []validationMatch{
  5226  				required("spec.validation.openAPIV3Schema.properties[foo].type"),
  5227  			},
  5228  		},
  5229  		{
  5230  			name: "non-structural updates allowed if old object has non-structural schema",
  5231  			old: &apiextensions.CustomResourceDefinition{
  5232  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  5233  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5234  					Group: "group.com",
  5235  					Scope: apiextensions.ResourceScope("Cluster"),
  5236  					Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  5237  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5238  						{Name: "version", Served: true, Storage: true, Schema: &apiextensions.CustomResourceValidation{
  5239  							OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5240  								Type:       "object",
  5241  								Properties: map[string]apiextensions.JSONSchemaProps{"foo": {}}, // untyped object, non-structural
  5242  							},
  5243  						}},
  5244  						{Name: "version2", Served: true, Storage: false, Schema: &apiextensions.CustomResourceValidation{
  5245  							OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5246  								Type:       "object",
  5247  								Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "number"}}, // structural
  5248  							},
  5249  						}},
  5250  					},
  5251  					PreserveUnknownFields: pointer.BoolPtr(true),
  5252  				},
  5253  				Status: apiextensions.CustomResourceDefinitionStatus{
  5254  					StoredVersions: []string{"version"},
  5255  				},
  5256  			},
  5257  			resource: &apiextensions.CustomResourceDefinition{
  5258  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  5259  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5260  					Group: "group.com",
  5261  					Scope: apiextensions.ResourceScope("Cluster"),
  5262  					Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  5263  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5264  						{Name: "version", Served: true, Storage: true, Schema: &apiextensions.CustomResourceValidation{
  5265  							OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5266  								Type:       "object",
  5267  								Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Description: "b"}}, // untyped object, non-structural
  5268  							},
  5269  						}},
  5270  						{Name: "version2", Served: true, Storage: false, Schema: &apiextensions.CustomResourceValidation{
  5271  							OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5272  								Type:       "object",
  5273  								Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Description: "a"}}, // untyped object, non-structural
  5274  							},
  5275  						}},
  5276  					},
  5277  					PreserveUnknownFields: pointer.BoolPtr(true),
  5278  				},
  5279  				Status: apiextensions.CustomResourceDefinitionStatus{
  5280  					StoredVersions: []string{"version"},
  5281  				},
  5282  			},
  5283  			errors: []validationMatch{},
  5284  		},
  5285  		{
  5286  			name: "webhookconfig: should pass on invalid ConversionReviewVersion with old invalid versions",
  5287  			resource: &apiextensions.CustomResourceDefinition{
  5288  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
  5289  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5290  					Group: "group.com",
  5291  					Scope: apiextensions.ResourceScope("Cluster"),
  5292  					Names: apiextensions.CustomResourceDefinitionNames{
  5293  						Plural:   "plural",
  5294  						Singular: "singular",
  5295  						Kind:     "Plural",
  5296  						ListKind: "PluralList",
  5297  					},
  5298  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5299  						{
  5300  							Name:    "version",
  5301  							Served:  true,
  5302  							Storage: true,
  5303  						},
  5304  						{
  5305  							Name:    "version2",
  5306  							Served:  true,
  5307  							Storage: false,
  5308  						},
  5309  					},
  5310  					Conversion: &apiextensions.CustomResourceConversion{
  5311  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
  5312  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
  5313  							URL: strPtr("https://example.com/webhook"),
  5314  						},
  5315  						ConversionReviewVersions: []string{"invalid-version"},
  5316  					},
  5317  					Validation: &apiextensions.CustomResourceValidation{
  5318  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5319  							Type: "object",
  5320  						},
  5321  					},
  5322  					PreserveUnknownFields: pointer.BoolPtr(false),
  5323  				},
  5324  				Status: apiextensions.CustomResourceDefinitionStatus{
  5325  					StoredVersions: []string{"version"},
  5326  				},
  5327  			},
  5328  			old: &apiextensions.CustomResourceDefinition{
  5329  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
  5330  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5331  					Group: "group.com",
  5332  					Scope: apiextensions.ResourceScope("Cluster"),
  5333  					Names: apiextensions.CustomResourceDefinitionNames{
  5334  						Plural:   "plural",
  5335  						Singular: "singular",
  5336  						Kind:     "Plural",
  5337  						ListKind: "PluralList",
  5338  					},
  5339  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5340  						{
  5341  							Name:    "version",
  5342  							Served:  true,
  5343  							Storage: true,
  5344  						},
  5345  						{
  5346  							Name:    "version2",
  5347  							Served:  true,
  5348  							Storage: false,
  5349  						},
  5350  					},
  5351  					Conversion: &apiextensions.CustomResourceConversion{
  5352  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
  5353  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
  5354  							URL: strPtr("https://example.com/webhook"),
  5355  						},
  5356  						ConversionReviewVersions: []string{"invalid-version_0, invalid-version"},
  5357  					},
  5358  					Validation: &apiextensions.CustomResourceValidation{
  5359  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5360  							Type: "object",
  5361  						},
  5362  					},
  5363  					PreserveUnknownFields: pointer.BoolPtr(false),
  5364  				},
  5365  				Status: apiextensions.CustomResourceDefinitionStatus{
  5366  					StoredVersions: []string{"version"},
  5367  				},
  5368  			},
  5369  			errors: []validationMatch{},
  5370  		},
  5371  		{
  5372  			name: "webhookconfig: should fail on invalid ConversionReviewVersion with old valid versions",
  5373  			resource: &apiextensions.CustomResourceDefinition{
  5374  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
  5375  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5376  					Group: "group.com",
  5377  					Scope: apiextensions.ResourceScope("Cluster"),
  5378  					Names: apiextensions.CustomResourceDefinitionNames{
  5379  						Plural:   "plural",
  5380  						Singular: "singular",
  5381  						Kind:     "Plural",
  5382  						ListKind: "PluralList",
  5383  					},
  5384  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5385  						{
  5386  							Name:    "version",
  5387  							Served:  true,
  5388  							Storage: true,
  5389  						},
  5390  						{
  5391  							Name:    "version2",
  5392  							Served:  true,
  5393  							Storage: false,
  5394  						},
  5395  					},
  5396  					Conversion: &apiextensions.CustomResourceConversion{
  5397  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
  5398  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
  5399  							URL: strPtr("https://example.com/webhook"),
  5400  						},
  5401  						ConversionReviewVersions: []string{"invalid-version"},
  5402  					},
  5403  					Validation: &apiextensions.CustomResourceValidation{
  5404  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5405  							Type: "object",
  5406  						},
  5407  					},
  5408  					PreserveUnknownFields: pointer.BoolPtr(false),
  5409  				},
  5410  				Status: apiextensions.CustomResourceDefinitionStatus{
  5411  					StoredVersions: []string{"version"},
  5412  				},
  5413  			},
  5414  			old: &apiextensions.CustomResourceDefinition{
  5415  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
  5416  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5417  					Group: "group.com",
  5418  					Scope: apiextensions.ResourceScope("Cluster"),
  5419  					Names: apiextensions.CustomResourceDefinitionNames{
  5420  						Plural:   "plural",
  5421  						Singular: "singular",
  5422  						Kind:     "Plural",
  5423  						ListKind: "PluralList",
  5424  					},
  5425  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5426  						{
  5427  							Name:    "version",
  5428  							Served:  true,
  5429  							Storage: true,
  5430  						},
  5431  						{
  5432  							Name:    "version2",
  5433  							Served:  true,
  5434  							Storage: false,
  5435  						},
  5436  					},
  5437  					Conversion: &apiextensions.CustomResourceConversion{
  5438  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
  5439  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
  5440  							URL: strPtr("https://example.com/webhook"),
  5441  						},
  5442  						ConversionReviewVersions: []string{"v1beta1", "invalid-version"},
  5443  					},
  5444  					Validation: &apiextensions.CustomResourceValidation{
  5445  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5446  							Type: "object",
  5447  						},
  5448  					},
  5449  					PreserveUnknownFields: pointer.BoolPtr(false),
  5450  				},
  5451  				Status: apiextensions.CustomResourceDefinitionStatus{
  5452  					StoredVersions: []string{"version"},
  5453  				},
  5454  			},
  5455  			errors: []validationMatch{
  5456  				invalid("spec", "conversion", "conversionReviewVersions"),
  5457  			},
  5458  		},
  5459  		{
  5460  			name: "webhookconfig: should fail on invalid ConversionReviewVersion with missing old versions",
  5461  			resource: &apiextensions.CustomResourceDefinition{
  5462  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
  5463  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5464  					Group: "group.com",
  5465  					Scope: apiextensions.ResourceScope("Cluster"),
  5466  					Names: apiextensions.CustomResourceDefinitionNames{
  5467  						Plural:   "plural",
  5468  						Singular: "singular",
  5469  						Kind:     "Plural",
  5470  						ListKind: "PluralList",
  5471  					},
  5472  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5473  						{
  5474  							Name:    "version",
  5475  							Served:  true,
  5476  							Storage: true,
  5477  						},
  5478  						{
  5479  							Name:    "version2",
  5480  							Served:  true,
  5481  							Storage: false,
  5482  						},
  5483  					},
  5484  					Conversion: &apiextensions.CustomResourceConversion{
  5485  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
  5486  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
  5487  							URL: strPtr("https://example.com/webhook"),
  5488  						},
  5489  						ConversionReviewVersions: []string{"invalid-version"},
  5490  					},
  5491  					Validation: &apiextensions.CustomResourceValidation{
  5492  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5493  							Type: "object",
  5494  						},
  5495  					},
  5496  					PreserveUnknownFields: pointer.BoolPtr(false),
  5497  				},
  5498  				Status: apiextensions.CustomResourceDefinitionStatus{
  5499  					StoredVersions: []string{"version"},
  5500  				},
  5501  			},
  5502  			old: &apiextensions.CustomResourceDefinition{
  5503  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
  5504  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5505  					Group: "group.com",
  5506  					Scope: apiextensions.ResourceScope("Cluster"),
  5507  					Names: apiextensions.CustomResourceDefinitionNames{
  5508  						Plural:   "plural",
  5509  						Singular: "singular",
  5510  						Kind:     "Plural",
  5511  						ListKind: "PluralList",
  5512  					},
  5513  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5514  						{
  5515  							Name:    "version",
  5516  							Served:  true,
  5517  							Storage: true,
  5518  						},
  5519  						{
  5520  							Name:    "version2",
  5521  							Served:  true,
  5522  							Storage: false,
  5523  						},
  5524  					},
  5525  					Conversion: &apiextensions.CustomResourceConversion{
  5526  						Strategy: apiextensions.ConversionStrategyType("Webhook"),
  5527  						WebhookClientConfig: &apiextensions.WebhookClientConfig{
  5528  							URL: strPtr("https://example.com/webhook"),
  5529  						},
  5530  					},
  5531  					Validation: &apiextensions.CustomResourceValidation{
  5532  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  5533  							Type: "object",
  5534  						},
  5535  					},
  5536  					PreserveUnknownFields: pointer.BoolPtr(false),
  5537  				},
  5538  				Status: apiextensions.CustomResourceDefinitionStatus{
  5539  					StoredVersions: []string{"version"},
  5540  				},
  5541  			},
  5542  			errors: []validationMatch{
  5543  				invalid("spec", "conversion", "conversionReviewVersions"),
  5544  			},
  5545  		},
  5546  		{
  5547  			name: "unchanged",
  5548  			old: &apiextensions.CustomResourceDefinition{
  5549  				ObjectMeta: metav1.ObjectMeta{
  5550  					Name:            "plural.group.com",
  5551  					ResourceVersion: "42",
  5552  				},
  5553  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5554  					Group:   "group.com",
  5555  					Version: "version",
  5556  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5557  						{
  5558  							Name:    "version",
  5559  							Served:  true,
  5560  							Storage: true,
  5561  						},
  5562  					},
  5563  					Scope: apiextensions.ResourceScope("Cluster"),
  5564  					Names: apiextensions.CustomResourceDefinitionNames{
  5565  						Plural:   "plural",
  5566  						Singular: "singular",
  5567  						Kind:     "kind",
  5568  						ListKind: "listkind",
  5569  					},
  5570  					PreserveUnknownFields: pointer.BoolPtr(true),
  5571  				},
  5572  				Status: apiextensions.CustomResourceDefinitionStatus{
  5573  					AcceptedNames: apiextensions.CustomResourceDefinitionNames{
  5574  						Plural:   "plural",
  5575  						Singular: "singular",
  5576  						Kind:     "kind",
  5577  						ListKind: "listkind",
  5578  					},
  5579  				},
  5580  			},
  5581  			resource: &apiextensions.CustomResourceDefinition{
  5582  				ObjectMeta: metav1.ObjectMeta{
  5583  					Name:            "plural.group.com",
  5584  					ResourceVersion: "42",
  5585  				},
  5586  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5587  					Group:   "group.com",
  5588  					Version: "version",
  5589  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5590  						{
  5591  							Name:    "version",
  5592  							Served:  true,
  5593  							Storage: true,
  5594  						},
  5595  					},
  5596  					Scope: apiextensions.ResourceScope("Cluster"),
  5597  					Names: apiextensions.CustomResourceDefinitionNames{
  5598  						Plural:   "plural",
  5599  						Singular: "singular",
  5600  						Kind:     "kind",
  5601  						ListKind: "listkind",
  5602  					},
  5603  					PreserveUnknownFields: pointer.BoolPtr(true),
  5604  				},
  5605  				Status: apiextensions.CustomResourceDefinitionStatus{
  5606  					AcceptedNames: apiextensions.CustomResourceDefinitionNames{
  5607  						Plural:   "plural",
  5608  						Singular: "singular",
  5609  						Kind:     "kind",
  5610  						ListKind: "listkind",
  5611  					},
  5612  					StoredVersions: []string{"version"},
  5613  				},
  5614  			},
  5615  			errors: []validationMatch{},
  5616  		},
  5617  		{
  5618  			name: "unchanged-established",
  5619  			old: &apiextensions.CustomResourceDefinition{
  5620  				ObjectMeta: metav1.ObjectMeta{
  5621  					Name:            "plural.group.com",
  5622  					ResourceVersion: "42",
  5623  				},
  5624  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5625  					Group:   "group.com",
  5626  					Version: "version",
  5627  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5628  						{
  5629  							Name:    "version",
  5630  							Served:  true,
  5631  							Storage: true,
  5632  						},
  5633  					},
  5634  					Scope: apiextensions.ResourceScope("Cluster"),
  5635  					Names: apiextensions.CustomResourceDefinitionNames{
  5636  						Plural:   "plural",
  5637  						Singular: "singular",
  5638  						Kind:     "kind",
  5639  						ListKind: "listkind",
  5640  					},
  5641  					PreserveUnknownFields: pointer.BoolPtr(true),
  5642  				},
  5643  				Status: apiextensions.CustomResourceDefinitionStatus{
  5644  					AcceptedNames: apiextensions.CustomResourceDefinitionNames{
  5645  						Plural:   "plural",
  5646  						Singular: "singular",
  5647  						Kind:     "kind",
  5648  						ListKind: "listkind",
  5649  					},
  5650  					Conditions: []apiextensions.CustomResourceDefinitionCondition{
  5651  						{Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
  5652  					},
  5653  				},
  5654  			},
  5655  			resource: &apiextensions.CustomResourceDefinition{
  5656  				ObjectMeta: metav1.ObjectMeta{
  5657  					Name:            "plural.group.com",
  5658  					ResourceVersion: "42",
  5659  				},
  5660  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5661  					Group:   "group.com",
  5662  					Version: "version",
  5663  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5664  						{
  5665  							Name:    "version",
  5666  							Served:  true,
  5667  							Storage: true,
  5668  						},
  5669  					},
  5670  					Scope: apiextensions.ResourceScope("Cluster"),
  5671  					Names: apiextensions.CustomResourceDefinitionNames{
  5672  						Plural:   "plural",
  5673  						Singular: "singular",
  5674  						Kind:     "kind",
  5675  						ListKind: "listkind",
  5676  					},
  5677  					PreserveUnknownFields: pointer.BoolPtr(true),
  5678  				},
  5679  				Status: apiextensions.CustomResourceDefinitionStatus{
  5680  					AcceptedNames: apiextensions.CustomResourceDefinitionNames{
  5681  						Plural:   "plural",
  5682  						Singular: "singular",
  5683  						Kind:     "kind",
  5684  						ListKind: "listkind",
  5685  					},
  5686  					StoredVersions: []string{"version"},
  5687  				},
  5688  			},
  5689  			errors: []validationMatch{},
  5690  		},
  5691  		{
  5692  			name: "version-deleted",
  5693  			old: &apiextensions.CustomResourceDefinition{
  5694  				ObjectMeta: metav1.ObjectMeta{
  5695  					Name:            "plural.group.com",
  5696  					ResourceVersion: "42",
  5697  				},
  5698  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5699  					Group:   "group.com",
  5700  					Version: "version",
  5701  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5702  						{
  5703  							Name:    "version",
  5704  							Served:  true,
  5705  							Storage: true,
  5706  						},
  5707  						{
  5708  							Name:    "version2",
  5709  							Served:  true,
  5710  							Storage: false,
  5711  						},
  5712  					},
  5713  					Scope: apiextensions.ResourceScope("Cluster"),
  5714  					Names: apiextensions.CustomResourceDefinitionNames{
  5715  						Plural:   "plural",
  5716  						Singular: "singular",
  5717  						Kind:     "kind",
  5718  						ListKind: "listkind",
  5719  					},
  5720  					PreserveUnknownFields: pointer.BoolPtr(true),
  5721  				},
  5722  				Status: apiextensions.CustomResourceDefinitionStatus{
  5723  					AcceptedNames: apiextensions.CustomResourceDefinitionNames{
  5724  						Plural:   "plural",
  5725  						Singular: "singular",
  5726  						Kind:     "kind",
  5727  						ListKind: "listkind",
  5728  					},
  5729  					StoredVersions: []string{"version", "version2"},
  5730  					Conditions: []apiextensions.CustomResourceDefinitionCondition{
  5731  						{Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
  5732  					},
  5733  				},
  5734  			},
  5735  			resource: &apiextensions.CustomResourceDefinition{
  5736  				ObjectMeta: metav1.ObjectMeta{
  5737  					Name:            "plural.group.com",
  5738  					ResourceVersion: "42",
  5739  				},
  5740  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5741  					Group:   "group.com",
  5742  					Version: "version",
  5743  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5744  						{
  5745  							Name:    "version",
  5746  							Served:  true,
  5747  							Storage: true,
  5748  						},
  5749  					},
  5750  					Scope: apiextensions.ResourceScope("Cluster"),
  5751  					Names: apiextensions.CustomResourceDefinitionNames{
  5752  						Plural:   "plural",
  5753  						Singular: "singular",
  5754  						Kind:     "kind",
  5755  						ListKind: "listkind",
  5756  					},
  5757  					PreserveUnknownFields: pointer.BoolPtr(true),
  5758  				},
  5759  				Status: apiextensions.CustomResourceDefinitionStatus{
  5760  					AcceptedNames: apiextensions.CustomResourceDefinitionNames{
  5761  						Plural:   "plural",
  5762  						Singular: "singular",
  5763  						Kind:     "kind",
  5764  						ListKind: "listkind",
  5765  					},
  5766  					StoredVersions: []string{"version", "version2"},
  5767  				},
  5768  			},
  5769  			errors: []validationMatch{
  5770  				invalid("status", "storedVersions[1]"),
  5771  			},
  5772  		},
  5773  		{
  5774  			name: "changes",
  5775  			old: &apiextensions.CustomResourceDefinition{
  5776  				ObjectMeta: metav1.ObjectMeta{
  5777  					Name:            "plural.group.com",
  5778  					ResourceVersion: "42",
  5779  				},
  5780  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5781  					Group:   "group.com",
  5782  					Version: "version",
  5783  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5784  						{
  5785  							Name:    "version",
  5786  							Served:  true,
  5787  							Storage: true,
  5788  						},
  5789  					},
  5790  					Scope: apiextensions.ResourceScope("Cluster"),
  5791  					Names: apiextensions.CustomResourceDefinitionNames{
  5792  						Plural:   "plural",
  5793  						Singular: "singular",
  5794  						Kind:     "kind",
  5795  						ListKind: "listkind",
  5796  					},
  5797  					PreserveUnknownFields: pointer.BoolPtr(true),
  5798  				},
  5799  				Status: apiextensions.CustomResourceDefinitionStatus{
  5800  					AcceptedNames: apiextensions.CustomResourceDefinitionNames{
  5801  						Plural:   "plural",
  5802  						Singular: "singular",
  5803  						Kind:     "kind",
  5804  						ListKind: "listkind",
  5805  					},
  5806  					Conditions: []apiextensions.CustomResourceDefinitionCondition{
  5807  						{Type: apiextensions.Established, Status: apiextensions.ConditionFalse},
  5808  					},
  5809  				},
  5810  			},
  5811  			resource: &apiextensions.CustomResourceDefinition{
  5812  				ObjectMeta: metav1.ObjectMeta{
  5813  					Name:            "plural.group.com",
  5814  					ResourceVersion: "42",
  5815  				},
  5816  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5817  					Group:   "abc.com",
  5818  					Version: "version2",
  5819  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5820  						{
  5821  							Name:    "version2",
  5822  							Served:  true,
  5823  							Storage: true,
  5824  						},
  5825  					},
  5826  					Scope: apiextensions.ResourceScope("Namespaced"),
  5827  					Names: apiextensions.CustomResourceDefinitionNames{
  5828  						Plural:   "plural2",
  5829  						Singular: "singular2",
  5830  						Kind:     "kind2",
  5831  						ListKind: "listkind2",
  5832  					},
  5833  					PreserveUnknownFields: pointer.BoolPtr(true),
  5834  				},
  5835  				Status: apiextensions.CustomResourceDefinitionStatus{
  5836  					AcceptedNames: apiextensions.CustomResourceDefinitionNames{
  5837  						Plural:   "plural2",
  5838  						Singular: "singular2",
  5839  						Kind:     "kind2",
  5840  						ListKind: "listkind2",
  5841  					},
  5842  					StoredVersions: []string{"version2"},
  5843  				},
  5844  			},
  5845  			errors: []validationMatch{
  5846  				immutable("spec", "group"),
  5847  				immutable("spec", "names", "plural"),
  5848  			},
  5849  		},
  5850  		{
  5851  			name: "changes-established",
  5852  			old: &apiextensions.CustomResourceDefinition{
  5853  				ObjectMeta: metav1.ObjectMeta{
  5854  					Name:            "plural.group.com",
  5855  					ResourceVersion: "42",
  5856  				},
  5857  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5858  					Group:   "group.com",
  5859  					Version: "version",
  5860  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5861  						{
  5862  							Name:    "version",
  5863  							Served:  true,
  5864  							Storage: true,
  5865  						},
  5866  					},
  5867  					Scope: apiextensions.ResourceScope("Cluster"),
  5868  					Names: apiextensions.CustomResourceDefinitionNames{
  5869  						Plural:   "plural",
  5870  						Singular: "singular",
  5871  						Kind:     "kind",
  5872  						ListKind: "listkind",
  5873  					},
  5874  					PreserveUnknownFields: pointer.BoolPtr(true),
  5875  				},
  5876  				Status: apiextensions.CustomResourceDefinitionStatus{
  5877  					AcceptedNames: apiextensions.CustomResourceDefinitionNames{
  5878  						Plural:   "plural",
  5879  						Singular: "singular",
  5880  						Kind:     "kind",
  5881  						ListKind: "listkind",
  5882  					},
  5883  					Conditions: []apiextensions.CustomResourceDefinitionCondition{
  5884  						{Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
  5885  					},
  5886  				},
  5887  			},
  5888  			resource: &apiextensions.CustomResourceDefinition{
  5889  				ObjectMeta: metav1.ObjectMeta{
  5890  					Name:            "plural.group.com",
  5891  					ResourceVersion: "42",
  5892  				},
  5893  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5894  					Group:   "abc.com",
  5895  					Version: "version2",
  5896  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5897  						{
  5898  							Name:    "version2",
  5899  							Served:  true,
  5900  							Storage: true,
  5901  						},
  5902  					},
  5903  					Scope: apiextensions.ResourceScope("Namespaced"),
  5904  					Names: apiextensions.CustomResourceDefinitionNames{
  5905  						Plural:   "plural2",
  5906  						Singular: "singular2",
  5907  						Kind:     "kind2",
  5908  						ListKind: "listkind2",
  5909  					},
  5910  					PreserveUnknownFields: pointer.BoolPtr(true),
  5911  				},
  5912  				Status: apiextensions.CustomResourceDefinitionStatus{
  5913  					AcceptedNames: apiextensions.CustomResourceDefinitionNames{
  5914  						Plural:   "plural2",
  5915  						Singular: "singular2",
  5916  						Kind:     "kind2",
  5917  						ListKind: "listkind2",
  5918  					},
  5919  					StoredVersions: []string{"version2"},
  5920  				},
  5921  			},
  5922  			errors: []validationMatch{
  5923  				immutable("spec", "group"),
  5924  				immutable("spec", "scope"),
  5925  				immutable("spec", "names", "kind"),
  5926  				immutable("spec", "names", "plural"),
  5927  			},
  5928  		},
  5929  		{
  5930  			name: "top-level and per-version fields are mutually exclusive",
  5931  			old: &apiextensions.CustomResourceDefinition{
  5932  				ObjectMeta: metav1.ObjectMeta{
  5933  					Name:            "plural.group.com",
  5934  					ResourceVersion: "42",
  5935  				},
  5936  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5937  					Group:   "group.com",
  5938  					Version: "version",
  5939  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5940  						{
  5941  							Name:         "version",
  5942  							Served:       true,
  5943  							Storage:      true,
  5944  							Subresources: &apiextensions.CustomResourceSubresources{},
  5945  						},
  5946  						{
  5947  							Name:    "version2",
  5948  							Served:  true,
  5949  							Storage: false,
  5950  						},
  5951  					},
  5952  					Scope: apiextensions.NamespaceScoped,
  5953  					Names: apiextensions.CustomResourceDefinitionNames{
  5954  						Plural:   "plural",
  5955  						Singular: "singular",
  5956  						Kind:     "Plural",
  5957  						ListKind: "PluralList",
  5958  					},
  5959  					PreserveUnknownFields: pointer.BoolPtr(true),
  5960  				},
  5961  				Status: apiextensions.CustomResourceDefinitionStatus{
  5962  					StoredVersions: []string{"version"},
  5963  				},
  5964  			},
  5965  			resource: &apiextensions.CustomResourceDefinition{
  5966  				ObjectMeta: metav1.ObjectMeta{
  5967  					Name:            "plural.group.com",
  5968  					ResourceVersion: "42",
  5969  				},
  5970  				Spec: apiextensions.CustomResourceDefinitionSpec{
  5971  					Group:   "group.com",
  5972  					Version: "version",
  5973  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  5974  						{
  5975  							Name:    "version",
  5976  							Served:  true,
  5977  							Storage: true,
  5978  						},
  5979  						{
  5980  							Name:    "version2",
  5981  							Served:  true,
  5982  							Storage: false,
  5983  							Schema: &apiextensions.CustomResourceValidation{
  5984  								OpenAPIV3Schema: validValidationSchema,
  5985  							},
  5986  							Subresources:             &apiextensions.CustomResourceSubresources{},
  5987  							AdditionalPrinterColumns: []apiextensions.CustomResourceColumnDefinition{{Name: "Alpha", Type: "string", JSONPath: ".spec.alpha"}},
  5988  						},
  5989  					},
  5990  					Validation: &apiextensions.CustomResourceValidation{
  5991  						OpenAPIV3Schema: validValidationSchema,
  5992  					},
  5993  					Subresources: &apiextensions.CustomResourceSubresources{},
  5994  					Scope:        apiextensions.NamespaceScoped,
  5995  					Names: apiextensions.CustomResourceDefinitionNames{
  5996  						Plural:   "plural",
  5997  						Singular: "singular",
  5998  						Kind:     "Plural",
  5999  						ListKind: "PluralList",
  6000  					},
  6001  					PreserveUnknownFields: pointer.BoolPtr(true),
  6002  				},
  6003  				Status: apiextensions.CustomResourceDefinitionStatus{
  6004  					StoredVersions: []string{"version"},
  6005  				},
  6006  			},
  6007  			errors: []validationMatch{
  6008  				forbidden("spec", "validation"),
  6009  				forbidden("spec", "subresources"),
  6010  			},
  6011  		},
  6012  		{
  6013  			name: "switch off preserveUnknownFields with structural schema before and after",
  6014  			old: &apiextensions.CustomResourceDefinition{
  6015  				ObjectMeta: metav1.ObjectMeta{
  6016  					Name:            "plural.group.com",
  6017  					ResourceVersion: "42",
  6018  				},
  6019  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6020  					Group:   "group.com",
  6021  					Version: "version",
  6022  					Validation: &apiextensions.CustomResourceValidation{
  6023  						OpenAPIV3Schema: validValidationSchema,
  6024  					},
  6025  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  6026  						{
  6027  							Name:    "version",
  6028  							Served:  true,
  6029  							Storage: true,
  6030  						},
  6031  					},
  6032  					Scope: apiextensions.NamespaceScoped,
  6033  					Names: apiextensions.CustomResourceDefinitionNames{
  6034  						Plural:   "plural",
  6035  						Singular: "singular",
  6036  						Kind:     "Plural",
  6037  						ListKind: "PluralList",
  6038  					},
  6039  					PreserveUnknownFields: pointer.BoolPtr(true),
  6040  				},
  6041  				Status: apiextensions.CustomResourceDefinitionStatus{
  6042  					StoredVersions: []string{"version"},
  6043  				},
  6044  			},
  6045  			resource: &apiextensions.CustomResourceDefinition{
  6046  				ObjectMeta: metav1.ObjectMeta{
  6047  					Name:            "plural.group.com",
  6048  					ResourceVersion: "42",
  6049  				},
  6050  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6051  					Group:   "group.com",
  6052  					Version: "version",
  6053  					Validation: &apiextensions.CustomResourceValidation{
  6054  						OpenAPIV3Schema: validUnstructuralValidationSchema,
  6055  					},
  6056  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  6057  						{
  6058  							Name:    "version",
  6059  							Served:  true,
  6060  							Storage: true,
  6061  						},
  6062  					},
  6063  					Scope: apiextensions.NamespaceScoped,
  6064  					Names: apiextensions.CustomResourceDefinitionNames{
  6065  						Plural:   "plural",
  6066  						Singular: "singular",
  6067  						Kind:     "Plural",
  6068  						ListKind: "PluralList",
  6069  					},
  6070  					PreserveUnknownFields: pointer.BoolPtr(false),
  6071  				},
  6072  				Status: apiextensions.CustomResourceDefinitionStatus{
  6073  					StoredVersions: []string{"version"},
  6074  				},
  6075  			},
  6076  			errors: []validationMatch{
  6077  				required("spec", "validation", "openAPIV3Schema", "properties[spec]", "type"),
  6078  				required("spec", "validation", "openAPIV3Schema", "properties[status]", "type"),
  6079  				required("spec", "validation", "openAPIV3Schema", "items", "type"),
  6080  			},
  6081  		},
  6082  		{
  6083  			name: "switch off preserveUnknownFields without structural schema before, but with after",
  6084  			old: &apiextensions.CustomResourceDefinition{
  6085  				ObjectMeta: metav1.ObjectMeta{
  6086  					Name:            "plural.group.com",
  6087  					ResourceVersion: "42",
  6088  				},
  6089  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6090  					Group:   "group.com",
  6091  					Version: "version",
  6092  					Validation: &apiextensions.CustomResourceValidation{
  6093  						OpenAPIV3Schema: validUnstructuralValidationSchema,
  6094  					},
  6095  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  6096  						{
  6097  							Name:    "version",
  6098  							Served:  true,
  6099  							Storage: true,
  6100  						},
  6101  					},
  6102  					Scope: apiextensions.NamespaceScoped,
  6103  					Names: apiextensions.CustomResourceDefinitionNames{
  6104  						Plural:   "plural",
  6105  						Singular: "singular",
  6106  						Kind:     "Plural",
  6107  						ListKind: "PluralList",
  6108  					},
  6109  					PreserveUnknownFields: pointer.BoolPtr(true),
  6110  				},
  6111  				Status: apiextensions.CustomResourceDefinitionStatus{
  6112  					StoredVersions: []string{"version"},
  6113  				},
  6114  			},
  6115  			resource: &apiextensions.CustomResourceDefinition{
  6116  				ObjectMeta: metav1.ObjectMeta{
  6117  					Name:            "plural.group.com",
  6118  					ResourceVersion: "42",
  6119  				},
  6120  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6121  					Group:   "group.com",
  6122  					Version: "version",
  6123  					Validation: &apiextensions.CustomResourceValidation{
  6124  						OpenAPIV3Schema: validValidationSchema,
  6125  					},
  6126  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  6127  						{
  6128  							Name:    "version",
  6129  							Served:  true,
  6130  							Storage: true,
  6131  						},
  6132  					},
  6133  					Scope: apiextensions.NamespaceScoped,
  6134  					Names: apiextensions.CustomResourceDefinitionNames{
  6135  						Plural:   "plural",
  6136  						Singular: "singular",
  6137  						Kind:     "Plural",
  6138  						ListKind: "PluralList",
  6139  					},
  6140  					PreserveUnknownFields: pointer.BoolPtr(false),
  6141  				},
  6142  				Status: apiextensions.CustomResourceDefinitionStatus{
  6143  					StoredVersions: []string{"version"},
  6144  				},
  6145  			},
  6146  			errors: []validationMatch{},
  6147  		},
  6148  		{
  6149  			name: "switch to preserveUnknownFields: true is forbidden",
  6150  			old: &apiextensions.CustomResourceDefinition{
  6151  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6152  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6153  					Group:                 "group.com",
  6154  					Version:               "version",
  6155  					Versions:              []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6156  					Validation:            &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}},
  6157  					Scope:                 apiextensions.NamespaceScoped,
  6158  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6159  					PreserveUnknownFields: pointer.BoolPtr(false),
  6160  				},
  6161  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6162  			},
  6163  			resource: &apiextensions.CustomResourceDefinition{
  6164  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6165  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6166  					Group:                 "group.com",
  6167  					Version:               "version",
  6168  					Versions:              []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6169  					Validation:            &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}},
  6170  					Scope:                 apiextensions.NamespaceScoped,
  6171  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6172  					PreserveUnknownFields: pointer.BoolPtr(true),
  6173  				},
  6174  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6175  			},
  6176  			errors: []validationMatch{invalid("spec.preserveUnknownFields")},
  6177  		},
  6178  		{
  6179  			name: "keep preserveUnknownFields: true is allowed",
  6180  			old: &apiextensions.CustomResourceDefinition{
  6181  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6182  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6183  					Group:                 "group.com",
  6184  					Version:               "version",
  6185  					Versions:              []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6186  					Validation:            &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}},
  6187  					Scope:                 apiextensions.NamespaceScoped,
  6188  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6189  					PreserveUnknownFields: pointer.BoolPtr(true),
  6190  				},
  6191  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6192  			},
  6193  			resource: &apiextensions.CustomResourceDefinition{
  6194  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6195  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6196  					Group:                 "group.com",
  6197  					Version:               "version",
  6198  					Versions:              []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6199  					Validation:            &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}},
  6200  					Scope:                 apiextensions.NamespaceScoped,
  6201  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6202  					PreserveUnknownFields: pointer.BoolPtr(true),
  6203  				},
  6204  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6205  			},
  6206  			errors: []validationMatch{},
  6207  		},
  6208  		{
  6209  			name: "schema not required if old object is missing schema",
  6210  			old: &apiextensions.CustomResourceDefinition{
  6211  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6212  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6213  					Group:                 "group.com",
  6214  					Version:               "version",
  6215  					Versions:              []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6216  					Scope:                 apiextensions.NamespaceScoped,
  6217  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6218  					PreserveUnknownFields: pointer.BoolPtr(true),
  6219  				},
  6220  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6221  			},
  6222  			resource: &apiextensions.CustomResourceDefinition{
  6223  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6224  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6225  					Group:                 "group.com",
  6226  					Version:               "version",
  6227  					Versions:              []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6228  					Scope:                 apiextensions.NamespaceScoped,
  6229  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6230  					PreserveUnknownFields: pointer.BoolPtr(true),
  6231  				},
  6232  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6233  			},
  6234  			errors: []validationMatch{},
  6235  		},
  6236  		{
  6237  			name: "schema not required if old object is missing schema for some versions",
  6238  			old: &apiextensions.CustomResourceDefinition{
  6239  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6240  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6241  					Group:   "group.com",
  6242  					Version: "version",
  6243  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  6244  						{Name: "version", Served: true, Storage: true, Schema: &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}}},
  6245  						{Name: "version2", Served: true, Storage: false},
  6246  					},
  6247  					Scope:                 apiextensions.NamespaceScoped,
  6248  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6249  					PreserveUnknownFields: pointer.BoolPtr(true),
  6250  				},
  6251  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6252  			},
  6253  			resource: &apiextensions.CustomResourceDefinition{
  6254  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6255  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6256  					Group:   "group.com",
  6257  					Version: "version",
  6258  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  6259  						{Name: "version", Served: true, Storage: true},
  6260  						{Name: "version2", Served: true, Storage: false},
  6261  					},
  6262  					Scope:                 apiextensions.NamespaceScoped,
  6263  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6264  					PreserveUnknownFields: pointer.BoolPtr(true),
  6265  				},
  6266  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6267  			},
  6268  			errors: []validationMatch{},
  6269  		},
  6270  		{
  6271  			name: "schema required if old object has top-level schema",
  6272  			old: &apiextensions.CustomResourceDefinition{
  6273  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6274  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6275  					Group:                 "group.com",
  6276  					Version:               "version",
  6277  					Versions:              []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6278  					Validation:            &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}},
  6279  					Scope:                 apiextensions.NamespaceScoped,
  6280  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6281  					PreserveUnknownFields: pointer.BoolPtr(true),
  6282  				},
  6283  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6284  			},
  6285  			resource: &apiextensions.CustomResourceDefinition{
  6286  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6287  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6288  					Group:                 "group.com",
  6289  					Version:               "version",
  6290  					Versions:              []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6291  					Scope:                 apiextensions.NamespaceScoped,
  6292  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6293  					PreserveUnknownFields: pointer.BoolPtr(true),
  6294  				},
  6295  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6296  			},
  6297  			errors: []validationMatch{
  6298  				required("spec.versions[0].schema.openAPIV3Schema"),
  6299  			},
  6300  		},
  6301  		{
  6302  			name: "schema required if all versions of old object have schema",
  6303  			old: &apiextensions.CustomResourceDefinition{
  6304  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6305  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6306  					Group:   "group.com",
  6307  					Version: "version",
  6308  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  6309  						{Name: "version", Served: true, Storage: true, Schema: &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object", Description: "1"}}},
  6310  						{Name: "version2", Served: true, Storage: false, Schema: &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object", Description: "2"}}},
  6311  					},
  6312  					Scope:                 apiextensions.NamespaceScoped,
  6313  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6314  					PreserveUnknownFields: pointer.BoolPtr(true),
  6315  				},
  6316  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6317  			},
  6318  			resource: &apiextensions.CustomResourceDefinition{
  6319  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6320  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6321  					Group:   "group.com",
  6322  					Version: "version",
  6323  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  6324  						{Name: "version", Served: true, Storage: true},
  6325  						{Name: "version2", Served: true, Storage: false},
  6326  					},
  6327  					Scope:                 apiextensions.NamespaceScoped,
  6328  					Names:                 apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6329  					PreserveUnknownFields: pointer.BoolPtr(true),
  6330  				},
  6331  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6332  			},
  6333  			errors: []validationMatch{
  6334  				required("spec.versions[0].schema.openAPIV3Schema"),
  6335  				required("spec.versions[1].schema.openAPIV3Schema"),
  6336  			},
  6337  		},
  6338  		{
  6339  			name: "setting defaults with enabled feature gate",
  6340  			old: &apiextensions.CustomResourceDefinition{
  6341  				ObjectMeta: metav1.ObjectMeta{
  6342  					Name:            "plural.group.com",
  6343  					ResourceVersion: "42",
  6344  				},
  6345  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6346  					Group:   "group.com",
  6347  					Version: "version",
  6348  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  6349  						{
  6350  							Name:    "version",
  6351  							Served:  true,
  6352  							Storage: true,
  6353  						},
  6354  					},
  6355  					Scope: apiextensions.NamespaceScoped,
  6356  					Names: apiextensions.CustomResourceDefinitionNames{
  6357  						Plural:   "plural",
  6358  						Singular: "singular",
  6359  						Kind:     "Plural",
  6360  						ListKind: "PluralList",
  6361  					},
  6362  					Validation: &apiextensions.CustomResourceValidation{
  6363  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6364  							Type: "object",
  6365  							Properties: map[string]apiextensions.JSONSchemaProps{
  6366  								"a": {
  6367  									Type: "number",
  6368  								},
  6369  							},
  6370  						},
  6371  					},
  6372  					PreserveUnknownFields: pointer.BoolPtr(false),
  6373  				},
  6374  				Status: apiextensions.CustomResourceDefinitionStatus{
  6375  					StoredVersions: []string{"version"},
  6376  				},
  6377  			},
  6378  			resource: &apiextensions.CustomResourceDefinition{
  6379  				ObjectMeta: metav1.ObjectMeta{
  6380  					Name:            "plural.group.com",
  6381  					ResourceVersion: "42",
  6382  				},
  6383  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6384  					Group:   "group.com",
  6385  					Version: "version",
  6386  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  6387  						{
  6388  							Name:    "version",
  6389  							Served:  true,
  6390  							Storage: true,
  6391  						},
  6392  					},
  6393  					Scope: apiextensions.NamespaceScoped,
  6394  					Names: apiextensions.CustomResourceDefinitionNames{
  6395  						Plural:   "plural",
  6396  						Singular: "singular",
  6397  						Kind:     "Plural",
  6398  						ListKind: "PluralList",
  6399  					},
  6400  					Validation: &apiextensions.CustomResourceValidation{
  6401  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6402  							Type: "object",
  6403  							Properties: map[string]apiextensions.JSONSchemaProps{
  6404  								"a": {
  6405  									Type:    "number",
  6406  									Default: jsonPtr(42.0),
  6407  								},
  6408  							},
  6409  						},
  6410  					},
  6411  					PreserveUnknownFields: pointer.BoolPtr(false),
  6412  				},
  6413  				Status: apiextensions.CustomResourceDefinitionStatus{
  6414  					StoredVersions: []string{"version"},
  6415  				},
  6416  			},
  6417  			errors: []validationMatch{},
  6418  		},
  6419  		{
  6420  			name: "add default with enabled feature gate, structural schema, without pruning",
  6421  			old: &apiextensions.CustomResourceDefinition{
  6422  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
  6423  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6424  					Group:    "group.com",
  6425  					Version:  "version",
  6426  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6427  					Scope:    apiextensions.NamespaceScoped,
  6428  					Names: apiextensions.CustomResourceDefinitionNames{
  6429  						Plural:   "plural",
  6430  						Singular: "singular",
  6431  						Kind:     "Plural",
  6432  						ListKind: "PluralList",
  6433  					},
  6434  					Validation: &apiextensions.CustomResourceValidation{
  6435  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6436  							Type: "object",
  6437  							Properties: map[string]apiextensions.JSONSchemaProps{
  6438  								"a": {
  6439  									Type: "number",
  6440  									//Default: jsonPtr(42.0),
  6441  								},
  6442  							},
  6443  						},
  6444  					},
  6445  					PreserveUnknownFields: pointer.BoolPtr(true),
  6446  				},
  6447  				Status: apiextensions.CustomResourceDefinitionStatus{
  6448  					StoredVersions: []string{"version"},
  6449  				},
  6450  			},
  6451  			resource: &apiextensions.CustomResourceDefinition{
  6452  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
  6453  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6454  					Group:    "group.com",
  6455  					Version:  "version",
  6456  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6457  					Scope:    apiextensions.NamespaceScoped,
  6458  					Names: apiextensions.CustomResourceDefinitionNames{
  6459  						Plural:   "plural",
  6460  						Singular: "singular",
  6461  						Kind:     "Plural",
  6462  						ListKind: "PluralList",
  6463  					},
  6464  					Validation: &apiextensions.CustomResourceValidation{
  6465  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6466  							Type: "object",
  6467  							Properties: map[string]apiextensions.JSONSchemaProps{
  6468  								"a": {
  6469  									Type:    "number",
  6470  									Default: jsonPtr(42.0),
  6471  								},
  6472  							},
  6473  						},
  6474  					},
  6475  					PreserveUnknownFields: pointer.BoolPtr(true),
  6476  				},
  6477  				Status: apiextensions.CustomResourceDefinitionStatus{
  6478  					StoredVersions: []string{"version"},
  6479  				},
  6480  			},
  6481  			errors: []validationMatch{
  6482  				invalid("spec", "preserveUnknownFields"),
  6483  			},
  6484  		},
  6485  		{
  6486  			name: "allow non-required key with no default in list of type map if pre-existing",
  6487  			old: &apiextensions.CustomResourceDefinition{
  6488  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6489  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6490  					Group:    "group.com",
  6491  					Scope:    apiextensions.ResourceScope("Cluster"),
  6492  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6493  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6494  					Validation: &apiextensions.CustomResourceValidation{
  6495  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6496  							Type: "object",
  6497  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
  6498  								Type:         "array",
  6499  								XListType:    strPtr("map"),
  6500  								XListMapKeys: []string{"key"},
  6501  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6502  									Schema: &apiextensions.JSONSchemaProps{
  6503  										Type: "object",
  6504  										Properties: map[string]apiextensions.JSONSchemaProps{
  6505  											"key": {
  6506  												Type: "string",
  6507  											},
  6508  										},
  6509  									},
  6510  								},
  6511  							}},
  6512  						},
  6513  					},
  6514  				},
  6515  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6516  			},
  6517  			resource: &apiextensions.CustomResourceDefinition{
  6518  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6519  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6520  					Group:    "group.com",
  6521  					Scope:    apiextensions.ResourceScope("Cluster"),
  6522  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6523  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6524  					Validation: &apiextensions.CustomResourceValidation{
  6525  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6526  							Type: "object",
  6527  							Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
  6528  								Type:         "array",
  6529  								XListType:    strPtr("map"),
  6530  								XListMapKeys: []string{"key"},
  6531  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6532  									Schema: &apiextensions.JSONSchemaProps{
  6533  										Type: "object",
  6534  										Properties: map[string]apiextensions.JSONSchemaProps{
  6535  											"key": {
  6536  												Type: "string",
  6537  											},
  6538  										},
  6539  									},
  6540  								},
  6541  							}},
  6542  						},
  6543  					},
  6544  				},
  6545  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6546  			},
  6547  			errors: nil,
  6548  		},
  6549  		{
  6550  			name: "reject non-required key with no default in list of type map if not pre-existing",
  6551  			old: &apiextensions.CustomResourceDefinition{
  6552  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6553  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6554  					Group:    "group.com",
  6555  					Scope:    apiextensions.ResourceScope("Cluster"),
  6556  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6557  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6558  					Validation: &apiextensions.CustomResourceValidation{
  6559  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6560  							Type: "object",
  6561  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
  6562  								Type:         "array",
  6563  								XListType:    strPtr("map"),
  6564  								XListMapKeys: []string{"key"},
  6565  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6566  									Schema: &apiextensions.JSONSchemaProps{
  6567  										Type: "object",
  6568  										Properties: map[string]apiextensions.JSONSchemaProps{
  6569  											"key": {
  6570  												Type:    "string",
  6571  												Default: jsonPtr("stuff"),
  6572  											},
  6573  										},
  6574  									},
  6575  								},
  6576  							}},
  6577  						},
  6578  					},
  6579  				},
  6580  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6581  			},
  6582  			resource: &apiextensions.CustomResourceDefinition{
  6583  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6584  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6585  					Group:    "group.com",
  6586  					Scope:    apiextensions.ResourceScope("Cluster"),
  6587  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6588  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6589  					Validation: &apiextensions.CustomResourceValidation{
  6590  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6591  							Type: "object",
  6592  							Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
  6593  								Type:         "array",
  6594  								XListType:    strPtr("map"),
  6595  								XListMapKeys: []string{"key"},
  6596  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6597  									Schema: &apiextensions.JSONSchemaProps{
  6598  										Type: "object",
  6599  										Properties: map[string]apiextensions.JSONSchemaProps{
  6600  											"key": {
  6601  												Type: "string",
  6602  											},
  6603  										},
  6604  									},
  6605  								},
  6606  							}},
  6607  						},
  6608  					},
  6609  				},
  6610  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6611  			},
  6612  			errors: []validationMatch{
  6613  				required("spec", "validation", "openAPIV3Schema", "properties[bar]", "items", "properties[key]", "default"),
  6614  			},
  6615  		},
  6616  		{
  6617  			name: "allow nullable key in list of type map if pre-existing",
  6618  			old: &apiextensions.CustomResourceDefinition{
  6619  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6620  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6621  					Group:    "group.com",
  6622  					Scope:    apiextensions.ResourceScope("Cluster"),
  6623  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6624  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6625  					Validation: &apiextensions.CustomResourceValidation{
  6626  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6627  							Type: "object",
  6628  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
  6629  								Type:         "array",
  6630  								XListType:    strPtr("map"),
  6631  								XListMapKeys: []string{"key"},
  6632  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6633  									Schema: &apiextensions.JSONSchemaProps{
  6634  										Type:     "object",
  6635  										Required: []string{"key"},
  6636  										Properties: map[string]apiextensions.JSONSchemaProps{
  6637  											"key": {
  6638  												Type:     "string",
  6639  												Nullable: true,
  6640  											},
  6641  										},
  6642  									},
  6643  								},
  6644  							}},
  6645  						},
  6646  					},
  6647  				},
  6648  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6649  			},
  6650  			resource: &apiextensions.CustomResourceDefinition{
  6651  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6652  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6653  					Group:    "group.com",
  6654  					Scope:    apiextensions.ResourceScope("Cluster"),
  6655  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6656  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6657  					Validation: &apiextensions.CustomResourceValidation{
  6658  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6659  							Type: "object",
  6660  							Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
  6661  								Type:         "array",
  6662  								XListType:    strPtr("map"),
  6663  								XListMapKeys: []string{"key"},
  6664  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6665  									Schema: &apiextensions.JSONSchemaProps{
  6666  										Type:     "object",
  6667  										Required: []string{"key"},
  6668  										Properties: map[string]apiextensions.JSONSchemaProps{
  6669  											"key": {
  6670  												Type:     "string",
  6671  												Nullable: true,
  6672  											},
  6673  										},
  6674  									},
  6675  								},
  6676  							}},
  6677  						},
  6678  					},
  6679  				},
  6680  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6681  			},
  6682  			errors: nil,
  6683  		},
  6684  		{
  6685  			name: "reject nullable key in list of type map if not pre-existing",
  6686  			old: &apiextensions.CustomResourceDefinition{
  6687  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6688  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6689  					Group:    "group.com",
  6690  					Scope:    apiextensions.ResourceScope("Cluster"),
  6691  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6692  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6693  					Validation: &apiextensions.CustomResourceValidation{
  6694  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6695  							Type: "object",
  6696  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
  6697  								Type:         "array",
  6698  								XListType:    strPtr("map"),
  6699  								XListMapKeys: []string{"key"},
  6700  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6701  									Schema: &apiextensions.JSONSchemaProps{
  6702  										Type:     "object",
  6703  										Required: []string{"key"},
  6704  										Properties: map[string]apiextensions.JSONSchemaProps{
  6705  											"key": {
  6706  												Type: "string",
  6707  											},
  6708  										},
  6709  									},
  6710  								},
  6711  							}},
  6712  						},
  6713  					},
  6714  				},
  6715  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6716  			},
  6717  			resource: &apiextensions.CustomResourceDefinition{
  6718  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6719  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6720  					Group:    "group.com",
  6721  					Scope:    apiextensions.ResourceScope("Cluster"),
  6722  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6723  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6724  					Validation: &apiextensions.CustomResourceValidation{
  6725  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6726  							Type: "object",
  6727  							Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
  6728  								Type:         "array",
  6729  								XListType:    strPtr("map"),
  6730  								XListMapKeys: []string{"key"},
  6731  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6732  									Schema: &apiextensions.JSONSchemaProps{
  6733  										Type:     "object",
  6734  										Required: []string{"key"},
  6735  										Properties: map[string]apiextensions.JSONSchemaProps{
  6736  											"key": {
  6737  												Type:     "string",
  6738  												Nullable: true,
  6739  											},
  6740  										},
  6741  									},
  6742  								},
  6743  							}},
  6744  						},
  6745  					},
  6746  				},
  6747  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6748  			},
  6749  			errors: []validationMatch{
  6750  				forbidden("spec", "validation", "openAPIV3Schema", "properties[bar]", "items", "properties[key]", "nullable"),
  6751  			},
  6752  		},
  6753  		{
  6754  			name: "allow nullable item in list of type map if pre-existing",
  6755  			old: &apiextensions.CustomResourceDefinition{
  6756  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6757  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6758  					Group:    "group.com",
  6759  					Scope:    apiextensions.ResourceScope("Cluster"),
  6760  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6761  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6762  					Validation: &apiextensions.CustomResourceValidation{
  6763  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6764  							Type: "object",
  6765  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
  6766  								Type:         "array",
  6767  								XListType:    strPtr("map"),
  6768  								XListMapKeys: []string{"key"},
  6769  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6770  									Schema: &apiextensions.JSONSchemaProps{
  6771  										Type:     "object",
  6772  										Nullable: true,
  6773  										Required: []string{"key"},
  6774  										Properties: map[string]apiextensions.JSONSchemaProps{
  6775  											"key": {
  6776  												Type: "string",
  6777  											},
  6778  										},
  6779  									},
  6780  								},
  6781  							}},
  6782  						},
  6783  					},
  6784  				},
  6785  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6786  			},
  6787  			resource: &apiextensions.CustomResourceDefinition{
  6788  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6789  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6790  					Group:    "group.com",
  6791  					Scope:    apiextensions.ResourceScope("Cluster"),
  6792  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6793  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6794  					Validation: &apiextensions.CustomResourceValidation{
  6795  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6796  							Type: "object",
  6797  							Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
  6798  								Type:         "array",
  6799  								XListType:    strPtr("map"),
  6800  								XListMapKeys: []string{"key"},
  6801  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6802  									Schema: &apiextensions.JSONSchemaProps{
  6803  										Type:     "object",
  6804  										Nullable: true,
  6805  										Required: []string{"key"},
  6806  										Properties: map[string]apiextensions.JSONSchemaProps{
  6807  											"key": {
  6808  												Type: "string",
  6809  											},
  6810  										},
  6811  									},
  6812  								},
  6813  							}},
  6814  						},
  6815  					},
  6816  				},
  6817  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6818  			},
  6819  			errors: nil,
  6820  		},
  6821  		{
  6822  			name: "reject nullable item in list of type map if not pre-existing",
  6823  			old: &apiextensions.CustomResourceDefinition{
  6824  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6825  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6826  					Group:    "group.com",
  6827  					Scope:    apiextensions.ResourceScope("Cluster"),
  6828  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6829  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6830  					Validation: &apiextensions.CustomResourceValidation{
  6831  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6832  							Type: "object",
  6833  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
  6834  								Type:         "array",
  6835  								XListType:    strPtr("map"),
  6836  								XListMapKeys: []string{"key"},
  6837  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6838  									Schema: &apiextensions.JSONSchemaProps{
  6839  										Type:     "object",
  6840  										Required: []string{"key"},
  6841  										Properties: map[string]apiextensions.JSONSchemaProps{
  6842  											"key": {
  6843  												Type: "string",
  6844  											},
  6845  										},
  6846  									},
  6847  								},
  6848  							}},
  6849  						},
  6850  					},
  6851  				},
  6852  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6853  			},
  6854  			resource: &apiextensions.CustomResourceDefinition{
  6855  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6856  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6857  					Group:    "group.com",
  6858  					Scope:    apiextensions.ResourceScope("Cluster"),
  6859  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6860  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6861  					Validation: &apiextensions.CustomResourceValidation{
  6862  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6863  							Type: "object",
  6864  							Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
  6865  								Type:         "array",
  6866  								XListType:    strPtr("map"),
  6867  								XListMapKeys: []string{"key"},
  6868  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6869  									Schema: &apiextensions.JSONSchemaProps{
  6870  										Type:     "object",
  6871  										Nullable: true,
  6872  										Required: []string{"key"},
  6873  										Properties: map[string]apiextensions.JSONSchemaProps{
  6874  											"key": {
  6875  												Type: "string",
  6876  											},
  6877  										},
  6878  									},
  6879  								},
  6880  							}},
  6881  						},
  6882  					},
  6883  				},
  6884  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6885  			},
  6886  			errors: []validationMatch{
  6887  				forbidden("spec", "validation", "openAPIV3Schema", "properties[bar]", "items", "nullable"),
  6888  			},
  6889  		},
  6890  		{
  6891  			name: "allow nullable items in list of type set if pre-existing",
  6892  			old: &apiextensions.CustomResourceDefinition{
  6893  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6894  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6895  					Group:    "group.com",
  6896  					Scope:    apiextensions.ResourceScope("Cluster"),
  6897  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6898  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6899  					Validation: &apiextensions.CustomResourceValidation{
  6900  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6901  							Type: "object",
  6902  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
  6903  								Type:      "array",
  6904  								XListType: strPtr("set"),
  6905  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6906  									Schema: &apiextensions.JSONSchemaProps{
  6907  										Type:     "string",
  6908  										Nullable: true,
  6909  									},
  6910  								},
  6911  							}},
  6912  						},
  6913  					},
  6914  				},
  6915  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6916  			},
  6917  			resource: &apiextensions.CustomResourceDefinition{
  6918  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6919  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6920  					Group:    "group.com",
  6921  					Scope:    apiextensions.ResourceScope("Cluster"),
  6922  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6923  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6924  					Validation: &apiextensions.CustomResourceValidation{
  6925  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6926  							Type: "object",
  6927  							Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
  6928  								Type:      "array",
  6929  								XListType: strPtr("set"),
  6930  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6931  									Schema: &apiextensions.JSONSchemaProps{
  6932  										Type:     "string",
  6933  										Nullable: true,
  6934  									},
  6935  								},
  6936  							}},
  6937  						},
  6938  					},
  6939  				},
  6940  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6941  			},
  6942  			errors: nil,
  6943  		},
  6944  		{
  6945  			name: "reject nullable items in list of type set if not pre-exisiting",
  6946  			old: &apiextensions.CustomResourceDefinition{
  6947  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6948  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6949  					Group:    "group.com",
  6950  					Scope:    apiextensions.ResourceScope("Cluster"),
  6951  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6952  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6953  					Validation: &apiextensions.CustomResourceValidation{
  6954  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6955  							Type: "object",
  6956  							Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
  6957  								Type:      "array",
  6958  								XListType: strPtr("set"),
  6959  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6960  									Schema: &apiextensions.JSONSchemaProps{
  6961  										Type: "string",
  6962  									},
  6963  								},
  6964  							}},
  6965  						},
  6966  					},
  6967  				},
  6968  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6969  			},
  6970  			resource: &apiextensions.CustomResourceDefinition{
  6971  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  6972  				Spec: apiextensions.CustomResourceDefinitionSpec{
  6973  					Group:    "group.com",
  6974  					Scope:    apiextensions.ResourceScope("Cluster"),
  6975  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  6976  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  6977  					Validation: &apiextensions.CustomResourceValidation{
  6978  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  6979  							Type: "object",
  6980  							Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
  6981  								Type:      "array",
  6982  								XListType: strPtr("set"),
  6983  								Items: &apiextensions.JSONSchemaPropsOrArray{
  6984  									Schema: &apiextensions.JSONSchemaProps{
  6985  										Type:     "string",
  6986  										Nullable: true,
  6987  									},
  6988  								},
  6989  							}},
  6990  						},
  6991  					},
  6992  				},
  6993  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  6994  			},
  6995  			errors: []validationMatch{
  6996  				forbidden("spec", "validation", "openAPIV3Schema", "properties[bar]", "items", "nullable"),
  6997  			},
  6998  		},
  6999  		{
  7000  			name: "suppress per-expression cost limit in pre-existing versions",
  7001  			old: &apiextensions.CustomResourceDefinition{
  7002  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  7003  				Spec: apiextensions.CustomResourceDefinitionSpec{
  7004  					Group: "group.com",
  7005  					Scope: apiextensions.ResourceScope("Cluster"),
  7006  					Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  7007  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  7008  						{
  7009  							Name:    "v1",
  7010  							Served:  true,
  7011  							Storage: true,
  7012  							Schema: &apiextensions.CustomResourceValidation{
  7013  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7014  									Type: "object",
  7015  									Properties: map[string]apiextensions.JSONSchemaProps{
  7016  										"f": {
  7017  											Type: "array",
  7018  											Items: &apiextensions.JSONSchemaPropsOrArray{
  7019  												Schema: &apiextensions.JSONSchemaProps{
  7020  													Type: "string",
  7021  												},
  7022  											},
  7023  											XValidations: apiextensions.ValidationRules{
  7024  												{
  7025  													Rule:              "true",
  7026  													MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
  7027  												},
  7028  											},
  7029  										},
  7030  									},
  7031  								},
  7032  							},
  7033  						},
  7034  						{
  7035  							Name: "v2",
  7036  							Schema: &apiextensions.CustomResourceValidation{
  7037  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7038  									Type: "object",
  7039  									Properties: map[string]apiextensions.JSONSchemaProps{
  7040  										"f": {
  7041  											Type: "array",
  7042  											Items: &apiextensions.JSONSchemaPropsOrArray{
  7043  												Schema: &apiextensions.JSONSchemaProps{
  7044  													Type: "string",
  7045  												},
  7046  											},
  7047  											XValidations: apiextensions.ValidationRules{
  7048  												{
  7049  													Rule:              "true",
  7050  													MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
  7051  												},
  7052  											},
  7053  										},
  7054  									},
  7055  								},
  7056  							},
  7057  						},
  7058  					},
  7059  				},
  7060  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"v1"}},
  7061  			},
  7062  			resource: &apiextensions.CustomResourceDefinition{
  7063  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  7064  				Spec: apiextensions.CustomResourceDefinitionSpec{
  7065  					Group: "group.com",
  7066  					Scope: apiextensions.ResourceScope("Cluster"),
  7067  					Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  7068  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  7069  						{
  7070  							Name:    "v1", // unchanged
  7071  							Served:  true,
  7072  							Storage: true,
  7073  							Schema: &apiextensions.CustomResourceValidation{
  7074  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7075  									Type: "object",
  7076  									Properties: map[string]apiextensions.JSONSchemaProps{
  7077  										"f": {
  7078  											Type: "array",
  7079  											Items: &apiextensions.JSONSchemaPropsOrArray{
  7080  												Schema: &apiextensions.JSONSchemaProps{
  7081  													Type: "string",
  7082  												},
  7083  											},
  7084  											XValidations: apiextensions.ValidationRules{
  7085  												{
  7086  													Rule:              "true",
  7087  													MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
  7088  												},
  7089  											},
  7090  										},
  7091  									},
  7092  								},
  7093  							},
  7094  						},
  7095  						{
  7096  							Name: "v2", // touched
  7097  							Schema: &apiextensions.CustomResourceValidation{
  7098  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7099  									Type: "object",
  7100  									Properties: map[string]apiextensions.JSONSchemaProps{
  7101  										"f": {
  7102  											Type: "array",
  7103  											Items: &apiextensions.JSONSchemaPropsOrArray{
  7104  												Schema: &apiextensions.JSONSchemaProps{
  7105  													Type: "string",
  7106  												},
  7107  											},
  7108  											XValidations: apiextensions.ValidationRules{
  7109  												{
  7110  													Rule:              "true",
  7111  													MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7] + self[8]`,
  7112  												},
  7113  											},
  7114  										},
  7115  									},
  7116  								},
  7117  							},
  7118  						},
  7119  						{
  7120  							Name: "v3", // new
  7121  							Schema: &apiextensions.CustomResourceValidation{
  7122  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7123  									Type: "object",
  7124  									Properties: map[string]apiextensions.JSONSchemaProps{
  7125  										"f": {
  7126  											Type: "array",
  7127  											Items: &apiextensions.JSONSchemaPropsOrArray{
  7128  												Schema: &apiextensions.JSONSchemaProps{
  7129  													Type: "string",
  7130  												},
  7131  											},
  7132  											XValidations: apiextensions.ValidationRules{
  7133  												{
  7134  													Rule:              "true",
  7135  													MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
  7136  												},
  7137  											},
  7138  										},
  7139  									},
  7140  								},
  7141  							},
  7142  						},
  7143  					},
  7144  				},
  7145  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"v1"}},
  7146  			},
  7147  			errors: []validationMatch{
  7148  				// versions[0] is exempted because it existed in oldObject
  7149  				forbidden("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[f]", "x-kubernetes-validations[0]", "messageExpression"),
  7150  				forbidden("spec", "versions[2]", "schema", "openAPIV3Schema", "properties[f]", "x-kubernetes-validations[0]", "messageExpression"),
  7151  			},
  7152  		},
  7153  		{
  7154  			name: "suppress per-expression cost limit in new object during top-level schema to Versions extraction",
  7155  			old: &apiextensions.CustomResourceDefinition{
  7156  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  7157  				Spec: apiextensions.CustomResourceDefinitionSpec{
  7158  					Group:   "group.com",
  7159  					Scope:   apiextensions.ResourceScope("Cluster"),
  7160  					Names:   apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  7161  					Version: "v1",
  7162  					Validation: &apiextensions.CustomResourceValidation{
  7163  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7164  							Type: "object",
  7165  							Properties: map[string]apiextensions.JSONSchemaProps{
  7166  								"f": {
  7167  									Type: "array",
  7168  									Items: &apiextensions.JSONSchemaPropsOrArray{
  7169  										Schema: &apiextensions.JSONSchemaProps{
  7170  											Type: "string",
  7171  										},
  7172  									},
  7173  									XValidations: apiextensions.ValidationRules{
  7174  										{
  7175  											Rule:              "true",
  7176  											MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
  7177  										},
  7178  									},
  7179  								},
  7180  							},
  7181  						},
  7182  					},
  7183  				},
  7184  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"v1"}},
  7185  			},
  7186  			resource: &apiextensions.CustomResourceDefinition{
  7187  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  7188  				Spec: apiextensions.CustomResourceDefinitionSpec{
  7189  					Group: "group.com",
  7190  					Scope: apiextensions.ResourceScope("Cluster"),
  7191  					Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  7192  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  7193  						{
  7194  							Name:    "v1", // unchanged, was top-level
  7195  							Served:  true,
  7196  							Storage: true,
  7197  							Schema: &apiextensions.CustomResourceValidation{
  7198  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7199  									Type: "object",
  7200  									Properties: map[string]apiextensions.JSONSchemaProps{
  7201  										"f": {
  7202  											Type: "array",
  7203  											Items: &apiextensions.JSONSchemaPropsOrArray{
  7204  												Schema: &apiextensions.JSONSchemaProps{
  7205  													Type: "string",
  7206  												},
  7207  											},
  7208  											XValidations: apiextensions.ValidationRules{
  7209  												{
  7210  													Rule:              "true",
  7211  													MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
  7212  												},
  7213  											},
  7214  										},
  7215  									},
  7216  								},
  7217  							},
  7218  						},
  7219  						{
  7220  							Name: "v2", // new
  7221  							Schema: &apiextensions.CustomResourceValidation{
  7222  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7223  									Type: "object",
  7224  									Properties: map[string]apiextensions.JSONSchemaProps{
  7225  										"f": {
  7226  											Type: "array",
  7227  											Items: &apiextensions.JSONSchemaPropsOrArray{
  7228  												Schema: &apiextensions.JSONSchemaProps{
  7229  													Type: "string",
  7230  												},
  7231  											},
  7232  											XValidations: apiextensions.ValidationRules{
  7233  												{
  7234  													Rule:              "true",
  7235  													MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7] + self[8]`,
  7236  												},
  7237  											},
  7238  										},
  7239  									},
  7240  								},
  7241  							},
  7242  						},
  7243  					},
  7244  				},
  7245  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"v1"}},
  7246  			},
  7247  			errors: []validationMatch{
  7248  				// versions[0] is exempted because it existed in oldObject as top-level schema.
  7249  				forbidden("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[f]", "x-kubernetes-validations[0]", "messageExpression"),
  7250  			},
  7251  		},
  7252  	}
  7253  
  7254  	for _, tc := range tests {
  7255  		t.Run(tc.name, func(t *testing.T) {
  7256  			ctx := context.TODO()
  7257  			errs := ValidateCustomResourceDefinitionUpdate(ctx, tc.resource, tc.old)
  7258  			seenErrs := make([]bool, len(errs))
  7259  
  7260  			for _, expectedError := range tc.errors {
  7261  				found := false
  7262  				for i, err := range errs {
  7263  					if expectedError.matches(err) && !seenErrs[i] {
  7264  						found = true
  7265  						seenErrs[i] = true
  7266  						break
  7267  					}
  7268  				}
  7269  
  7270  				if !found {
  7271  					t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
  7272  				}
  7273  			}
  7274  
  7275  			for i, seen := range seenErrs {
  7276  				if !seen {
  7277  					t.Errorf("unexpected error: %v", errs[i])
  7278  				}
  7279  			}
  7280  		})
  7281  	}
  7282  }
  7283  
  7284  func TestValidateCustomResourceDefinitionValidationRuleCompatibility(t *testing.T) {
  7285  	allValidationsErrors := []validationMatch{
  7286  		invalid("spec", "validation", "openAPIV3Schema", "properties[x]", "x-kubernetes-validations[0]", "rule"),
  7287  		invalid("spec", "validation", "openAPIV3Schema", "properties[obj]", "x-kubernetes-validations[0]", "rule"),
  7288  		invalid("spec", "validation", "openAPIV3Schema", "properties[obj]", "properties[a]", "x-kubernetes-validations[0]", "rule"),
  7289  		invalid("spec", "validation", "openAPIV3Schema", "properties[array]", "x-kubernetes-validations[0]", "rule"),
  7290  		invalid("spec", "validation", "openAPIV3Schema", "properties[array]", "items", "x-kubernetes-validations[0]", "rule"),
  7291  		invalid("spec", "validation", "openAPIV3Schema", "properties[map]", "x-kubernetes-validations[0]", "rule"),
  7292  		invalid("spec", "validation", "openAPIV3Schema", "properties[map]", "additionalProperties", "x-kubernetes-validations[0]", "rule"),
  7293  	}
  7294  
  7295  	tests := []struct {
  7296  		name        string
  7297  		storedRule  string
  7298  		updatedRule string
  7299  		errors      []validationMatch
  7300  	}{
  7301  		{
  7302  			name:        "functions declared for storage mode allowed if expression is unchanged from what is stored",
  7303  			storedRule:  "test() == true",
  7304  			updatedRule: "test() == true",
  7305  		},
  7306  		{
  7307  			name:        "functions declared for storage mode not allowed if expression is changed",
  7308  			storedRule:  "test() == false",
  7309  			updatedRule: "test() == true",
  7310  			errors:      allValidationsErrors,
  7311  		},
  7312  	}
  7313  
  7314  	// Include the test library, which includes the test() function in the storage environment during test
  7315  	base := environment.MustBaseEnvSet(version.MajorMinor(1, 998))
  7316  	envSet, err := base.Extend(environment.VersionedOptions{
  7317  		IntroducedVersion: version.MajorMinor(1, 999),
  7318  		EnvOptions:        []cel.EnvOption{library.Test()},
  7319  	})
  7320  	if err != nil {
  7321  		t.Fatal(err)
  7322  	}
  7323  
  7324  	for _, tc := range tests {
  7325  		fn := func(rule string) *apiextensions.CustomResourceDefinition {
  7326  			validationRules := []apiextensions.ValidationRule{
  7327  				{
  7328  					Rule: rule,
  7329  				},
  7330  			}
  7331  			return &apiextensions.CustomResourceDefinition{
  7332  				ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
  7333  				Spec: apiextensions.CustomResourceDefinitionSpec{
  7334  					Group:    "group.com",
  7335  					Scope:    apiextensions.ResourceScope("Cluster"),
  7336  					Names:    apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
  7337  					Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
  7338  					Validation: &apiextensions.CustomResourceValidation{
  7339  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7340  							Type: "object",
  7341  							Properties: map[string]apiextensions.JSONSchemaProps{
  7342  								"x": {
  7343  									Type:         "string",
  7344  									XValidations: validationRules,
  7345  								},
  7346  								"obj": {
  7347  									Type: "object",
  7348  									Properties: map[string]apiextensions.JSONSchemaProps{
  7349  										"a": {
  7350  											Type:         "string",
  7351  											XValidations: validationRules,
  7352  										},
  7353  									},
  7354  									XValidations: validationRules,
  7355  								},
  7356  								"array": {
  7357  									Type:     "array",
  7358  									MaxItems: pointer.Int64(1),
  7359  									Items: &apiextensions.JSONSchemaPropsOrArray{
  7360  										Schema: &apiextensions.JSONSchemaProps{
  7361  											Type:         "string",
  7362  											XValidations: validationRules,
  7363  										},
  7364  									},
  7365  									XValidations: validationRules,
  7366  								},
  7367  								"map": {
  7368  									Type:          "object",
  7369  									MaxProperties: pointer.Int64(1),
  7370  									AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  7371  										Schema: &apiextensions.JSONSchemaProps{
  7372  											Type:         "string",
  7373  											XValidations: validationRules,
  7374  										},
  7375  									},
  7376  									XValidations: validationRules,
  7377  								},
  7378  							},
  7379  						},
  7380  					},
  7381  				},
  7382  				Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
  7383  			}
  7384  		}
  7385  		old := fn(tc.storedRule)
  7386  		resource := fn(tc.updatedRule)
  7387  
  7388  		t.Run(tc.name, func(t *testing.T) {
  7389  			ctx := context.TODO()
  7390  			errs := validateCustomResourceDefinitionUpdate(ctx, resource, old, validationOptions{
  7391  				preexistingExpressions: findPreexistingExpressions(&old.Spec),
  7392  				celEnvironmentSet:      envSet,
  7393  			})
  7394  			seenErrs := make([]bool, len(errs))
  7395  
  7396  			for _, expectedError := range tc.errors {
  7397  				found := false
  7398  				for i, err := range errs {
  7399  					if expectedError.matches(err) && !seenErrs[i] {
  7400  						found = true
  7401  						seenErrs[i] = true
  7402  						break
  7403  					}
  7404  				}
  7405  
  7406  				if !found {
  7407  					t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
  7408  				}
  7409  			}
  7410  
  7411  			for i, seen := range seenErrs {
  7412  				if !seen {
  7413  					t.Errorf("unexpected error: %v", errs[i])
  7414  				}
  7415  			}
  7416  		})
  7417  	}
  7418  }
  7419  
  7420  func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
  7421  	tests := []struct {
  7422  		name           string
  7423  		input          apiextensions.CustomResourceValidation
  7424  		statusEnabled  bool
  7425  		opts           validationOptions
  7426  		expectedErrors []validationMatch
  7427  	}{
  7428  		{
  7429  			name:  "empty",
  7430  			input: apiextensions.CustomResourceValidation{},
  7431  		},
  7432  		{
  7433  			name:          "empty with status",
  7434  			input:         apiextensions.CustomResourceValidation{},
  7435  			statusEnabled: true,
  7436  		},
  7437  		{
  7438  			name: "root type without status",
  7439  			input: apiextensions.CustomResourceValidation{
  7440  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7441  					Type: "string",
  7442  				},
  7443  			},
  7444  			statusEnabled: false,
  7445  		},
  7446  		{
  7447  			name: "root type having invalid value, with status",
  7448  			input: apiextensions.CustomResourceValidation{
  7449  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7450  					Type: "string",
  7451  				},
  7452  			},
  7453  			statusEnabled: true,
  7454  			expectedErrors: []validationMatch{
  7455  				invalid("spec.validation.openAPIV3Schema.type"),
  7456  			},
  7457  		},
  7458  		{
  7459  			name: "non-allowed root field with status",
  7460  			input: apiextensions.CustomResourceValidation{
  7461  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7462  					AnyOf: []apiextensions.JSONSchemaProps{
  7463  						{
  7464  							Description: "First schema",
  7465  						},
  7466  						{
  7467  							Description: "Second schema",
  7468  						},
  7469  					},
  7470  				},
  7471  			},
  7472  			statusEnabled: true,
  7473  			expectedErrors: []validationMatch{
  7474  				invalid("spec.validation.openAPIV3Schema"),
  7475  			},
  7476  		},
  7477  		{
  7478  			name: "all allowed fields at the root of the schema with status",
  7479  			input: apiextensions.CustomResourceValidation{
  7480  				OpenAPIV3Schema: validValidationSchema,
  7481  			},
  7482  			statusEnabled: true,
  7483  		},
  7484  		{
  7485  			name: "null type",
  7486  			input: apiextensions.CustomResourceValidation{
  7487  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7488  					Properties: map[string]apiextensions.JSONSchemaProps{
  7489  						"null": {
  7490  							Type: "null",
  7491  						},
  7492  					},
  7493  				},
  7494  			},
  7495  			expectedErrors: []validationMatch{
  7496  				forbidden("spec.validation.openAPIV3Schema.properties[null].type"),
  7497  			},
  7498  		},
  7499  		{
  7500  			name: "nullable at the root",
  7501  			input: apiextensions.CustomResourceValidation{
  7502  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7503  					Type:     "object",
  7504  					Nullable: true,
  7505  				},
  7506  			},
  7507  			expectedErrors: []validationMatch{
  7508  				forbidden("spec.validation.openAPIV3Schema.nullable"),
  7509  			},
  7510  		},
  7511  		{
  7512  			name: "nullable without type",
  7513  			input: apiextensions.CustomResourceValidation{
  7514  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7515  					Properties: map[string]apiextensions.JSONSchemaProps{
  7516  						"nullable": {
  7517  							Nullable: true,
  7518  						},
  7519  					},
  7520  				},
  7521  			},
  7522  		},
  7523  		{
  7524  			name: "nullable with types",
  7525  			input: apiextensions.CustomResourceValidation{
  7526  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7527  					Properties: map[string]apiextensions.JSONSchemaProps{
  7528  						"object": {
  7529  							Type:     "object",
  7530  							Nullable: true,
  7531  						},
  7532  						"array": {
  7533  							Type:     "array",
  7534  							Nullable: true,
  7535  						},
  7536  						"number": {
  7537  							Type:     "number",
  7538  							Nullable: true,
  7539  						},
  7540  						"string": {
  7541  							Type:     "string",
  7542  							Nullable: true,
  7543  						},
  7544  					},
  7545  				},
  7546  			},
  7547  		},
  7548  		{
  7549  			name: "must be structural, but isn't",
  7550  			input: apiextensions.CustomResourceValidation{
  7551  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{},
  7552  			},
  7553  			opts: validationOptions{requireStructuralSchema: true},
  7554  			expectedErrors: []validationMatch{
  7555  				required("spec.validation.openAPIV3Schema.type"),
  7556  			},
  7557  		},
  7558  		{
  7559  			name: "must be structural",
  7560  			input: apiextensions.CustomResourceValidation{
  7561  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7562  					Type: "object",
  7563  				},
  7564  			},
  7565  			opts: validationOptions{requireStructuralSchema: true},
  7566  		},
  7567  		{
  7568  			name: "require valid types, valid",
  7569  			input: apiextensions.CustomResourceValidation{
  7570  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7571  					Type: "object",
  7572  				},
  7573  			},
  7574  			opts: validationOptions{requireValidPropertyType: true, requireStructuralSchema: true},
  7575  		},
  7576  		{
  7577  			name: "require valid types, invalid",
  7578  			input: apiextensions.CustomResourceValidation{
  7579  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7580  					Type: "null",
  7581  				},
  7582  			},
  7583  			opts: validationOptions{requireValidPropertyType: true, requireStructuralSchema: true},
  7584  			expectedErrors: []validationMatch{
  7585  				// Invalid value: "null": must be object at the root
  7586  				unsupported("spec.validation.openAPIV3Schema.type"),
  7587  				// Forbidden: type cannot be set to null, use nullable as an alternative
  7588  				forbidden("spec.validation.openAPIV3Schema.type"),
  7589  				// Unsupported value: "null": supported values: "array", "boolean", "integer", "number", "object", "string"
  7590  				invalid("spec.validation.openAPIV3Schema.type"),
  7591  			},
  7592  		},
  7593  		{
  7594  			name: "require valid types, invalid",
  7595  			input: apiextensions.CustomResourceValidation{
  7596  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7597  					Type: "bogus",
  7598  				},
  7599  			},
  7600  			opts: validationOptions{requireValidPropertyType: true, requireStructuralSchema: true},
  7601  			expectedErrors: []validationMatch{
  7602  				unsupported("spec.validation.openAPIV3Schema.type"),
  7603  				invalid("spec.validation.openAPIV3Schema.type"),
  7604  			},
  7605  		},
  7606  		{
  7607  			name: "invalid type with list type extension set",
  7608  			input: apiextensions.CustomResourceValidation{
  7609  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7610  					Type:      "object",
  7611  					XListType: strPtr("set"),
  7612  				},
  7613  			},
  7614  			expectedErrors: []validationMatch{
  7615  				invalid("spec.validation.openAPIV3Schema.type"),
  7616  			},
  7617  		},
  7618  		{
  7619  			name: "unset type with list type extension set",
  7620  			input: apiextensions.CustomResourceValidation{
  7621  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7622  					XListType: strPtr("set"),
  7623  				},
  7624  			},
  7625  			expectedErrors: []validationMatch{
  7626  				required("spec.validation.openAPIV3Schema.type"),
  7627  			},
  7628  		},
  7629  		{
  7630  			name: "invalid list type extension",
  7631  			input: apiextensions.CustomResourceValidation{
  7632  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7633  					Type:      "array",
  7634  					XListType: strPtr("invalid"),
  7635  				},
  7636  			},
  7637  			expectedErrors: []validationMatch{
  7638  				unsupported("spec.validation.openAPIV3Schema.x-kubernetes-list-type"),
  7639  			},
  7640  		},
  7641  		{
  7642  			name: "invalid list type extension with list map keys extension non-empty",
  7643  			input: apiextensions.CustomResourceValidation{
  7644  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7645  					Type:         "array",
  7646  					XListType:    strPtr("set"),
  7647  					XListMapKeys: []string{"key"},
  7648  				},
  7649  			},
  7650  			expectedErrors: []validationMatch{
  7651  				invalid("spec.validation.openAPIV3Schema.x-kubernetes-list-type"),
  7652  			},
  7653  		},
  7654  		{
  7655  			name: "unset list type extension with list map keys extension non-empty",
  7656  			input: apiextensions.CustomResourceValidation{
  7657  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7658  					XListMapKeys: []string{"key"},
  7659  				},
  7660  			},
  7661  			expectedErrors: []validationMatch{
  7662  				required("spec.validation.openAPIV3Schema.x-kubernetes-list-type"),
  7663  			},
  7664  		},
  7665  		{
  7666  			name: "empty list map keys extension with list type extension map",
  7667  			input: apiextensions.CustomResourceValidation{
  7668  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7669  					Type:      "array",
  7670  					XListType: strPtr("map"),
  7671  				},
  7672  			},
  7673  			expectedErrors: []validationMatch{
  7674  				required("spec.validation.openAPIV3Schema.x-kubernetes-list-map-keys"),
  7675  				required("spec.validation.openAPIV3Schema.items"),
  7676  			},
  7677  		},
  7678  		{
  7679  			name: "no items schema with list type extension map",
  7680  			input: apiextensions.CustomResourceValidation{
  7681  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7682  					Type:         "array",
  7683  					XListType:    strPtr("map"),
  7684  					XListMapKeys: []string{"key"},
  7685  				},
  7686  			},
  7687  			expectedErrors: []validationMatch{
  7688  				required("spec.validation.openAPIV3Schema.items"),
  7689  			},
  7690  		},
  7691  		{
  7692  			name: "multiple schema items with list type extension map",
  7693  			input: apiextensions.CustomResourceValidation{
  7694  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7695  					Type:         "array",
  7696  					XListType:    strPtr("map"),
  7697  					XListMapKeys: []string{"key"},
  7698  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7699  						JSONSchemas: []apiextensions.JSONSchemaProps{
  7700  							{
  7701  								Type: "string",
  7702  							}, {
  7703  								Type: "integer",
  7704  							},
  7705  						},
  7706  					},
  7707  				},
  7708  			},
  7709  			expectedErrors: []validationMatch{
  7710  				forbidden("spec.validation.openAPIV3Schema.items"),
  7711  				invalid("spec.validation.openAPIV3Schema.items"),
  7712  			},
  7713  		},
  7714  		{
  7715  			name: "non object item with list type extension map",
  7716  			input: apiextensions.CustomResourceValidation{
  7717  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7718  					Type:         "array",
  7719  					XListType:    strPtr("map"),
  7720  					XListMapKeys: []string{"key"},
  7721  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7722  						Schema: &apiextensions.JSONSchemaProps{
  7723  							Type: "string",
  7724  						},
  7725  					},
  7726  				},
  7727  			},
  7728  			expectedErrors: []validationMatch{
  7729  				invalid("spec.validation.openAPIV3Schema.items.type"),
  7730  			},
  7731  		},
  7732  		{
  7733  			name: "items with key missing from properties with list type extension map",
  7734  			input: apiextensions.CustomResourceValidation{
  7735  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7736  					Type:         "array",
  7737  					XListType:    strPtr("map"),
  7738  					XListMapKeys: []string{"key"},
  7739  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7740  						Schema: &apiextensions.JSONSchemaProps{
  7741  							Type: "object",
  7742  						},
  7743  					},
  7744  				},
  7745  			},
  7746  			expectedErrors: []validationMatch{
  7747  				invalid("spec.validation.openAPIV3Schema.x-kubernetes-list-map-keys"),
  7748  			},
  7749  		},
  7750  		{
  7751  			name: "items with non scalar key property type with list type extension map",
  7752  			input: apiextensions.CustomResourceValidation{
  7753  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7754  					Type:         "array",
  7755  					XListType:    strPtr("map"),
  7756  					XListMapKeys: []string{"key"},
  7757  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7758  						Schema: &apiextensions.JSONSchemaProps{
  7759  							Type: "object",
  7760  							Properties: map[string]apiextensions.JSONSchemaProps{
  7761  								"key": {
  7762  									Type: "object",
  7763  								},
  7764  							},
  7765  						},
  7766  					},
  7767  				},
  7768  			},
  7769  			expectedErrors: []validationMatch{
  7770  				invalid("spec.validation.openAPIV3Schema.items.properties[key].type"),
  7771  			},
  7772  		},
  7773  		{
  7774  			name: "duplicate map keys with list type extension map",
  7775  			input: apiextensions.CustomResourceValidation{
  7776  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7777  					Type:         "array",
  7778  					XListType:    strPtr("map"),
  7779  					XListMapKeys: []string{"key", "key"},
  7780  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7781  						Schema: &apiextensions.JSONSchemaProps{
  7782  							Type: "object",
  7783  							Properties: map[string]apiextensions.JSONSchemaProps{
  7784  								"key": {
  7785  									Type: "string",
  7786  								},
  7787  							},
  7788  						},
  7789  					},
  7790  				},
  7791  			},
  7792  			expectedErrors: []validationMatch{
  7793  				invalid("spec.validation.openAPIV3Schema.x-kubernetes-list-map-keys"),
  7794  			},
  7795  		},
  7796  		{
  7797  			name: "allowed schema with list type extension map",
  7798  			input: apiextensions.CustomResourceValidation{
  7799  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7800  					Type:         "array",
  7801  					XListType:    strPtr("map"),
  7802  					XListMapKeys: []string{"keyA", "keyB"},
  7803  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7804  						Schema: &apiextensions.JSONSchemaProps{
  7805  							Type: "object",
  7806  							Properties: map[string]apiextensions.JSONSchemaProps{
  7807  								"keyA": {
  7808  									Type: "string",
  7809  								},
  7810  								"keyB": {
  7811  									Type: "integer",
  7812  								},
  7813  							},
  7814  						},
  7815  					},
  7816  				},
  7817  			},
  7818  		},
  7819  		{
  7820  			name: "allowed list-type atomic",
  7821  			input: apiextensions.CustomResourceValidation{
  7822  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7823  					Type:      "array",
  7824  					XListType: strPtr("atomic"),
  7825  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7826  						Schema: &apiextensions.JSONSchemaProps{
  7827  							Type: "string",
  7828  						},
  7829  					},
  7830  				},
  7831  			},
  7832  		},
  7833  		{
  7834  			name: "allowed list-type atomic with non-atomic items",
  7835  			input: apiextensions.CustomResourceValidation{
  7836  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7837  					Type:      "array",
  7838  					XListType: strPtr("atomic"),
  7839  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7840  						Schema: &apiextensions.JSONSchemaProps{
  7841  							Type:       "object",
  7842  							Properties: map[string]apiextensions.JSONSchemaProps{},
  7843  						},
  7844  					},
  7845  				},
  7846  			},
  7847  		},
  7848  		{
  7849  			name: "allowed list-type set with scalar items",
  7850  			input: apiextensions.CustomResourceValidation{
  7851  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7852  					Type:      "array",
  7853  					XListType: strPtr("set"),
  7854  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7855  						Schema: &apiextensions.JSONSchemaProps{
  7856  							Type: "string",
  7857  						},
  7858  					},
  7859  				},
  7860  			},
  7861  		},
  7862  		{
  7863  			name: "allowed list-type set with atomic map items",
  7864  			input: apiextensions.CustomResourceValidation{
  7865  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7866  					Type:      "array",
  7867  					XListType: strPtr("set"),
  7868  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7869  						Schema: &apiextensions.JSONSchemaProps{
  7870  							Type:     "object",
  7871  							XMapType: strPtr("atomic"),
  7872  							Properties: map[string]apiextensions.JSONSchemaProps{
  7873  								"foo": {Type: "string"},
  7874  							},
  7875  						},
  7876  					},
  7877  				},
  7878  			},
  7879  		},
  7880  		{
  7881  			name: "invalid list-type set with non-atomic map items",
  7882  			input: apiextensions.CustomResourceValidation{
  7883  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7884  					Type:      "array",
  7885  					XListType: strPtr("set"),
  7886  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7887  						Schema: &apiextensions.JSONSchemaProps{
  7888  							Type:     "object",
  7889  							XMapType: strPtr("granular"),
  7890  							Properties: map[string]apiextensions.JSONSchemaProps{
  7891  								"foo": {Type: "string"},
  7892  							},
  7893  						},
  7894  					},
  7895  				},
  7896  			},
  7897  			opts: validationOptions{requireAtomicSetType: true},
  7898  			expectedErrors: []validationMatch{
  7899  				invalid("spec.validation.openAPIV3Schema.items.x-kubernetes-map-type"),
  7900  			},
  7901  		},
  7902  		{
  7903  			name: "invalid list-type set with unspecified map-type for map items",
  7904  			input: apiextensions.CustomResourceValidation{
  7905  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7906  					Type:      "array",
  7907  					XListType: strPtr("set"),
  7908  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7909  						Schema: &apiextensions.JSONSchemaProps{
  7910  							Type: "object",
  7911  							Properties: map[string]apiextensions.JSONSchemaProps{
  7912  								"foo": {Type: "string"},
  7913  							},
  7914  						},
  7915  					},
  7916  				},
  7917  			},
  7918  			opts: validationOptions{requireAtomicSetType: true},
  7919  			expectedErrors: []validationMatch{
  7920  				invalid("spec.validation.openAPIV3Schema.items.x-kubernetes-map-type"),
  7921  			},
  7922  		},
  7923  		{
  7924  			name: "allowed list-type set with atomic list items",
  7925  			input: apiextensions.CustomResourceValidation{
  7926  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7927  					Type:      "array",
  7928  					XListType: strPtr("set"),
  7929  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7930  						Schema: &apiextensions.JSONSchemaProps{
  7931  							Type:      "array",
  7932  							XListType: strPtr("atomic"),
  7933  							Items: &apiextensions.JSONSchemaPropsOrArray{
  7934  								Schema: &apiextensions.JSONSchemaProps{
  7935  									Type: "string",
  7936  								},
  7937  							},
  7938  						},
  7939  					},
  7940  				},
  7941  			},
  7942  		},
  7943  		{
  7944  			name: "allowed list-type set with unspecified list-type in list items",
  7945  			input: apiextensions.CustomResourceValidation{
  7946  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7947  					Type:      "array",
  7948  					XListType: strPtr("set"),
  7949  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7950  						Schema: &apiextensions.JSONSchemaProps{
  7951  							Type: "array",
  7952  							Items: &apiextensions.JSONSchemaPropsOrArray{
  7953  								Schema: &apiextensions.JSONSchemaProps{
  7954  									Type: "string",
  7955  								},
  7956  							},
  7957  						},
  7958  					},
  7959  				},
  7960  			},
  7961  		},
  7962  		{
  7963  			name: "invalid list-type set with with non-atomic list items",
  7964  			input: apiextensions.CustomResourceValidation{
  7965  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7966  					Type:      "array",
  7967  					XListType: strPtr("set"),
  7968  					Items: &apiextensions.JSONSchemaPropsOrArray{
  7969  						Schema: &apiextensions.JSONSchemaProps{
  7970  							Type:      "array",
  7971  							XListType: strPtr("set"),
  7972  							Items: &apiextensions.JSONSchemaPropsOrArray{
  7973  								Schema: &apiextensions.JSONSchemaProps{
  7974  									Type: "string",
  7975  								},
  7976  							},
  7977  						},
  7978  					},
  7979  				},
  7980  			},
  7981  			opts: validationOptions{requireAtomicSetType: true},
  7982  			expectedErrors: []validationMatch{
  7983  				invalid("spec.validation.openAPIV3Schema.items.x-kubernetes-list-type"),
  7984  			},
  7985  		},
  7986  		{
  7987  			name: "invalid type with map type extension (granular)",
  7988  			input: apiextensions.CustomResourceValidation{
  7989  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  7990  					Type:     "array",
  7991  					XMapType: strPtr("granular"),
  7992  				},
  7993  			},
  7994  			expectedErrors: []validationMatch{
  7995  				invalid("spec.validation.openAPIV3Schema.type"),
  7996  			},
  7997  		},
  7998  		{
  7999  			name: "unset type with map type extension (granular)",
  8000  			input: apiextensions.CustomResourceValidation{
  8001  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8002  					XMapType: strPtr("granular"),
  8003  				},
  8004  			},
  8005  			expectedErrors: []validationMatch{
  8006  				required("spec.validation.openAPIV3Schema.type"),
  8007  			},
  8008  		},
  8009  		{
  8010  			name: "invalid type with map type extension (atomic)",
  8011  			input: apiextensions.CustomResourceValidation{
  8012  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8013  					Type:     "array",
  8014  					XMapType: strPtr("atomic"),
  8015  				},
  8016  			},
  8017  			expectedErrors: []validationMatch{
  8018  				invalid("spec.validation.openAPIV3Schema.type"),
  8019  			},
  8020  		},
  8021  		{
  8022  			name: "unset type with map type extension (atomic)",
  8023  			input: apiextensions.CustomResourceValidation{
  8024  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8025  					XMapType: strPtr("atomic"),
  8026  				},
  8027  			},
  8028  			expectedErrors: []validationMatch{
  8029  				required("spec.validation.openAPIV3Schema.type"),
  8030  			},
  8031  		},
  8032  		{
  8033  			name: "invalid map type",
  8034  			input: apiextensions.CustomResourceValidation{
  8035  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8036  					Type:     "object",
  8037  					XMapType: strPtr("badMapType"),
  8038  				},
  8039  			},
  8040  			expectedErrors: []validationMatch{
  8041  				unsupported("spec.validation.openAPIV3Schema.x-kubernetes-map-type"),
  8042  			},
  8043  		},
  8044  		{
  8045  			name: "allowed type with map type extension (granular)",
  8046  			input: apiextensions.CustomResourceValidation{
  8047  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8048  					Type:     "object",
  8049  					XMapType: strPtr("granular"),
  8050  				},
  8051  			},
  8052  		},
  8053  		{
  8054  			name: "allowed type with map type extension (atomic)",
  8055  			input: apiextensions.CustomResourceValidation{
  8056  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8057  					Type:     "object",
  8058  					XMapType: strPtr("atomic"),
  8059  				},
  8060  			},
  8061  		},
  8062  		{
  8063  			name: "invalid map with non-required key and no default",
  8064  			input: apiextensions.CustomResourceValidation{
  8065  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8066  					Type:         "array",
  8067  					XListType:    strPtr("map"),
  8068  					XListMapKeys: []string{"key"},
  8069  					Items: &apiextensions.JSONSchemaPropsOrArray{
  8070  						Schema: &apiextensions.JSONSchemaProps{
  8071  							Type: "object",
  8072  							Properties: map[string]apiextensions.JSONSchemaProps{
  8073  								"key": {
  8074  									Type: "string",
  8075  								},
  8076  							},
  8077  						},
  8078  					},
  8079  				},
  8080  			},
  8081  			opts: validationOptions{
  8082  				requireMapListKeysMapSetValidation: true,
  8083  			},
  8084  			expectedErrors: []validationMatch{
  8085  				required("spec.validation.openAPIV3Schema.items.properties[key].default"),
  8086  			},
  8087  		},
  8088  		{
  8089  			name: "allowed map with required key and no default",
  8090  			input: apiextensions.CustomResourceValidation{
  8091  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8092  					Type:         "array",
  8093  					XListType:    strPtr("map"),
  8094  					XListMapKeys: []string{"key"},
  8095  					Items: &apiextensions.JSONSchemaPropsOrArray{
  8096  						Schema: &apiextensions.JSONSchemaProps{
  8097  							Type:     "object",
  8098  							Required: []string{"key"},
  8099  							Properties: map[string]apiextensions.JSONSchemaProps{
  8100  								"key": {
  8101  									Type: "string",
  8102  								},
  8103  							},
  8104  						},
  8105  					},
  8106  				},
  8107  			},
  8108  			opts: validationOptions{
  8109  				requireMapListKeysMapSetValidation: true,
  8110  			},
  8111  		},
  8112  		{
  8113  			name: "allowed map with non-required key and default",
  8114  			input: apiextensions.CustomResourceValidation{
  8115  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8116  					Type:         "array",
  8117  					XListType:    strPtr("map"),
  8118  					XListMapKeys: []string{"key"},
  8119  					Items: &apiextensions.JSONSchemaPropsOrArray{
  8120  						Schema: &apiextensions.JSONSchemaProps{
  8121  							Type: "object",
  8122  							Properties: map[string]apiextensions.JSONSchemaProps{
  8123  								"key": {
  8124  									Type:    "string",
  8125  									Default: jsonPtr("stuff"),
  8126  								},
  8127  							},
  8128  						},
  8129  					},
  8130  				},
  8131  			},
  8132  			opts: validationOptions{
  8133  				allowDefaults:                      true,
  8134  				requireMapListKeysMapSetValidation: true,
  8135  			},
  8136  		},
  8137  		{
  8138  			name: "invalid map with nullable key",
  8139  			input: apiextensions.CustomResourceValidation{
  8140  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8141  					Type:         "array",
  8142  					XListType:    strPtr("map"),
  8143  					XListMapKeys: []string{"key"},
  8144  					Items: &apiextensions.JSONSchemaPropsOrArray{
  8145  						Schema: &apiextensions.JSONSchemaProps{
  8146  							Type: "object",
  8147  							Properties: map[string]apiextensions.JSONSchemaProps{
  8148  								"key": {
  8149  									Type:     "string",
  8150  									Nullable: true,
  8151  								},
  8152  							},
  8153  						},
  8154  					},
  8155  				},
  8156  			},
  8157  			opts: validationOptions{
  8158  				requireMapListKeysMapSetValidation: true,
  8159  			},
  8160  			expectedErrors: []validationMatch{
  8161  				required("spec.validation.openAPIV3Schema.items.properties[key].default"),
  8162  				forbidden("spec.validation.openAPIV3Schema.items.properties[key].nullable"),
  8163  			},
  8164  		},
  8165  		{
  8166  			name: "invalid map with nullable items",
  8167  			input: apiextensions.CustomResourceValidation{
  8168  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8169  					Type:         "array",
  8170  					XListType:    strPtr("map"),
  8171  					XListMapKeys: []string{"key"},
  8172  					Items: &apiextensions.JSONSchemaPropsOrArray{
  8173  						Schema: &apiextensions.JSONSchemaProps{
  8174  							Type:     "object",
  8175  							Nullable: true,
  8176  							Properties: map[string]apiextensions.JSONSchemaProps{
  8177  								"key": {
  8178  									Type: "string",
  8179  								},
  8180  							},
  8181  						},
  8182  					},
  8183  				},
  8184  			},
  8185  			opts: validationOptions{
  8186  				requireMapListKeysMapSetValidation: true,
  8187  			},
  8188  			expectedErrors: []validationMatch{
  8189  				forbidden("spec.validation.openAPIV3Schema.items.nullable"),
  8190  				required("spec.validation.openAPIV3Schema.items.properties[key].default"),
  8191  			},
  8192  		},
  8193  		{
  8194  			name: "valid map with some required, some defaulted, and non-key fields",
  8195  			input: apiextensions.CustomResourceValidation{
  8196  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8197  					Type:         "array",
  8198  					XListType:    strPtr("map"),
  8199  					XListMapKeys: []string{"a"},
  8200  					Items: &apiextensions.JSONSchemaPropsOrArray{
  8201  						Schema: &apiextensions.JSONSchemaProps{
  8202  							Type:     "object",
  8203  							Required: []string{"a", "c"},
  8204  							Properties: map[string]apiextensions.JSONSchemaProps{
  8205  								"key": {
  8206  									Type: "string",
  8207  								},
  8208  								"a": {
  8209  									Type: "string",
  8210  								},
  8211  								"b": {
  8212  									Type:    "string",
  8213  									Default: jsonPtr("stuff"),
  8214  								},
  8215  								"c": {
  8216  									Type: "int",
  8217  								},
  8218  							},
  8219  						},
  8220  					},
  8221  				},
  8222  			},
  8223  			opts: validationOptions{
  8224  				requireMapListKeysMapSetValidation: true,
  8225  			},
  8226  			expectedErrors: []validationMatch{
  8227  				forbidden("spec.validation.openAPIV3Schema.items.properties[b].default"),
  8228  			},
  8229  		},
  8230  		{
  8231  			name: "invalid set with nullable items",
  8232  			input: apiextensions.CustomResourceValidation{
  8233  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8234  					Type:      "array",
  8235  					XListType: strPtr("set"),
  8236  					Items: &apiextensions.JSONSchemaPropsOrArray{
  8237  						Schema: &apiextensions.JSONSchemaProps{
  8238  							Nullable: true,
  8239  						},
  8240  					},
  8241  				},
  8242  			},
  8243  			opts: validationOptions{
  8244  				requireMapListKeysMapSetValidation: true,
  8245  			},
  8246  			expectedErrors: []validationMatch{
  8247  				forbidden("spec.validation.openAPIV3Schema.items.nullable"),
  8248  			},
  8249  		},
  8250  		{
  8251  			name: "allowed set with non-nullable items",
  8252  			input: apiextensions.CustomResourceValidation{
  8253  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8254  					Type:      "array",
  8255  					XListType: strPtr("set"),
  8256  					Items: &apiextensions.JSONSchemaPropsOrArray{
  8257  						Schema: &apiextensions.JSONSchemaProps{
  8258  							Nullable: false,
  8259  						},
  8260  					},
  8261  				},
  8262  			},
  8263  			opts: validationOptions{
  8264  				requireMapListKeysMapSetValidation: true,
  8265  			},
  8266  		},
  8267  		{
  8268  			name: "valid x-kubernetes-validations for scalar element",
  8269  			input: apiextensions.CustomResourceValidation{
  8270  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8271  					Type: "object",
  8272  					Properties: map[string]apiextensions.JSONSchemaProps{
  8273  						"subRoot": {
  8274  							Type: "string",
  8275  							XValidations: apiextensions.ValidationRules{
  8276  								{
  8277  									Rule:    "self.startsWith('s')",
  8278  									Message: "subRoot should start with 's'.",
  8279  								},
  8280  								{
  8281  									Rule:    "self.endsWith('s')",
  8282  									Message: "subRoot should end with 's'.",
  8283  								},
  8284  							},
  8285  						},
  8286  					},
  8287  				},
  8288  			},
  8289  			opts: validationOptions{
  8290  				requireStructuralSchema: true,
  8291  			},
  8292  		},
  8293  		{
  8294  			name: "valid x-kubernetes-validations for object",
  8295  			input: apiextensions.CustomResourceValidation{
  8296  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8297  					Type: "object",
  8298  					XValidations: apiextensions.ValidationRules{
  8299  						{
  8300  							Rule:    "self.minReplicas <= self.maxReplicas",
  8301  							Message: "minReplicas should be no greater than maxReplicas",
  8302  						},
  8303  					},
  8304  					Properties: map[string]apiextensions.JSONSchemaProps{
  8305  						"minReplicas": {
  8306  							Type: "integer",
  8307  						},
  8308  						"maxReplicas": {
  8309  							Type: "integer",
  8310  						},
  8311  					},
  8312  				},
  8313  			},
  8314  			opts: validationOptions{
  8315  				requireStructuralSchema: true,
  8316  			},
  8317  		},
  8318  		{
  8319  			name: "invalid x-kubernetes-validations with empty rule",
  8320  			input: apiextensions.CustomResourceValidation{
  8321  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8322  					Type: "object",
  8323  					XValidations: apiextensions.ValidationRules{
  8324  						{Message: "empty rule"},
  8325  					},
  8326  				},
  8327  			},
  8328  			expectedErrors: []validationMatch{
  8329  				required("spec.validation.openAPIV3Schema.x-kubernetes-validations[0].rule"),
  8330  			},
  8331  			opts: validationOptions{
  8332  				requireStructuralSchema: true,
  8333  			},
  8334  		},
  8335  		{
  8336  			name: "valid x-kubernetes-validations with empty validators",
  8337  			input: apiextensions.CustomResourceValidation{
  8338  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8339  					Type:         "object",
  8340  					XValidations: apiextensions.ValidationRules{},
  8341  				},
  8342  			},
  8343  			opts: validationOptions{
  8344  				requireStructuralSchema: true,
  8345  			},
  8346  		},
  8347  		{
  8348  			name: "invalid rule in x-kubernetes-validations",
  8349  			input: apiextensions.CustomResourceValidation{
  8350  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8351  					Type: "object",
  8352  					Properties: map[string]apiextensions.JSONSchemaProps{
  8353  						"subRoot": {
  8354  							Type: "string",
  8355  							XValidations: apiextensions.ValidationRules{
  8356  								{
  8357  									Rule:    "self == true",
  8358  									Message: "subRoot should be true.",
  8359  								},
  8360  								{
  8361  									Rule:    "self.endsWith('s')",
  8362  									Message: "subRoot should end with 's'.",
  8363  								},
  8364  							},
  8365  						},
  8366  					},
  8367  				},
  8368  			},
  8369  			expectedErrors: []validationMatch{
  8370  				invalid("spec.validation.openAPIV3Schema.properties[subRoot].x-kubernetes-validations[0].rule"),
  8371  			},
  8372  			opts: validationOptions{
  8373  				requireStructuralSchema: true,
  8374  			},
  8375  		},
  8376  		{
  8377  			name: "valid x-kubernetes-validations for nested object under multiple fields",
  8378  			input: apiextensions.CustomResourceValidation{
  8379  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8380  					Type: "object",
  8381  					XValidations: apiextensions.ValidationRules{
  8382  						{
  8383  							Rule:    "self.minReplicas <= self.maxReplicas",
  8384  							Message: "minReplicas should be no greater than maxReplicas.",
  8385  						},
  8386  					},
  8387  					Properties: map[string]apiextensions.JSONSchemaProps{
  8388  						"minReplicas": {
  8389  							Type: "integer",
  8390  						},
  8391  						"maxReplicas": {
  8392  							Type: "integer",
  8393  						},
  8394  						"subRule": {
  8395  							Type: "object",
  8396  							XValidations: apiextensions.ValidationRules{
  8397  								{
  8398  									Rule:    "self.isTest == true",
  8399  									Message: "isTest should be true.",
  8400  								},
  8401  							},
  8402  							Properties: map[string]apiextensions.JSONSchemaProps{
  8403  								"isTest": {
  8404  									Type: "boolean",
  8405  								},
  8406  							},
  8407  						},
  8408  					},
  8409  				},
  8410  			},
  8411  			opts: validationOptions{
  8412  				requireStructuralSchema: true,
  8413  			},
  8414  		},
  8415  		{
  8416  			name: "valid x-kubernetes-validations for object of array",
  8417  			input: apiextensions.CustomResourceValidation{
  8418  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8419  					Type: "object",
  8420  					XValidations: apiextensions.ValidationRules{
  8421  						{
  8422  							Rule:    "size(self.nestedObj[0]) == 10",
  8423  							Message: "size of first element in nestedObj should be equal to 10",
  8424  						},
  8425  					},
  8426  					Properties: map[string]apiextensions.JSONSchemaProps{
  8427  						"nestedObj": {
  8428  							Type: "array",
  8429  							Items: &apiextensions.JSONSchemaPropsOrArray{
  8430  								Schema: &apiextensions.JSONSchemaProps{
  8431  									Type: "array",
  8432  									Items: &apiextensions.JSONSchemaPropsOrArray{
  8433  										Schema: &apiextensions.JSONSchemaProps{
  8434  											Type: "string",
  8435  										},
  8436  									},
  8437  								},
  8438  							},
  8439  						},
  8440  					},
  8441  				},
  8442  			},
  8443  			opts: validationOptions{
  8444  				requireStructuralSchema: true,
  8445  			},
  8446  		},
  8447  		{
  8448  			name: "valid x-kubernetes-validations for array",
  8449  			input: apiextensions.CustomResourceValidation{
  8450  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8451  					Type: "object",
  8452  					Items: &apiextensions.JSONSchemaPropsOrArray{
  8453  						Schema: &apiextensions.JSONSchemaProps{
  8454  							Type: "array",
  8455  							XValidations: apiextensions.ValidationRules{
  8456  								{
  8457  									Rule:    "size(self) > 0",
  8458  									Message: "scoped field should contain more than 0 element.",
  8459  								},
  8460  							},
  8461  							Items: &apiextensions.JSONSchemaPropsOrArray{
  8462  								Schema: &apiextensions.JSONSchemaProps{
  8463  									Type: "string",
  8464  								},
  8465  							},
  8466  						},
  8467  					},
  8468  				},
  8469  			},
  8470  			opts: validationOptions{
  8471  				requireStructuralSchema: true,
  8472  			},
  8473  		},
  8474  		{
  8475  			name: "valid x-kubernetes-validations for array of object",
  8476  			input: apiextensions.CustomResourceValidation{
  8477  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8478  					Type: "object",
  8479  					Items: &apiextensions.JSONSchemaPropsOrArray{
  8480  						Schema: &apiextensions.JSONSchemaProps{
  8481  							Type: "array",
  8482  							XValidations: apiextensions.ValidationRules{
  8483  								{
  8484  									Rule:    "self[0].nestedObj.val > 0",
  8485  									Message: "val should be greater than 0.",
  8486  								},
  8487  							},
  8488  							Items: &apiextensions.JSONSchemaPropsOrArray{
  8489  								Schema: &apiextensions.JSONSchemaProps{
  8490  									Type: "object",
  8491  									Properties: map[string]apiextensions.JSONSchemaProps{
  8492  										"nestedObj": {
  8493  											Type: "object",
  8494  											Properties: map[string]apiextensions.JSONSchemaProps{
  8495  												"val": {
  8496  													Type: "integer",
  8497  												},
  8498  											},
  8499  										},
  8500  									},
  8501  								},
  8502  							},
  8503  						},
  8504  					},
  8505  				},
  8506  			},
  8507  			opts: validationOptions{
  8508  				requireStructuralSchema: true,
  8509  			},
  8510  		},
  8511  		{
  8512  			name: "valid x-kubernetes-validations for escaping",
  8513  			input: apiextensions.CustomResourceValidation{
  8514  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8515  					Type: "object",
  8516  					XValidations: apiextensions.ValidationRules{
  8517  						{
  8518  							Rule: "self.__if__ > 0",
  8519  						},
  8520  						{
  8521  							Rule: "self.__namespace__ > 0",
  8522  						},
  8523  						{
  8524  							Rule: "self.self > 0",
  8525  						},
  8526  						{
  8527  							Rule: "self.int > 0",
  8528  						},
  8529  					},
  8530  					Properties: map[string]apiextensions.JSONSchemaProps{
  8531  						"if": {
  8532  							Type: "integer",
  8533  						},
  8534  						"namespace": {
  8535  							Type: "integer",
  8536  						},
  8537  						"self": {
  8538  							Type: "integer",
  8539  						},
  8540  						"int": {
  8541  							Type: "integer",
  8542  						},
  8543  					},
  8544  				},
  8545  			},
  8546  			opts: validationOptions{
  8547  				requireStructuralSchema: true,
  8548  			},
  8549  		},
  8550  		{
  8551  			name: "invalid x-kubernetes-validations for escaping",
  8552  			input: apiextensions.CustomResourceValidation{
  8553  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8554  					Type: "object",
  8555  					XValidations: apiextensions.ValidationRules{
  8556  						{
  8557  							Rule: "self.if > 0",
  8558  						},
  8559  						{
  8560  							Rule: "self.namespace > 0",
  8561  						},
  8562  						{
  8563  							Rule: "self.unknownProp > 0",
  8564  						},
  8565  					},
  8566  					Properties: map[string]apiextensions.JSONSchemaProps{
  8567  						"if": {
  8568  							Type: "integer",
  8569  						},
  8570  						"namespace": {
  8571  							Type: "integer",
  8572  						},
  8573  					},
  8574  				},
  8575  			},
  8576  			expectedErrors: []validationMatch{
  8577  				invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[0].rule"),
  8578  				invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[1].rule"),
  8579  				invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[2].rule"),
  8580  			},
  8581  			opts: validationOptions{
  8582  				requireStructuralSchema: true,
  8583  			},
  8584  		},
  8585  		{
  8586  			name: "valid default with x-kubernetes-validations",
  8587  			input: apiextensions.CustomResourceValidation{
  8588  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8589  					Type: "object",
  8590  					Properties: map[string]apiextensions.JSONSchemaProps{
  8591  						"embedded": {
  8592  							Type: "object",
  8593  							Properties: map[string]apiextensions.JSONSchemaProps{
  8594  								"metadata": {
  8595  									Type:              "object",
  8596  									XEmbeddedResource: true,
  8597  									Properties: map[string]apiextensions.JSONSchemaProps{
  8598  										"name": {
  8599  											Type: "string",
  8600  											XValidations: apiextensions.ValidationRules{
  8601  												{
  8602  													Rule: "self == 'singleton'",
  8603  												},
  8604  											},
  8605  											Default: jsonPtr("singleton"),
  8606  										},
  8607  									},
  8608  								},
  8609  							},
  8610  						},
  8611  						"value": {
  8612  							Type: "string",
  8613  							XValidations: apiextensions.ValidationRules{
  8614  								{
  8615  									Rule: "self.startsWith('kube')",
  8616  								},
  8617  							},
  8618  							Default: jsonPtr("kube-everything"),
  8619  						},
  8620  						"object": {
  8621  							Type: "object",
  8622  							Properties: map[string]apiextensions.JSONSchemaProps{
  8623  								"field1": {
  8624  									Type: "integer",
  8625  								},
  8626  								"field2": {
  8627  									Type: "integer",
  8628  								},
  8629  							},
  8630  							XValidations: apiextensions.ValidationRules{
  8631  								{
  8632  									Rule: "self.field1 < self.field2",
  8633  								},
  8634  							},
  8635  							Default: jsonPtr(map[string]interface{}{"field1": 1, "field2": 2}),
  8636  						},
  8637  					},
  8638  				},
  8639  			},
  8640  			opts: validationOptions{
  8641  				requireStructuralSchema: true,
  8642  				allowDefaults:           true,
  8643  			},
  8644  		},
  8645  		{
  8646  			name: "invalid default with x-kubernetes-validations",
  8647  			input: apiextensions.CustomResourceValidation{
  8648  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8649  					Type: "object",
  8650  					Properties: map[string]apiextensions.JSONSchemaProps{
  8651  						"embedded": {
  8652  							Type: "object",
  8653  							Properties: map[string]apiextensions.JSONSchemaProps{
  8654  								"metadata": {
  8655  									Type:              "object",
  8656  									XEmbeddedResource: true,
  8657  									Properties: map[string]apiextensions.JSONSchemaProps{
  8658  										"name": {
  8659  											Type: "string",
  8660  											XValidations: apiextensions.ValidationRules{
  8661  												{
  8662  													Rule: "self == 'singleton'",
  8663  												},
  8664  											},
  8665  											Default: jsonPtr("nope"),
  8666  										},
  8667  									},
  8668  								},
  8669  							},
  8670  						},
  8671  						"value": {
  8672  							Type: "string",
  8673  							XValidations: apiextensions.ValidationRules{
  8674  								{
  8675  									Rule: "self.startsWith('kube')",
  8676  								},
  8677  							},
  8678  							Default: jsonPtr("nope"),
  8679  						},
  8680  						"object": {
  8681  							Type: "object",
  8682  							Properties: map[string]apiextensions.JSONSchemaProps{
  8683  								"field1": {
  8684  									Type: "integer",
  8685  								},
  8686  								"field2": {
  8687  									Type: "integer",
  8688  								},
  8689  							},
  8690  							XValidations: apiextensions.ValidationRules{
  8691  								{
  8692  									Rule: "self.field1 < self.field2",
  8693  								},
  8694  							},
  8695  							Default: jsonPtr(map[string]interface{}{"field1": 2, "field2": 1}),
  8696  						},
  8697  					},
  8698  				},
  8699  			},
  8700  			expectedErrors: []validationMatch{
  8701  				invalid("spec.validation.openAPIV3Schema.properties[embedded].properties[metadata].properties[name].default"),
  8702  				invalid("spec.validation.openAPIV3Schema.properties[value].default"),
  8703  				invalid("spec.validation.openAPIV3Schema.properties[object].default"),
  8704  			},
  8705  			opts: validationOptions{
  8706  				requireStructuralSchema: true,
  8707  				allowDefaults:           true,
  8708  			},
  8709  		},
  8710  		{
  8711  			name: "rule is empty or not specified",
  8712  			input: apiextensions.CustomResourceValidation{
  8713  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8714  					Type: "object",
  8715  					Properties: map[string]apiextensions.JSONSchemaProps{
  8716  						"value": {
  8717  							Type: "integer",
  8718  							XValidations: apiextensions.ValidationRules{
  8719  								{
  8720  									Message: "something",
  8721  								},
  8722  								{
  8723  									Rule:    "   ",
  8724  									Message: "something",
  8725  								},
  8726  							},
  8727  						},
  8728  					},
  8729  				},
  8730  			},
  8731  			expectedErrors: []validationMatch{
  8732  				required("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].rule"),
  8733  				required("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[1].rule"),
  8734  			},
  8735  		},
  8736  		{
  8737  			name: "multiline rule with message",
  8738  			input: apiextensions.CustomResourceValidation{
  8739  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8740  					Type: "object",
  8741  					Properties: map[string]apiextensions.JSONSchemaProps{
  8742  						"value": {
  8743  							Type: "integer",
  8744  							XValidations: apiextensions.ValidationRules{
  8745  								{
  8746  									Rule:    "self >= 0 &&\nself <= 100",
  8747  									Message: "value must be between 0 and 100 (inclusive)",
  8748  								},
  8749  							},
  8750  						},
  8751  					},
  8752  				},
  8753  			},
  8754  		},
  8755  		{
  8756  			name: "invalid and required messages",
  8757  			input: apiextensions.CustomResourceValidation{
  8758  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8759  					Type: "object",
  8760  					Properties: map[string]apiextensions.JSONSchemaProps{
  8761  						"value": {
  8762  							Type: "integer",
  8763  							XValidations: apiextensions.ValidationRules{
  8764  								{
  8765  									Rule: "self >= 0 &&\nself <= 100",
  8766  								},
  8767  								{
  8768  									Rule:    "self == 50",
  8769  									Message: "value requirements:\nmust be >= 0\nmust be <= 100 ",
  8770  								},
  8771  								{
  8772  									Rule:    "self == 50",
  8773  									Message: " ",
  8774  								},
  8775  							},
  8776  						},
  8777  					},
  8778  				},
  8779  			},
  8780  			expectedErrors: []validationMatch{
  8781  				// message must be specified if rule contains line breaks
  8782  				required("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].message"),
  8783  				// message must not contain line breaks
  8784  				invalid("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[1].message"),
  8785  				// message must be non-empty if specified
  8786  				invalid("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[2].message"),
  8787  			},
  8788  		},
  8789  		{
  8790  			name: "forbid transition rule on element of list of type atomic",
  8791  			opts: validationOptions{requireStructuralSchema: true},
  8792  			input: apiextensions.CustomResourceValidation{
  8793  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8794  					Type: "object",
  8795  					Properties: map[string]apiextensions.JSONSchemaProps{
  8796  						"value": {
  8797  							Type:      "array",
  8798  							XListType: strPtr("atomic"),
  8799  							Items: &apiextensions.JSONSchemaPropsOrArray{
  8800  								Schema: &apiextensions.JSONSchemaProps{
  8801  									Type:      "string",
  8802  									MaxLength: int64ptr(10),
  8803  									XValidations: apiextensions.ValidationRules{
  8804  										{Rule: "self == oldSelf"},
  8805  									},
  8806  								},
  8807  							},
  8808  						},
  8809  					},
  8810  				},
  8811  			},
  8812  			expectedErrors: []validationMatch{
  8813  				invalid("spec.validation.openAPIV3Schema.properties[value].items.x-kubernetes-validations[0].rule"),
  8814  			},
  8815  		},
  8816  		{
  8817  			name: "forbid transition rule on element of list defaulting to type atomic",
  8818  			opts: validationOptions{requireStructuralSchema: true},
  8819  			input: apiextensions.CustomResourceValidation{
  8820  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8821  					Type: "object",
  8822  					Properties: map[string]apiextensions.JSONSchemaProps{
  8823  						"value": {
  8824  							Type: "array",
  8825  							Items: &apiextensions.JSONSchemaPropsOrArray{
  8826  								Schema: &apiextensions.JSONSchemaProps{
  8827  									Type:      "string",
  8828  									MaxLength: int64ptr(10),
  8829  									XValidations: apiextensions.ValidationRules{
  8830  										{Rule: "self == oldSelf"},
  8831  									},
  8832  								},
  8833  							},
  8834  						},
  8835  					},
  8836  				},
  8837  			},
  8838  			expectedErrors: []validationMatch{
  8839  				invalid("spec.validation.openAPIV3Schema.properties[value].items.x-kubernetes-validations[0].rule"),
  8840  			},
  8841  		},
  8842  		{
  8843  			name: "allow transition rule on list of type atomic",
  8844  			opts: validationOptions{requireStructuralSchema: true},
  8845  			input: apiextensions.CustomResourceValidation{
  8846  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8847  					Type: "object",
  8848  					Properties: map[string]apiextensions.JSONSchemaProps{
  8849  						"value": {
  8850  							Type:      "array",
  8851  							MaxItems:  int64ptr(10),
  8852  							XListType: strPtr("atomic"),
  8853  							Items: &apiextensions.JSONSchemaPropsOrArray{
  8854  								Schema: &apiextensions.JSONSchemaProps{
  8855  									Type: "string",
  8856  								},
  8857  							},
  8858  							XValidations: apiextensions.ValidationRules{
  8859  								{Rule: "self == oldSelf"},
  8860  							},
  8861  						},
  8862  					},
  8863  				},
  8864  			},
  8865  		},
  8866  		{
  8867  			name: "allow transition rule on list defaulting to type atomic",
  8868  			input: apiextensions.CustomResourceValidation{
  8869  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8870  					Type: "object",
  8871  					Properties: map[string]apiextensions.JSONSchemaProps{
  8872  						"value": {
  8873  							Type:     "array",
  8874  							MaxItems: int64ptr(10),
  8875  							Items: &apiextensions.JSONSchemaPropsOrArray{
  8876  								Schema: &apiextensions.JSONSchemaProps{
  8877  									Type:      "string",
  8878  									MaxLength: int64ptr(10),
  8879  								},
  8880  							},
  8881  							XValidations: apiextensions.ValidationRules{
  8882  								{Rule: "self == oldSelf"},
  8883  							},
  8884  						},
  8885  					},
  8886  				},
  8887  			},
  8888  		},
  8889  		{
  8890  			name: "forbid transition rule on element of list of type set",
  8891  			opts: validationOptions{requireStructuralSchema: true},
  8892  			input: apiextensions.CustomResourceValidation{
  8893  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8894  					Type: "object",
  8895  					Properties: map[string]apiextensions.JSONSchemaProps{
  8896  						"value": {
  8897  							Type:      "array",
  8898  							MaxItems:  int64ptr(10),
  8899  							XListType: strPtr("set"),
  8900  							Items: &apiextensions.JSONSchemaPropsOrArray{
  8901  								Schema: &apiextensions.JSONSchemaProps{
  8902  									Type:      "string",
  8903  									MaxLength: int64ptr(10),
  8904  									XValidations: apiextensions.ValidationRules{
  8905  										{Rule: "self == oldSelf"},
  8906  									},
  8907  								},
  8908  							},
  8909  						},
  8910  					},
  8911  				},
  8912  			},
  8913  			expectedErrors: []validationMatch{
  8914  				invalid("spec.validation.openAPIV3Schema.properties[value].items.x-kubernetes-validations[0].rule"),
  8915  			},
  8916  		},
  8917  		{
  8918  			name: "allow transition rule on list of type set",
  8919  			opts: validationOptions{requireStructuralSchema: true},
  8920  			input: apiextensions.CustomResourceValidation{
  8921  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8922  					Type: "object",
  8923  					Properties: map[string]apiextensions.JSONSchemaProps{
  8924  						"value": {
  8925  							Type:      "array",
  8926  							MaxItems:  int64ptr(10),
  8927  							XListType: strPtr("set"),
  8928  							Items: &apiextensions.JSONSchemaPropsOrArray{
  8929  								Schema: &apiextensions.JSONSchemaProps{
  8930  									Type:      "string",
  8931  									MaxLength: int64ptr(10),
  8932  								},
  8933  							},
  8934  							XValidations: apiextensions.ValidationRules{
  8935  								{Rule: "self == oldSelf"},
  8936  							},
  8937  						},
  8938  					},
  8939  				},
  8940  			},
  8941  		},
  8942  		{
  8943  			name: "allow transition rule on element of list of type map",
  8944  			opts: validationOptions{requireStructuralSchema: true},
  8945  			input: apiextensions.CustomResourceValidation{
  8946  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8947  					Type: "object",
  8948  					Properties: map[string]apiextensions.JSONSchemaProps{
  8949  						"value": {
  8950  							Type:         "array",
  8951  							XListType:    strPtr("map"),
  8952  							XListMapKeys: []string{"name"},
  8953  							Items: &apiextensions.JSONSchemaPropsOrArray{
  8954  								Schema: &apiextensions.JSONSchemaProps{
  8955  									Type: "object",
  8956  									XValidations: apiextensions.ValidationRules{
  8957  										{Rule: "self == oldSelf"},
  8958  									},
  8959  									Required: []string{"name"},
  8960  									Properties: map[string]apiextensions.JSONSchemaProps{
  8961  										"name": {Type: "string", MaxLength: int64ptr(5)},
  8962  									},
  8963  								},
  8964  							},
  8965  						},
  8966  					},
  8967  				},
  8968  			},
  8969  		},
  8970  		{
  8971  			name: "allow transition rule on list of type map",
  8972  			opts: validationOptions{requireStructuralSchema: true},
  8973  			input: apiextensions.CustomResourceValidation{
  8974  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  8975  					Type: "object",
  8976  					Properties: map[string]apiextensions.JSONSchemaProps{
  8977  						"value": {
  8978  							Type:         "array",
  8979  							MaxItems:     int64ptr(10),
  8980  							XListType:    strPtr("map"),
  8981  							XListMapKeys: []string{"name"},
  8982  							Items: &apiextensions.JSONSchemaPropsOrArray{
  8983  								Schema: &apiextensions.JSONSchemaProps{
  8984  									Type:     "object",
  8985  									Required: []string{"name"},
  8986  									Properties: map[string]apiextensions.JSONSchemaProps{
  8987  										"name": {Type: "string", MaxLength: int64ptr(5)},
  8988  									},
  8989  								},
  8990  							},
  8991  							XValidations: apiextensions.ValidationRules{
  8992  								{Rule: "self == oldSelf"},
  8993  							},
  8994  						},
  8995  					},
  8996  				},
  8997  			},
  8998  		},
  8999  		{
  9000  			name: "allow transition rule on element of map of type granular",
  9001  			opts: validationOptions{requireStructuralSchema: true},
  9002  			input: apiextensions.CustomResourceValidation{
  9003  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9004  					Type: "object",
  9005  					Properties: map[string]apiextensions.JSONSchemaProps{
  9006  						"value": {
  9007  							Type:     "object",
  9008  							XMapType: strPtr("granular"),
  9009  							Properties: map[string]apiextensions.JSONSchemaProps{
  9010  								"subfield": {
  9011  									Type:      "string",
  9012  									MaxLength: int64ptr(10),
  9013  									XValidations: apiextensions.ValidationRules{
  9014  										{Rule: "self == oldSelf"},
  9015  									},
  9016  								},
  9017  							},
  9018  						},
  9019  					},
  9020  				},
  9021  			},
  9022  		},
  9023  		{
  9024  			name: "forbid transition rule on element of map of unrecognized type",
  9025  			opts: validationOptions{requireStructuralSchema: true},
  9026  			input: apiextensions.CustomResourceValidation{
  9027  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9028  					Type: "object",
  9029  					Properties: map[string]apiextensions.JSONSchemaProps{
  9030  						"value": {
  9031  							Type:     "object",
  9032  							XMapType: strPtr("future"),
  9033  							Properties: map[string]apiextensions.JSONSchemaProps{
  9034  								"subfield": {
  9035  									Type:      "string",
  9036  									MaxLength: int64ptr(10),
  9037  									XValidations: apiextensions.ValidationRules{
  9038  										{Rule: "self == oldSelf"},
  9039  									},
  9040  								},
  9041  							},
  9042  						},
  9043  					},
  9044  				},
  9045  			},
  9046  			expectedErrors: []validationMatch{
  9047  				invalid("spec.validation.openAPIV3Schema.properties[value].properties[subfield].x-kubernetes-validations[0].rule"),
  9048  				unsupported("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-map-type"),
  9049  			},
  9050  		},
  9051  		{
  9052  			name: "allow transition rule on element of map defaulting to type granular",
  9053  			opts: validationOptions{requireStructuralSchema: true},
  9054  			input: apiextensions.CustomResourceValidation{
  9055  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9056  					Type: "object",
  9057  					Properties: map[string]apiextensions.JSONSchemaProps{
  9058  						"value": {
  9059  							Type: "object",
  9060  							Properties: map[string]apiextensions.JSONSchemaProps{
  9061  								"subfield": {
  9062  									Type:      "string",
  9063  									MaxLength: int64ptr(10),
  9064  									XValidations: apiextensions.ValidationRules{
  9065  										{Rule: "self == oldSelf"},
  9066  									},
  9067  								},
  9068  							},
  9069  						},
  9070  					},
  9071  				},
  9072  			},
  9073  		},
  9074  		{
  9075  			name: "allow transition rule on map of type granular",
  9076  			opts: validationOptions{requireStructuralSchema: true},
  9077  			input: apiextensions.CustomResourceValidation{
  9078  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9079  					Type: "object",
  9080  					Properties: map[string]apiextensions.JSONSchemaProps{
  9081  						"value": {
  9082  							Type:     "object",
  9083  							XMapType: strPtr("granular"),
  9084  							XValidations: apiextensions.ValidationRules{
  9085  								{Rule: "self == oldSelf"},
  9086  							},
  9087  						},
  9088  					},
  9089  				},
  9090  			},
  9091  		},
  9092  		{
  9093  			name: "allow transition rule on map defaulting to type granular",
  9094  			opts: validationOptions{requireStructuralSchema: true},
  9095  			input: apiextensions.CustomResourceValidation{
  9096  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9097  					Type: "object",
  9098  					Properties: map[string]apiextensions.JSONSchemaProps{
  9099  						"value": {
  9100  							Type: "object",
  9101  							XValidations: apiextensions.ValidationRules{
  9102  								{Rule: "self == oldSelf"},
  9103  							},
  9104  						},
  9105  					},
  9106  				},
  9107  			},
  9108  		},
  9109  		{
  9110  			name: "allow transition rule on element of map of type atomic",
  9111  			opts: validationOptions{requireStructuralSchema: true},
  9112  			input: apiextensions.CustomResourceValidation{
  9113  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9114  					Type: "object",
  9115  					Properties: map[string]apiextensions.JSONSchemaProps{
  9116  						"value": {
  9117  							Type:     "object",
  9118  							XMapType: strPtr("atomic"),
  9119  							Properties: map[string]apiextensions.JSONSchemaProps{
  9120  								"subfield": {
  9121  									Type: "object",
  9122  									XValidations: apiextensions.ValidationRules{
  9123  										{Rule: "self == oldSelf"},
  9124  									},
  9125  								},
  9126  							},
  9127  						},
  9128  					},
  9129  				},
  9130  			},
  9131  		},
  9132  		{
  9133  			name: "allow transition rule on map of type atomic",
  9134  			opts: validationOptions{requireStructuralSchema: true},
  9135  			input: apiextensions.CustomResourceValidation{
  9136  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9137  					Type: "object",
  9138  					Properties: map[string]apiextensions.JSONSchemaProps{
  9139  						"value": {
  9140  							Type:     "object",
  9141  							XMapType: strPtr("atomic"),
  9142  							XValidations: apiextensions.ValidationRules{
  9143  								{Rule: "self == oldSelf"},
  9144  							},
  9145  						},
  9146  					},
  9147  				},
  9148  			},
  9149  		},
  9150  		{
  9151  			name: "forbid double-nested rule with no limit set",
  9152  			opts: validationOptions{requireStructuralSchema: true},
  9153  			input: apiextensions.CustomResourceValidation{
  9154  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9155  					Type: "object",
  9156  					Properties: map[string]apiextensions.JSONSchemaProps{
  9157  						"value": {
  9158  							Type: "array",
  9159  							Items: &apiextensions.JSONSchemaPropsOrArray{
  9160  								Schema: &apiextensions.JSONSchemaProps{
  9161  									Type: "object",
  9162  									AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  9163  										Schema: &apiextensions.JSONSchemaProps{
  9164  											Type:     "object",
  9165  											Required: []string{"key"},
  9166  											Properties: map[string]apiextensions.JSONSchemaProps{
  9167  												"key": {Type: "string"},
  9168  											},
  9169  										},
  9170  									},
  9171  								},
  9172  							},
  9173  							XValidations: apiextensions.ValidationRules{
  9174  								{Rule: "self.all(x, x.all(y, x[y].key == x[y].key))"},
  9175  							},
  9176  						},
  9177  					},
  9178  				},
  9179  			},
  9180  			expectedErrors: []validationMatch{
  9181  				// exceeds per-rule limit and contributes to total limit being exceeded (1 error for each)
  9182  				forbidden("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].rule"),
  9183  				forbidden("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].rule"),
  9184  				// total limit is exceeded
  9185  				forbidden("spec.validation.openAPIV3Schema"),
  9186  			},
  9187  		},
  9188  		{
  9189  			name: "forbid double-nested rule with one limit set",
  9190  			opts: validationOptions{requireStructuralSchema: true},
  9191  			input: apiextensions.CustomResourceValidation{
  9192  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9193  					Type: "object",
  9194  					Properties: map[string]apiextensions.JSONSchemaProps{
  9195  						"value": {
  9196  							Type: "array",
  9197  							Items: &apiextensions.JSONSchemaPropsOrArray{
  9198  								Schema: &apiextensions.JSONSchemaProps{
  9199  									Type: "object",
  9200  									AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  9201  										Schema: &apiextensions.JSONSchemaProps{
  9202  											Type:     "object",
  9203  											Required: []string{"key"},
  9204  											Properties: map[string]apiextensions.JSONSchemaProps{
  9205  												"key": {Type: "string", MaxLength: int64ptr(10)},
  9206  											},
  9207  										},
  9208  									},
  9209  								},
  9210  							},
  9211  							XValidations: apiextensions.ValidationRules{
  9212  								{Rule: "self.all(x, x.all(y, x[y].key == x[y].key))"},
  9213  							},
  9214  						},
  9215  					},
  9216  				},
  9217  			},
  9218  			expectedErrors: []validationMatch{
  9219  				// exceeds per-rule limit and contributes to total limit being exceeded (1 error for each)
  9220  				forbidden("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].rule"),
  9221  				forbidden("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].rule"),
  9222  				// total limit is exceeded
  9223  				forbidden("spec.validation.openAPIV3Schema"),
  9224  			},
  9225  		},
  9226  		{
  9227  			name: "allow double-nested rule with three limits set",
  9228  			opts: validationOptions{requireStructuralSchema: true},
  9229  			input: apiextensions.CustomResourceValidation{
  9230  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9231  					Type: "object",
  9232  					Properties: map[string]apiextensions.JSONSchemaProps{
  9233  						"value": {
  9234  							Type:     "array",
  9235  							MaxItems: int64ptr(10),
  9236  							Items: &apiextensions.JSONSchemaPropsOrArray{
  9237  								Schema: &apiextensions.JSONSchemaProps{
  9238  									Type:          "object",
  9239  									MaxProperties: int64ptr(10),
  9240  									AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  9241  										Schema: &apiextensions.JSONSchemaProps{
  9242  											Type:     "object",
  9243  											Required: []string{"key"},
  9244  											Properties: map[string]apiextensions.JSONSchemaProps{
  9245  												"key": {Type: "string", MaxLength: int64ptr(10)},
  9246  											},
  9247  										},
  9248  									},
  9249  								},
  9250  							},
  9251  							XValidations: apiextensions.ValidationRules{
  9252  								{Rule: "self.all(x, x.all(y, x[y].key == x[y].key))"},
  9253  							},
  9254  						},
  9255  					},
  9256  				},
  9257  			},
  9258  			expectedErrors: []validationMatch{},
  9259  		},
  9260  		{
  9261  			name: "allow double-nested rule with one limit set on outermost array",
  9262  			opts: validationOptions{requireStructuralSchema: true},
  9263  			input: apiextensions.CustomResourceValidation{
  9264  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9265  					Type: "object",
  9266  					Properties: map[string]apiextensions.JSONSchemaProps{
  9267  						"value": {
  9268  							Type:     "array",
  9269  							MaxItems: int64ptr(4),
  9270  							Items: &apiextensions.JSONSchemaPropsOrArray{
  9271  								Schema: &apiextensions.JSONSchemaProps{
  9272  									Type: "object",
  9273  									AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  9274  										Schema: &apiextensions.JSONSchemaProps{
  9275  											Type:     "object",
  9276  											Required: []string{"key"},
  9277  											Properties: map[string]apiextensions.JSONSchemaProps{
  9278  												"key": {Type: "number"},
  9279  											},
  9280  										},
  9281  									},
  9282  								},
  9283  							},
  9284  							XValidations: apiextensions.ValidationRules{
  9285  								{Rule: "self.all(x, x.all(y, x[y].key == x[y].key))"},
  9286  							},
  9287  						},
  9288  					},
  9289  				},
  9290  			},
  9291  			expectedErrors: []validationMatch{},
  9292  		},
  9293  		{
  9294  			name: "check for cardinality of 1 under root object",
  9295  			opts: validationOptions{requireStructuralSchema: true},
  9296  			input: apiextensions.CustomResourceValidation{
  9297  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9298  					Type: "object",
  9299  					Properties: map[string]apiextensions.JSONSchemaProps{
  9300  						"value": {
  9301  							Type: "integer",
  9302  							XValidations: apiextensions.ValidationRules{
  9303  								{Rule: "self < 1024"},
  9304  							},
  9305  						},
  9306  					},
  9307  				},
  9308  			},
  9309  			expectedErrors: []validationMatch{},
  9310  		},
  9311  		{
  9312  			name: "forbid validation rules where cost total exceeds total limit",
  9313  			opts: validationOptions{requireStructuralSchema: true},
  9314  			input: apiextensions.CustomResourceValidation{
  9315  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9316  					Type: "object",
  9317  					Properties: map[string]apiextensions.JSONSchemaProps{
  9318  						"list": {
  9319  							Type:     "array",
  9320  							MaxItems: int64Ptr(100000),
  9321  							Items: &apiextensions.JSONSchemaPropsOrArray{
  9322  								Schema: &apiextensions.JSONSchemaProps{
  9323  									Type:      "string",
  9324  									MaxLength: int64Ptr(5000),
  9325  									XValidations: apiextensions.ValidationRules{
  9326  										{Rule: "self.contains('keyword')"},
  9327  									},
  9328  								},
  9329  							},
  9330  						},
  9331  						"map": {
  9332  							Type:          "object",
  9333  							MaxProperties: int64Ptr(1000),
  9334  							AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  9335  								Allows: true,
  9336  								Schema: &apiextensions.JSONSchemaProps{
  9337  									Type:      "string",
  9338  									MaxLength: int64Ptr(5000),
  9339  									XValidations: apiextensions.ValidationRules{
  9340  										{Rule: "self.contains('keyword')"},
  9341  									},
  9342  								},
  9343  							},
  9344  						},
  9345  						"field": { // include a validation rule that does not contribute to total limit being exceeded (i.e. it is less than 1% of the limit)
  9346  							Type: "integer",
  9347  							XValidations: apiextensions.ValidationRules{
  9348  								{Rule: "self > 50 && self < 100"},
  9349  							},
  9350  						},
  9351  					},
  9352  				},
  9353  			},
  9354  			expectedErrors: []validationMatch{
  9355  				// exceeds per-rule limit and contributes to total limit being exceeded (1 error for each)
  9356  				forbidden("spec.validation.openAPIV3Schema.properties[list].items.x-kubernetes-validations[0].rule"),
  9357  				forbidden("spec.validation.openAPIV3Schema.properties[list].items.x-kubernetes-validations[0].rule"),
  9358  				// contributes to total limit being exceeded, but does not exceed per-rule limit
  9359  				forbidden("spec.validation.openAPIV3Schema.properties[map].additionalProperties.x-kubernetes-validations[0].rule"),
  9360  				// total limit is exceeded
  9361  				forbidden("spec.validation.openAPIV3Schema"),
  9362  			},
  9363  		},
  9364  		{
  9365  			name: "skip CEL expression validation when OpenAPIv3 schema is an invalid structural schema",
  9366  			opts: validationOptions{requireStructuralSchema: true},
  9367  			input: apiextensions.CustomResourceValidation{
  9368  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9369  					Type: "object",
  9370  					// illegal to have both Properties and AdditionalProperties
  9371  					Properties: map[string]apiextensions.JSONSchemaProps{
  9372  						"field": {
  9373  							Type: "integer",
  9374  						},
  9375  					},
  9376  					AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  9377  						Schema: &apiextensions.JSONSchemaProps{
  9378  							Type: "string",
  9379  						},
  9380  					},
  9381  					XValidations: apiextensions.ValidationRules{
  9382  						{Rule: "self.invalidFieldName > 50"}, // invalid CEL rule
  9383  					},
  9384  				},
  9385  			},
  9386  			expectedErrors: []validationMatch{
  9387  				forbidden("spec.validation.openAPIV3Schema.additionalProperties"), // illegal to have both properties and additional properties
  9388  				forbidden("spec.validation.openAPIV3Schema.additionalProperties"), // structural schema rule: illegal to have additional properties at root
  9389  				// Error for invalid CEL rule is NOT expected here because CEL rules are not checked when the schema is invalid
  9390  			},
  9391  		},
  9392  		{
  9393  			name: "skip CEL expression validation when OpenAPIv3 schema is an invalid structural schema at level below",
  9394  			opts: validationOptions{requireStructuralSchema: true},
  9395  			input: apiextensions.CustomResourceValidation{
  9396  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9397  					Type: "object",
  9398  					Properties: map[string]apiextensions.JSONSchemaProps{
  9399  						"field": {
  9400  							Type: "object",
  9401  							// illegal to have both Properties and AdditionalProperties
  9402  							Properties: map[string]apiextensions.JSONSchemaProps{
  9403  								"field": {
  9404  									Type: "integer",
  9405  								},
  9406  							},
  9407  							AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  9408  								Schema: &apiextensions.JSONSchemaProps{
  9409  									Type: "string",
  9410  								},
  9411  							},
  9412  						},
  9413  					},
  9414  					XValidations: apiextensions.ValidationRules{
  9415  						{Rule: "self.invalidFieldName > 50"},
  9416  					},
  9417  				},
  9418  			},
  9419  			expectedErrors: []validationMatch{
  9420  				forbidden("spec.validation.openAPIV3Schema.properties[field].additionalProperties"),
  9421  			},
  9422  		},
  9423  		{
  9424  			// So long at the schema information accessible to the CEL expression is valid, the expression should be validated.
  9425  			name: "do not skip when OpenAPIv3 schema is an invalid structural schema in a separate part of the schema tree",
  9426  			opts: validationOptions{requireStructuralSchema: true},
  9427  			input: apiextensions.CustomResourceValidation{
  9428  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9429  					Type: "object",
  9430  					Properties: map[string]apiextensions.JSONSchemaProps{
  9431  						"a": {
  9432  							Type: "object",
  9433  							// illegal to have both Properties and AdditionalProperties
  9434  							Properties: map[string]apiextensions.JSONSchemaProps{
  9435  								"field": {
  9436  									Type: "integer",
  9437  								},
  9438  							},
  9439  							AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  9440  								Schema: &apiextensions.JSONSchemaProps{
  9441  									Type: "string",
  9442  								},
  9443  							},
  9444  						},
  9445  						"b": {
  9446  							Type: "object",
  9447  							Properties: map[string]apiextensions.JSONSchemaProps{
  9448  								"field": {
  9449  									Type: "integer",
  9450  								},
  9451  							},
  9452  							XValidations: apiextensions.ValidationRules{
  9453  								{Rule: "self.invalidFieldName > 50"},
  9454  							},
  9455  						},
  9456  					},
  9457  				},
  9458  			},
  9459  			expectedErrors: []validationMatch{
  9460  				forbidden("spec.validation.openAPIV3Schema.properties[a].additionalProperties"),
  9461  				invalid("spec.validation.openAPIV3Schema.properties[b].x-kubernetes-validations[0].rule"),
  9462  			},
  9463  		},
  9464  		{
  9465  			// So long at the schema information accessible to the CEL expression is valid, the expression should be validated.
  9466  			name: "do not skip CEL expression validation when OpenAPIv3 schema is an invalid structural schema at level above",
  9467  			opts: validationOptions{requireStructuralSchema: true},
  9468  			input: apiextensions.CustomResourceValidation{
  9469  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9470  					Type: "object",
  9471  					Properties: map[string]apiextensions.JSONSchemaProps{
  9472  						"a": {
  9473  							Type: "object",
  9474  							// illegal to have both Properties and AdditionalProperties
  9475  							Properties: map[string]apiextensions.JSONSchemaProps{
  9476  								"b": {
  9477  									Type: "integer",
  9478  									XValidations: apiextensions.ValidationRules{
  9479  										{Rule: "self == 'abc'"},
  9480  									},
  9481  								},
  9482  							},
  9483  							AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  9484  								Schema: &apiextensions.JSONSchemaProps{
  9485  									Type: "string",
  9486  								},
  9487  							},
  9488  						},
  9489  					},
  9490  				},
  9491  			},
  9492  			expectedErrors: []validationMatch{
  9493  				forbidden("spec.validation.openAPIV3Schema.properties[a].additionalProperties"),
  9494  				invalid("spec.validation.openAPIV3Schema.properties[a].properties[b].x-kubernetes-validations[0].rule"),
  9495  			},
  9496  		},
  9497  		{
  9498  			name: "x-kubernetes-validations rule validated for escaped property name",
  9499  			opts: validationOptions{requireStructuralSchema: true},
  9500  			input: apiextensions.CustomResourceValidation{
  9501  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9502  					Type: "object",
  9503  					Properties: map[string]apiextensions.JSONSchemaProps{
  9504  						"f/2": {
  9505  							Type: "string",
  9506  						},
  9507  					},
  9508  					XValidations: apiextensions.ValidationRules{
  9509  						{Rule: "self.f__slash__2 == 1"}, // invalid comparison of string and int
  9510  					},
  9511  				},
  9512  			},
  9513  			expectedErrors: []validationMatch{
  9514  				invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[0].rule"),
  9515  			},
  9516  		},
  9517  		{
  9518  			name: "x-kubernetes-validations rule validated under array items",
  9519  			opts: validationOptions{requireStructuralSchema: true},
  9520  			input: apiextensions.CustomResourceValidation{
  9521  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9522  					Type: "object",
  9523  					Properties: map[string]apiextensions.JSONSchemaProps{
  9524  						"a": {
  9525  							Type: "array",
  9526  							Items: &apiextensions.JSONSchemaPropsOrArray{
  9527  								Schema: &apiextensions.JSONSchemaProps{
  9528  									Type: "string",
  9529  									XValidations: apiextensions.ValidationRules{
  9530  										{Rule: "self == 1"}, // invalid comparison of string and int
  9531  									},
  9532  								},
  9533  							},
  9534  						},
  9535  					},
  9536  				},
  9537  			},
  9538  			expectedErrors: []validationMatch{
  9539  				invalid("spec.validation.openAPIV3Schema.properties[a].items.x-kubernetes-validations[0].rule"),
  9540  			},
  9541  		},
  9542  		{
  9543  			name: "x-kubernetes-validations rule validated under array items, parent has rule",
  9544  			opts: validationOptions{requireStructuralSchema: true},
  9545  			input: apiextensions.CustomResourceValidation{
  9546  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9547  					Type: "object",
  9548  					Properties: map[string]apiextensions.JSONSchemaProps{
  9549  						"a": {Type: "array",
  9550  							Items: &apiextensions.JSONSchemaPropsOrArray{
  9551  								Schema: &apiextensions.JSONSchemaProps{
  9552  									Type: "string",
  9553  									XValidations: apiextensions.ValidationRules{
  9554  										{Rule: "self == 1"}, // invalid comparison of string and int
  9555  									},
  9556  								},
  9557  							},
  9558  							XValidations: apiextensions.ValidationRules{
  9559  								{Rule: "1 == 1"},
  9560  							},
  9561  						},
  9562  					},
  9563  				},
  9564  			},
  9565  			expectedErrors: []validationMatch{
  9566  				invalid("spec.validation.openAPIV3Schema.properties[a].items.x-kubernetes-validations[0].rule"),
  9567  			},
  9568  		},
  9569  		{
  9570  			name: "x-kubernetes-validations rule validated under additionalProperties",
  9571  			opts: validationOptions{requireStructuralSchema: true},
  9572  			input: apiextensions.CustomResourceValidation{
  9573  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9574  					Type: "object",
  9575  					Properties: map[string]apiextensions.JSONSchemaProps{
  9576  						"a": {
  9577  							Type: "object",
  9578  							AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  9579  								Schema: &apiextensions.JSONSchemaProps{
  9580  									Type: "string",
  9581  									XValidations: apiextensions.ValidationRules{
  9582  										{Rule: "self == 1"}, // invalid comparison of string and int
  9583  									},
  9584  								},
  9585  							},
  9586  						},
  9587  					},
  9588  				},
  9589  			},
  9590  			expectedErrors: []validationMatch{
  9591  				invalid("spec.validation.openAPIV3Schema.properties[a].additionalProperties.x-kubernetes-validations[0].rule"),
  9592  			},
  9593  		},
  9594  		{
  9595  			name: "x-kubernetes-validations rule validated under additionalProperties, parent has rule",
  9596  			opts: validationOptions{requireStructuralSchema: true},
  9597  			input: apiextensions.CustomResourceValidation{
  9598  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9599  					Type: "object",
  9600  					Properties: map[string]apiextensions.JSONSchemaProps{
  9601  						"a": {
  9602  							Type: "object",
  9603  							AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
  9604  								Schema: &apiextensions.JSONSchemaProps{
  9605  									Type: "string",
  9606  									XValidations: apiextensions.ValidationRules{
  9607  										{Rule: "self == 1"}, // invalid comparison of string and int
  9608  									},
  9609  								},
  9610  							},
  9611  							XValidations: apiextensions.ValidationRules{
  9612  								{Rule: "1 == 1"},
  9613  							},
  9614  						},
  9615  					},
  9616  				},
  9617  			},
  9618  			expectedErrors: []validationMatch{
  9619  				invalid("spec.validation.openAPIV3Schema.properties[a].additionalProperties.x-kubernetes-validations[0].rule"),
  9620  			},
  9621  		},
  9622  		{
  9623  			name: "x-kubernetes-validations rule validated under unescaped property name",
  9624  			opts: validationOptions{requireStructuralSchema: true},
  9625  			input: apiextensions.CustomResourceValidation{
  9626  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9627  					Type: "object",
  9628  					Properties: map[string]apiextensions.JSONSchemaProps{
  9629  						"f": {
  9630  							Type: "string",
  9631  							XValidations: apiextensions.ValidationRules{
  9632  								{Rule: "self == 1"}, // invalid comparison of string and int
  9633  							},
  9634  						},
  9635  					},
  9636  				},
  9637  			},
  9638  			expectedErrors: []validationMatch{
  9639  				invalid("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].rule"),
  9640  			},
  9641  		},
  9642  		{
  9643  			name: "x-kubernetes-validations rule validated under unescaped property name, parent has rule",
  9644  			opts: validationOptions{requireStructuralSchema: true},
  9645  			input: apiextensions.CustomResourceValidation{
  9646  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9647  					Type: "object",
  9648  					Properties: map[string]apiextensions.JSONSchemaProps{
  9649  						"f": {
  9650  							Type: "string",
  9651  							XValidations: apiextensions.ValidationRules{
  9652  								{Rule: "self == 1"}, // invalid comparison of string and int
  9653  							},
  9654  						},
  9655  					},
  9656  					XValidations: apiextensions.ValidationRules{
  9657  						{Rule: "1 == 1"},
  9658  					},
  9659  				},
  9660  			},
  9661  			expectedErrors: []validationMatch{
  9662  				invalid("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].rule"),
  9663  			},
  9664  		},
  9665  		{
  9666  			name: "x-kubernetes-validations rule validated under escaped property name",
  9667  			opts: validationOptions{requireStructuralSchema: true},
  9668  			input: apiextensions.CustomResourceValidation{
  9669  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9670  					Type: "object",
  9671  					Properties: map[string]apiextensions.JSONSchemaProps{
  9672  						"f/2": {
  9673  							Type: "string",
  9674  							XValidations: apiextensions.ValidationRules{
  9675  								{Rule: "self == 1"}, // invalid comparison of string and int
  9676  							},
  9677  						},
  9678  					},
  9679  				},
  9680  			},
  9681  			expectedErrors: []validationMatch{
  9682  				invalid("spec.validation.openAPIV3Schema.properties[f/2].x-kubernetes-validations[0].rule"),
  9683  			},
  9684  		},
  9685  		{
  9686  			name: "x-kubernetes-validations rule validated under escaped property name, parent has rule",
  9687  			opts: validationOptions{requireStructuralSchema: true},
  9688  			input: apiextensions.CustomResourceValidation{
  9689  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9690  					Type: "object",
  9691  					Properties: map[string]apiextensions.JSONSchemaProps{
  9692  						"f/2": {
  9693  							Type: "string",
  9694  							XValidations: apiextensions.ValidationRules{
  9695  								{Rule: "self == 1"}, // invalid comparison of string and int
  9696  							},
  9697  						},
  9698  					},
  9699  					XValidations: apiextensions.ValidationRules{
  9700  						{Rule: "1 == 1"},
  9701  					},
  9702  				},
  9703  			},
  9704  			expectedErrors: []validationMatch{
  9705  				invalid("spec.validation.openAPIV3Schema.properties[f/2].x-kubernetes-validations[0].rule"),
  9706  			},
  9707  		},
  9708  		{
  9709  			name: "x-kubernetes-validations rule validated under unescapable property name",
  9710  			opts: validationOptions{requireStructuralSchema: true},
  9711  			input: apiextensions.CustomResourceValidation{
  9712  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9713  					Type: "object",
  9714  					Properties: map[string]apiextensions.JSONSchemaProps{
  9715  						"f@2": {
  9716  							Type: "string",
  9717  							XValidations: apiextensions.ValidationRules{
  9718  								{Rule: "self == 1"}, // invalid comparison of string and int
  9719  							},
  9720  						},
  9721  					},
  9722  				},
  9723  			},
  9724  			expectedErrors: []validationMatch{
  9725  				invalid("spec.validation.openAPIV3Schema.properties[f@2].x-kubernetes-validations[0].rule"),
  9726  			},
  9727  		},
  9728  		{
  9729  			name: "x-kubernetes-validations rule validated under unescapable property name, parent has rule",
  9730  			opts: validationOptions{requireStructuralSchema: true},
  9731  			input: apiextensions.CustomResourceValidation{
  9732  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9733  					Type: "object",
  9734  					Properties: map[string]apiextensions.JSONSchemaProps{
  9735  						"f@2": {
  9736  							Type: "string",
  9737  							XValidations: apiextensions.ValidationRules{
  9738  								{Rule: "self == 1"}, // invalid comparison of string and int
  9739  							},
  9740  						},
  9741  					},
  9742  					XValidations: apiextensions.ValidationRules{
  9743  						{Rule: "1 == 1"},
  9744  					},
  9745  				},
  9746  			},
  9747  			expectedErrors: []validationMatch{
  9748  				invalid("spec.validation.openAPIV3Schema.properties[f@2].x-kubernetes-validations[0].rule"),
  9749  			},
  9750  		},
  9751  		{
  9752  			name: "x-kubernetes-validations rule with messageExpression",
  9753  			opts: validationOptions{requireStructuralSchema: true},
  9754  			input: apiextensions.CustomResourceValidation{
  9755  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9756  					Type: "object",
  9757  					Properties: map[string]apiextensions.JSONSchemaProps{
  9758  						"f": {
  9759  							Type: "string",
  9760  							XValidations: apiextensions.ValidationRules{
  9761  								{
  9762  									Rule:              "self == \"string value\"",
  9763  									MessageExpression: `self + " should be \"string value\""`,
  9764  								},
  9765  							},
  9766  						},
  9767  					},
  9768  				},
  9769  			},
  9770  			expectedErrors: []validationMatch{},
  9771  		},
  9772  		{
  9773  			name: "x-kubernetes-validations rule allows both message and messageExpression",
  9774  			opts: validationOptions{requireStructuralSchema: true},
  9775  			input: apiextensions.CustomResourceValidation{
  9776  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9777  					Type: "object",
  9778  					Properties: map[string]apiextensions.JSONSchemaProps{
  9779  						"f": {
  9780  							Type: "string",
  9781  							XValidations: apiextensions.ValidationRules{
  9782  								{
  9783  									Rule:              "self == \"string value\"",
  9784  									Message:           `string should be set to "string value"`,
  9785  									MessageExpression: `self + " should be \"string value\""`,
  9786  								},
  9787  							},
  9788  						},
  9789  					},
  9790  				},
  9791  			},
  9792  			expectedErrors: []validationMatch{},
  9793  		},
  9794  		{
  9795  			name: "x-kubernetes-validations rule invalidated by messageExpression syntax error",
  9796  			opts: validationOptions{requireStructuralSchema: true},
  9797  			input: apiextensions.CustomResourceValidation{
  9798  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9799  					Type: "object",
  9800  					Properties: map[string]apiextensions.JSONSchemaProps{
  9801  						"f": {
  9802  							Type: "string",
  9803  							XValidations: apiextensions.ValidationRules{
  9804  								{
  9805  									Rule:              "self == \"string value\"",
  9806  									MessageExpression: `self + " `,
  9807  								},
  9808  							},
  9809  						},
  9810  					},
  9811  				},
  9812  			},
  9813  			expectedErrors: []validationMatch{
  9814  				invalid("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].messageExpression"),
  9815  			},
  9816  		},
  9817  		{
  9818  			name: "x-kubernetes-validations rule invalidated by messageExpression not returning a string",
  9819  			opts: validationOptions{requireStructuralSchema: true},
  9820  			input: apiextensions.CustomResourceValidation{
  9821  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9822  					Type: "object",
  9823  					Properties: map[string]apiextensions.JSONSchemaProps{
  9824  						"f": {
  9825  							Type: "string",
  9826  							XValidations: apiextensions.ValidationRules{
  9827  								{
  9828  									Rule:              "self == \"string value\"",
  9829  									MessageExpression: `256`,
  9830  								},
  9831  							},
  9832  						},
  9833  					},
  9834  				},
  9835  			},
  9836  			expectedErrors: []validationMatch{
  9837  				invalid("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].messageExpression"),
  9838  			},
  9839  		},
  9840  		{
  9841  			name: "x-kubernetes-validations rule invalidated by messageExpression exceeding per-expression estimated cost limit",
  9842  			opts: validationOptions{requireStructuralSchema: true},
  9843  			input: apiextensions.CustomResourceValidation{
  9844  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9845  					Type: "object",
  9846  					Properties: map[string]apiextensions.JSONSchemaProps{
  9847  						"f": {
  9848  							Type: "array",
  9849  							Items: &apiextensions.JSONSchemaPropsOrArray{
  9850  								Schema: &apiextensions.JSONSchemaProps{
  9851  									Type: "string",
  9852  								},
  9853  							},
  9854  							XValidations: apiextensions.ValidationRules{
  9855  								{
  9856  									Rule:              "true",
  9857  									MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
  9858  								},
  9859  							},
  9860  						},
  9861  					},
  9862  				},
  9863  			},
  9864  			expectedErrors: []validationMatch{
  9865  				// forbidden due to messageExpression exceeding per-expression cost limit
  9866  				forbidden("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].messageExpression"),
  9867  			},
  9868  		},
  9869  		{
  9870  			name: "x-kubernetes-validations rule with lowerAscii check should be within estimated cost limit",
  9871  			opts: validationOptions{requireStructuralSchema: true},
  9872  			input: apiextensions.CustomResourceValidation{
  9873  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9874  					Type: "object",
  9875  					Properties: map[string]apiextensions.JSONSchemaProps{
  9876  						"f": {
  9877  							Type:     "array",
  9878  							MaxItems: pointer.Int64(5),
  9879  							Items: &apiextensions.JSONSchemaPropsOrArray{
  9880  								Schema: &apiextensions.JSONSchemaProps{
  9881  									Type:      "string",
  9882  									MaxLength: pointer.Int64(5),
  9883  								},
  9884  							},
  9885  							XValidations: apiextensions.ValidationRules{
  9886  								{
  9887  									Rule: "self.all(x, self.exists_one(y, x.lowerAscii() == y.lowerAscii()))",
  9888  								},
  9889  							},
  9890  						},
  9891  					},
  9892  				},
  9893  			},
  9894  		},
  9895  		{
  9896  			name: "x-kubernetes-validations rule invalidated by messageExpression exceeding per-CRD estimated cost limit",
  9897  			opts: validationOptions{requireStructuralSchema: true},
  9898  			input: apiextensions.CustomResourceValidation{
  9899  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9900  					Type: "object",
  9901  					Properties: map[string]apiextensions.JSONSchemaProps{
  9902  						"f": {
  9903  							Type: "array",
  9904  							Items: &apiextensions.JSONSchemaPropsOrArray{
  9905  								Schema: &apiextensions.JSONSchemaProps{
  9906  									Type: "string",
  9907  								},
  9908  							},
  9909  							XValidations: apiextensions.ValidationRules{
  9910  								{
  9911  									Rule:              "true",
  9912  									MessageExpression: `string(self[0]) + string(self[1]) + string(self[2])`,
  9913  								},
  9914  							},
  9915  						},
  9916  					},
  9917  				},
  9918  			},
  9919  			expectedErrors: []validationMatch{
  9920  				// forbidden due to per-CRD cost limit being exceeded
  9921  				forbidden("spec.validation.openAPIV3Schema"),
  9922  				// forbidden due to messageExpression exceeding per-expression cost limit
  9923  				forbidden("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].messageExpression"),
  9924  				// additional message indicated messageExpression's contribution to exceeding the per-CRD cost limit
  9925  				forbidden("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].messageExpression"),
  9926  			},
  9927  		},
  9928  		{
  9929  			name: "x-kubernetes-validations rule invalidated by messageExpression being only empty spaces",
  9930  			opts: validationOptions{requireStructuralSchema: true},
  9931  			input: apiextensions.CustomResourceValidation{
  9932  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9933  					Type: "object",
  9934  					Properties: map[string]apiextensions.JSONSchemaProps{
  9935  						"f": {
  9936  							Type: "string",
  9937  							XValidations: apiextensions.ValidationRules{
  9938  								{
  9939  									Rule:              "self == \"string value\"",
  9940  									MessageExpression: `     `,
  9941  								},
  9942  							},
  9943  						},
  9944  					},
  9945  				},
  9946  			},
  9947  			expectedErrors: []validationMatch{
  9948  				required("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].messageExpression"),
  9949  			},
  9950  		},
  9951  		{
  9952  			name: "forbid transition rule on element of list of type atomic when optionalOldSelf is set",
  9953  			opts: validationOptions{requireStructuralSchema: true},
  9954  			input: apiextensions.CustomResourceValidation{
  9955  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9956  					Type: "object",
  9957  					Properties: map[string]apiextensions.JSONSchemaProps{
  9958  						"value": {
  9959  							Type:      "array",
  9960  							XListType: strPtr("atomic"),
  9961  							Items: &apiextensions.JSONSchemaPropsOrArray{
  9962  								Schema: &apiextensions.JSONSchemaProps{
  9963  									Type:      "string",
  9964  									MaxLength: int64ptr(10),
  9965  									XValidations: apiextensions.ValidationRules{
  9966  										{Rule: `self == oldSelf.orValue("")`, OptionalOldSelf: ptr.To(true)},
  9967  									},
  9968  								},
  9969  							},
  9970  						},
  9971  					},
  9972  				},
  9973  			},
  9974  			expectedErrors: []validationMatch{
  9975  				invalid("spec.validation.openAPIV3Schema.properties[value].items.x-kubernetes-validations[0].rule"),
  9976  			},
  9977  		},
  9978  		{
  9979  			name: "forbid transition rule on element of list defaulting to type atomic when optionalOldSelf is set",
  9980  			opts: validationOptions{requireStructuralSchema: true},
  9981  			input: apiextensions.CustomResourceValidation{
  9982  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  9983  					Type: "object",
  9984  					Properties: map[string]apiextensions.JSONSchemaProps{
  9985  						"value": {
  9986  							Type: "array",
  9987  							Items: &apiextensions.JSONSchemaPropsOrArray{
  9988  								Schema: &apiextensions.JSONSchemaProps{
  9989  									Type:      "string",
  9990  									MaxLength: int64ptr(10),
  9991  									XValidations: apiextensions.ValidationRules{
  9992  										{Rule: `self == oldSelf.orValue("")`, OptionalOldSelf: ptr.To(true)},
  9993  									},
  9994  								},
  9995  							},
  9996  						},
  9997  					},
  9998  				},
  9999  			},
 10000  			expectedErrors: []validationMatch{
 10001  				invalid("spec.validation.openAPIV3Schema.properties[value].items.x-kubernetes-validations[0].rule"),
 10002  			},
 10003  		},
 10004  		{
 10005  			name: "forbid transition rule on element of list of type set when optionalOldSelf is set",
 10006  			opts: validationOptions{requireStructuralSchema: true},
 10007  			input: apiextensions.CustomResourceValidation{
 10008  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
 10009  					Type: "object",
 10010  					Properties: map[string]apiextensions.JSONSchemaProps{
 10011  						"value": {
 10012  							Type:      "array",
 10013  							MaxItems:  int64ptr(10),
 10014  							XListType: strPtr("set"),
 10015  							Items: &apiextensions.JSONSchemaPropsOrArray{
 10016  								Schema: &apiextensions.JSONSchemaProps{
 10017  									Type:      "string",
 10018  									MaxLength: int64ptr(10),
 10019  									XValidations: apiextensions.ValidationRules{
 10020  										{Rule: `self == oldSelf.orValue("")`, OptionalOldSelf: ptr.To(true)},
 10021  									},
 10022  								},
 10023  							},
 10024  						},
 10025  					},
 10026  				},
 10027  			},
 10028  			expectedErrors: []validationMatch{
 10029  				invalid("spec.validation.openAPIV3Schema.properties[value].items.x-kubernetes-validations[0].rule"),
 10030  			},
 10031  		},
 10032  		{
 10033  			name: "forbid transition rule on element of map of unrecognized type when optionalOldSelf is set",
 10034  			opts: validationOptions{requireStructuralSchema: true},
 10035  			input: apiextensions.CustomResourceValidation{
 10036  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
 10037  					Type: "object",
 10038  					Properties: map[string]apiextensions.JSONSchemaProps{
 10039  						"value": {
 10040  							Type:     "object",
 10041  							XMapType: strPtr("future"),
 10042  							Properties: map[string]apiextensions.JSONSchemaProps{
 10043  								"subfield": {
 10044  									Type:      "string",
 10045  									MaxLength: int64ptr(10),
 10046  									XValidations: apiextensions.ValidationRules{
 10047  										{Rule: `self == oldSelf.orValue("")`, OptionalOldSelf: ptr.To(true)},
 10048  									},
 10049  								},
 10050  							},
 10051  						},
 10052  					},
 10053  				},
 10054  			},
 10055  			expectedErrors: []validationMatch{
 10056  				invalid("spec.validation.openAPIV3Schema.properties[value].properties[subfield].x-kubernetes-validations[0].rule"),
 10057  				unsupported("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-map-type"),
 10058  			},
 10059  		},
 10060  		{
 10061  			name: "forbid setting optionalOldSelf to true if oldSelf is not used",
 10062  			opts: validationOptions{requireStructuralSchema: true},
 10063  			input: apiextensions.CustomResourceValidation{
 10064  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
 10065  					Type: "object",
 10066  					Properties: map[string]apiextensions.JSONSchemaProps{
 10067  						"value": {
 10068  							Type:      "string",
 10069  							MaxLength: int64ptr(10),
 10070  							XValidations: apiextensions.ValidationRules{
 10071  								{Rule: `self == "foo"`, OptionalOldSelf: ptr.To(true)},
 10072  							},
 10073  						},
 10074  					},
 10075  				},
 10076  			},
 10077  			expectedErrors: []validationMatch{
 10078  				invalid("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].optionalOldSelf"),
 10079  			},
 10080  		},
 10081  		{
 10082  			name: "forbid setting optionalOldSelf to false if oldSelf is not used",
 10083  			opts: validationOptions{requireStructuralSchema: true},
 10084  			input: apiextensions.CustomResourceValidation{
 10085  				OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
 10086  					Type: "object",
 10087  					Properties: map[string]apiextensions.JSONSchemaProps{
 10088  						"value": {
 10089  							Type:      "string",
 10090  							MaxLength: int64ptr(10),
 10091  							XValidations: apiextensions.ValidationRules{
 10092  								{Rule: `self == "foo"`, OptionalOldSelf: ptr.To(false)},
 10093  							},
 10094  						},
 10095  					},
 10096  				},
 10097  			},
 10098  			expectedErrors: []validationMatch{
 10099  				invalid("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].optionalOldSelf"),
 10100  			},
 10101  		},
 10102  	}
 10103  	for _, tt := range tests {
 10104  		t.Run(tt.name, func(t *testing.T) {
 10105  			ctx := context.TODO()
 10106  			if tt.opts.celEnvironmentSet == nil {
 10107  				tt.opts.celEnvironmentSet = environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())
 10108  			}
 10109  			got := validateCustomResourceDefinitionValidation(ctx, &tt.input, tt.statusEnabled, tt.opts, field.NewPath("spec", "validation"))
 10110  
 10111  			seenErrs := make([]bool, len(got))
 10112  
 10113  			for _, expectedError := range tt.expectedErrors {
 10114  				found := false
 10115  				for i, err := range got {
 10116  					if expectedError.matches(err) && !seenErrs[i] {
 10117  						found = true
 10118  						seenErrs[i] = true
 10119  						break
 10120  					}
 10121  				}
 10122  
 10123  				if !found {
 10124  					t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), got)
 10125  				}
 10126  			}
 10127  
 10128  			for i, seen := range seenErrs {
 10129  				if !seen {
 10130  					t.Errorf("unexpected error: %v", got[i])
 10131  				}
 10132  			}
 10133  		})
 10134  	}
 10135  }
 10136  
 10137  func TestSchemaHasDefaults(t *testing.T) {
 10138  	scheme := runtime.NewScheme()
 10139  	codecs := serializer.NewCodecFactory(scheme)
 10140  	if err := apiextensions.AddToScheme(scheme); err != nil {
 10141  		t.Fatal(err)
 10142  	}
 10143  
 10144  	seed := rand.Int63()
 10145  	t.Logf("seed: %d", seed)
 10146  	fuzzerFuncs := fuzzer.MergeFuzzerFuncs(apiextensionsfuzzer.Funcs)
 10147  	f := fuzzer.FuzzerFor(fuzzerFuncs, rand.NewSource(seed), codecs)
 10148  
 10149  	for i := 0; i < 10000; i++ {
 10150  		// fuzz internal types
 10151  		schema := &apiextensions.JSONSchemaProps{}
 10152  		f.Fuzz(schema)
 10153  
 10154  		v1beta1Schema := &apiextensionsv1beta1.JSONSchemaProps{}
 10155  		if err := apiextensionsv1beta1.Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(schema, v1beta1Schema, nil); err != nil {
 10156  			t.Fatal(err)
 10157  		}
 10158  
 10159  		bs, err := json.Marshal(v1beta1Schema)
 10160  		if err != nil {
 10161  			t.Fatal(err)
 10162  		}
 10163  
 10164  		expected := strings.Contains(strings.Replace(string(bs), `"default":null`, `"deleted":null`, -1), `"default":`)
 10165  		if got := schemaHasDefaults(schema); got != expected {
 10166  			t.Errorf("expected %v, got %v for: %s", expected, got, string(bs))
 10167  		}
 10168  	}
 10169  }
 10170  
 10171  func TestValidateCustomResourceDefinitionStoredVersions(t *testing.T) {
 10172  	tests := []struct {
 10173  		name           string
 10174  		versions       []string
 10175  		storageVersion string
 10176  		storedVersions []string
 10177  		errors         []validationMatch
 10178  	}{
 10179  		{
 10180  			name:           "one version",
 10181  			versions:       []string{"v1"},
 10182  			storageVersion: "v1",
 10183  			storedVersions: []string{"v1"},
 10184  		},
 10185  		{
 10186  			name:           "no stored version",
 10187  			versions:       []string{"v1"},
 10188  			storageVersion: "v1",
 10189  			storedVersions: []string{},
 10190  			errors: []validationMatch{
 10191  				invalid("status", "storedVersions").contains("Invalid value: []string{}: must have at least one stored version"),
 10192  			},
 10193  		},
 10194  		{
 10195  			name:           "many versions",
 10196  			versions:       []string{"v1alpha", "v1beta1", "v1"},
 10197  			storageVersion: "v1",
 10198  			storedVersions: []string{"v1alpha", "v1"},
 10199  		},
 10200  		{
 10201  			name:           "missing stored versions",
 10202  			versions:       []string{"v1beta1", "v1"},
 10203  			storageVersion: "v1",
 10204  			storedVersions: []string{"v1alpha", "v1beta1", "v1"},
 10205  			errors: []validationMatch{
 10206  				invalidIndex(0, "status", "storedVersions").contains("Invalid value: \"v1alpha\": must appear in spec.versions"),
 10207  			},
 10208  		},
 10209  		{
 10210  			name:           "missing storage versions",
 10211  			versions:       []string{"v1alpha", "v1beta1", "v1"},
 10212  			storageVersion: "v1",
 10213  			storedVersions: []string{"v1alpha", "v1beta1"},
 10214  			errors: []validationMatch{
 10215  				invalid("status", "storedVersions").contains("Invalid value: []string{\"v1alpha\", \"v1beta1\"}: must have the storage version v1"),
 10216  			},
 10217  		},
 10218  	}
 10219  
 10220  	for _, tc := range tests {
 10221  		crd := &apiextensions.CustomResourceDefinition{
 10222  			ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
 10223  			Spec: apiextensions.CustomResourceDefinitionSpec{
 10224  				Group: "group.com",
 10225  				Scope: "Cluster",
 10226  				Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
 10227  			},
 10228  			Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: tc.storedVersions},
 10229  		}
 10230  		for _, version := range tc.versions {
 10231  			v := apiextensions.CustomResourceDefinitionVersion{Name: version}
 10232  			if tc.storageVersion == version {
 10233  				v.Storage = true
 10234  			}
 10235  			crd.Spec.Versions = append(crd.Spec.Versions, v)
 10236  		}
 10237  
 10238  		t.Run(tc.name, func(t *testing.T) {
 10239  			errs := ValidateCustomResourceDefinitionStoredVersions(crd.Status.StoredVersions, crd.Spec.Versions, field.NewPath("status", "storedVersions"))
 10240  			seenErrs := make([]bool, len(errs))
 10241  
 10242  			for _, expectedError := range tc.errors {
 10243  				found := false
 10244  				for i, err := range errs {
 10245  					if expectedError.matches(err) && !seenErrs[i] {
 10246  						found = true
 10247  						seenErrs[i] = true
 10248  						break
 10249  					}
 10250  				}
 10251  
 10252  				if !found {
 10253  					t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
 10254  				}
 10255  			}
 10256  			for i, seen := range seenErrs {
 10257  				if !seen {
 10258  					t.Errorf("unexpected error: %v", errs[i])
 10259  				}
 10260  			}
 10261  		})
 10262  	}
 10263  }
 10264  
 10265  func BenchmarkSchemaHas(b *testing.B) {
 10266  	scheme := runtime.NewScheme()
 10267  	codecs := serializer.NewCodecFactory(scheme)
 10268  	if err := apiextensions.AddToScheme(scheme); err != nil {
 10269  		b.Fatal(err)
 10270  	}
 10271  	fuzzerFuncs := fuzzer.MergeFuzzerFuncs(apiextensionsfuzzer.Funcs)
 10272  	seed := int64(5577006791947779410)
 10273  	f := fuzzer.FuzzerFor(fuzzerFuncs, rand.NewSource(seed), codecs)
 10274  	// fuzz internal types
 10275  	schema := &apiextensions.JSONSchemaProps{}
 10276  	f.NilChance(0).NumElements(10, 10).MaxDepth(10).Fuzz(schema)
 10277  
 10278  	b.ReportAllocs()
 10279  	b.ResetTimer()
 10280  	for i := 0; i < b.N; i++ {
 10281  		if SchemaHas(schema, func(_ *apiextensions.JSONSchemaProps) bool {
 10282  			return false
 10283  		}) {
 10284  			b.Errorf("Function returned true")
 10285  		}
 10286  	}
 10287  }
 10288  
 10289  var example = apiextensions.JSON(`"This is an example"`)
 10290  
 10291  var validValidationSchema = &apiextensions.JSONSchemaProps{
 10292  	Description:      "This is a description",
 10293  	Type:             "object",
 10294  	Format:           "date-time",
 10295  	Title:            "This is a title",
 10296  	Maximum:          float64Ptr(10),
 10297  	ExclusiveMaximum: true,
 10298  	Minimum:          float64Ptr(5),
 10299  	ExclusiveMinimum: true,
 10300  	MaxLength:        int64Ptr(10),
 10301  	MinLength:        int64Ptr(5),
 10302  	Pattern:          "^[a-z]$",
 10303  	MaxItems:         int64Ptr(10),
 10304  	MinItems:         int64Ptr(5),
 10305  	MultipleOf:       float64Ptr(3),
 10306  	Required:         []string{"spec", "status"},
 10307  	Properties: map[string]apiextensions.JSONSchemaProps{
 10308  		"spec": {
 10309  			Type: "object",
 10310  			Items: &apiextensions.JSONSchemaPropsOrArray{
 10311  				Schema: &apiextensions.JSONSchemaProps{
 10312  					Description: "This is a schema nested under Items",
 10313  					Type:        "string",
 10314  				},
 10315  			},
 10316  		},
 10317  		"status": {
 10318  			Type: "object",
 10319  		},
 10320  	},
 10321  	ExternalDocs: &apiextensions.ExternalDocumentation{
 10322  		Description: "This is an external documentation description",
 10323  	},
 10324  	Example: &example,
 10325  }
 10326  
 10327  var validUnstructuralValidationSchema = &apiextensions.JSONSchemaProps{
 10328  	Description:      "This is a description",
 10329  	Type:             "object",
 10330  	Format:           "date-time",
 10331  	Title:            "This is a title",
 10332  	Maximum:          float64Ptr(10),
 10333  	ExclusiveMaximum: true,
 10334  	Minimum:          float64Ptr(5),
 10335  	ExclusiveMinimum: true,
 10336  	MaxLength:        int64Ptr(10),
 10337  	MinLength:        int64Ptr(5),
 10338  	Pattern:          "^[a-z]$",
 10339  	MaxItems:         int64Ptr(10),
 10340  	MinItems:         int64Ptr(5),
 10341  	MultipleOf:       float64Ptr(3),
 10342  	Required:         []string{"spec", "status"},
 10343  	Items: &apiextensions.JSONSchemaPropsOrArray{
 10344  		Schema: &apiextensions.JSONSchemaProps{
 10345  			Description: "This is a schema nested under Items",
 10346  		},
 10347  	},
 10348  	Properties: map[string]apiextensions.JSONSchemaProps{
 10349  		"spec":   {},
 10350  		"status": {},
 10351  	},
 10352  	ExternalDocs: &apiextensions.ExternalDocumentation{
 10353  		Description: "This is an external documentation description",
 10354  	},
 10355  	Example: &example,
 10356  }
 10357  
 10358  func float64Ptr(f float64) *float64 {
 10359  	return &f
 10360  }
 10361  
 10362  func int64Ptr(f int64) *int64 {
 10363  	return &f
 10364  }
 10365  
 10366  func jsonPtr(x interface{}) *apiextensions.JSON {
 10367  	ret := apiextensions.JSON(x)
 10368  	return &ret
 10369  }
 10370  
 10371  func jsonSlice(l ...interface{}) []apiextensions.JSON {
 10372  	if len(l) == 0 {
 10373  		return nil
 10374  	}
 10375  	ret := make([]apiextensions.JSON, 0, len(l))
 10376  	for _, x := range l {
 10377  		ret = append(ret, x)
 10378  	}
 10379  	return ret
 10380  }
 10381  
 10382  func Test_validateDeprecationWarning(t *testing.T) {
 10383  	tests := []struct {
 10384  		name string
 10385  
 10386  		deprecated bool
 10387  		warning    *string
 10388  
 10389  		want []string
 10390  	}{
 10391  		{
 10392  			name:       "not deprecated, nil warning",
 10393  			deprecated: false,
 10394  			warning:    nil,
 10395  			want:       nil,
 10396  		},
 10397  
 10398  		{
 10399  			name:       "not deprecated, empty warning",
 10400  			deprecated: false,
 10401  			warning:    pointer.StringPtr(""),
 10402  			want:       []string{"can only be set for deprecated versions"},
 10403  		},
 10404  		{
 10405  			name:       "not deprecated, set warning",
 10406  			deprecated: false,
 10407  			warning:    pointer.StringPtr("foo"),
 10408  			want:       []string{"can only be set for deprecated versions"},
 10409  		},
 10410  
 10411  		{
 10412  			name:       "utf-8",
 10413  			deprecated: true,
 10414  			warning:    pointer.StringPtr("Iñtërnâtiônàlizætiøn,💝🐹🌇⛔"),
 10415  			want:       nil,
 10416  		},
 10417  		{
 10418  			name:       "long warning",
 10419  			deprecated: true,
 10420  			warning:    pointer.StringPtr(strings.Repeat("x", 256)),
 10421  			want:       nil,
 10422  		},
 10423  
 10424  		{
 10425  			name:       "too long warning",
 10426  			deprecated: true,
 10427  			warning:    pointer.StringPtr(strings.Repeat("x", 257)),
 10428  			want:       []string{"must be <= 256 characters long"},
 10429  		},
 10430  		{
 10431  			name:       "newline",
 10432  			deprecated: true,
 10433  			warning:    pointer.StringPtr("Test message\nfoo"),
 10434  			want:       []string{"must only contain printable UTF-8 characters; non-printable character found at index 12"},
 10435  		},
 10436  		{
 10437  			name:       "non-printable character",
 10438  			deprecated: true,
 10439  			warning:    pointer.StringPtr("Test message\u0008"),
 10440  			want:       []string{"must only contain printable UTF-8 characters; non-printable character found at index 12"},
 10441  		},
 10442  		{
 10443  			name:       "null character",
 10444  			deprecated: true,
 10445  			warning:    pointer.StringPtr("Test message\u0000"),
 10446  			want:       []string{"must only contain printable UTF-8 characters; non-printable character found at index 12"},
 10447  		},
 10448  		{
 10449  			name:       "non-utf-8",
 10450  			deprecated: true,
 10451  			warning:    pointer.StringPtr("Test message\xc5foo"),
 10452  			want:       []string{"must only contain printable UTF-8 characters"},
 10453  		},
 10454  	}
 10455  	for _, tt := range tests {
 10456  		t.Run(tt.name, func(t *testing.T) {
 10457  			if got := validateDeprecationWarning(tt.deprecated, tt.warning); !reflect.DeepEqual(got, tt.want) {
 10458  				t.Errorf("validateDeprecationWarning() = %v, want %v", got, tt.want)
 10459  			}
 10460  		})
 10461  	}
 10462  }
 10463  
 10464  func genMapSchema() *apiextensions.JSONSchemaProps {
 10465  	return &apiextensions.JSONSchemaProps{
 10466  		Type: "object",
 10467  		AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
 10468  			Schema: &apiextensions.JSONSchemaProps{
 10469  				Type: "string",
 10470  			},
 10471  		},
 10472  	}
 10473  }
 10474  
 10475  func withMaxProperties(mapSchema *apiextensions.JSONSchemaProps, maxProps *int64) *apiextensions.JSONSchemaProps {
 10476  	mapSchema.MaxProperties = maxProps
 10477  	return mapSchema
 10478  }
 10479  
 10480  func genArraySchema() *apiextensions.JSONSchemaProps {
 10481  	return &apiextensions.JSONSchemaProps{
 10482  		Type: "array",
 10483  	}
 10484  }
 10485  
 10486  func withMaxItems(arraySchema *apiextensions.JSONSchemaProps, maxItems *int64) *apiextensions.JSONSchemaProps {
 10487  	arraySchema.MaxItems = maxItems
 10488  	return arraySchema
 10489  }
 10490  
 10491  func genObjectSchema() *apiextensions.JSONSchemaProps {
 10492  	return &apiextensions.JSONSchemaProps{
 10493  		Type: "object",
 10494  	}
 10495  }
 10496  
 10497  func TestCostInfo(t *testing.T) {
 10498  	tests := []struct {
 10499  		name                   string
 10500  		schema                 []*apiextensions.JSONSchemaProps
 10501  		expectedMaxCardinality *uint64
 10502  	}{
 10503  		{
 10504  			name: "object",
 10505  			schema: []*apiextensions.JSONSchemaProps{
 10506  				genObjectSchema(),
 10507  			},
 10508  			expectedMaxCardinality: uint64ptr(1),
 10509  		},
 10510  		{
 10511  			name: "array",
 10512  			schema: []*apiextensions.JSONSchemaProps{
 10513  				withMaxItems(genArraySchema(), int64ptr(5)),
 10514  			},
 10515  			expectedMaxCardinality: uint64ptr(5),
 10516  		},
 10517  		{
 10518  			name:                   "unbounded array",
 10519  			schema:                 []*apiextensions.JSONSchemaProps{genArraySchema()},
 10520  			expectedMaxCardinality: nil,
 10521  		},
 10522  		{
 10523  			name:                   "map",
 10524  			schema:                 []*apiextensions.JSONSchemaProps{withMaxProperties(genMapSchema(), int64ptr(10))},
 10525  			expectedMaxCardinality: uint64ptr(10),
 10526  		},
 10527  		{
 10528  			name: "unbounded map",
 10529  			schema: []*apiextensions.JSONSchemaProps{
 10530  				genMapSchema(),
 10531  			},
 10532  			expectedMaxCardinality: nil,
 10533  		},
 10534  		{
 10535  			name: "array inside map",
 10536  			schema: []*apiextensions.JSONSchemaProps{
 10537  				withMaxProperties(genMapSchema(), int64ptr(5)),
 10538  				withMaxItems(genArraySchema(), int64ptr(5)),
 10539  			},
 10540  			expectedMaxCardinality: uint64ptr(25),
 10541  		},
 10542  		{
 10543  			name: "unbounded array inside bounded map",
 10544  			schema: []*apiextensions.JSONSchemaProps{
 10545  				withMaxProperties(genMapSchema(), int64ptr(5)),
 10546  				genArraySchema(),
 10547  			},
 10548  			expectedMaxCardinality: nil,
 10549  		},
 10550  		{
 10551  			name: "object inside array",
 10552  			schema: []*apiextensions.JSONSchemaProps{
 10553  				withMaxItems(genArraySchema(), int64ptr(3)),
 10554  				genObjectSchema(),
 10555  			},
 10556  			expectedMaxCardinality: uint64ptr(3),
 10557  		},
 10558  		{
 10559  			name: "map inside object inside array",
 10560  			schema: []*apiextensions.JSONSchemaProps{
 10561  				withMaxItems(genArraySchema(), int64ptr(2)),
 10562  				genObjectSchema(),
 10563  				withMaxProperties(genMapSchema(), int64ptr(4)),
 10564  			},
 10565  			expectedMaxCardinality: uint64ptr(8),
 10566  		},
 10567  		{
 10568  			name: "integer overflow bounds check",
 10569  			schema: []*apiextensions.JSONSchemaProps{
 10570  				withMaxItems(genArraySchema(), int64ptr(math.MaxInt)),
 10571  				withMaxItems(genArraySchema(), int64ptr(100)),
 10572  			},
 10573  			expectedMaxCardinality: uint64ptr(math.MaxUint),
 10574  		},
 10575  	}
 10576  	for _, tt := range tests {
 10577  		t.Run(tt.name, func(t *testing.T) {
 10578  			// simulate the recursive validation calls
 10579  			schemas := append(tt.schema, &apiextensions.JSONSchemaProps{Type: "string"}) // append a leaf type
 10580  			curCostInfo := RootCELContext(schemas[0])
 10581  			for i := 1; i < len(schemas); i++ {
 10582  				curCostInfo = curCostInfo.childContext(schemas[i], nil)
 10583  			}
 10584  			if tt.expectedMaxCardinality == nil && curCostInfo.MaxCardinality == nil {
 10585  				// unbounded cardinality case, test ran correctly
 10586  			} else if tt.expectedMaxCardinality == nil && curCostInfo.MaxCardinality != nil {
 10587  				t.Errorf("expected unbounded cardinality (got %d)", curCostInfo.MaxCardinality)
 10588  			} else if tt.expectedMaxCardinality != nil && curCostInfo.MaxCardinality == nil {
 10589  				t.Errorf("expected bounded cardinality of %d but got unbounded cardinality", tt.expectedMaxCardinality)
 10590  			} else if *tt.expectedMaxCardinality != *curCostInfo.MaxCardinality {
 10591  				t.Errorf("wrong cardinality (expected %d, got %d)", *tt.expectedMaxCardinality, curCostInfo.MaxCardinality)
 10592  			}
 10593  		})
 10594  	}
 10595  }
 10596  
 10597  func TestCelContext(t *testing.T) {
 10598  	tests := []struct {
 10599  		name   string
 10600  		schema *apiextensions.JSONSchemaProps
 10601  	}{
 10602  		{
 10603  			name: "verify that schemas are converted only once and then reused",
 10604  			schema: &apiextensions.JSONSchemaProps{
 10605  				Type:         "object",
 10606  				XValidations: []apiextensions.ValidationRule{{Rule: "self.size() < 100"}},
 10607  				AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
 10608  					Schema: &apiextensions.JSONSchemaProps{
 10609  						Type: "array",
 10610  						Items: &apiextensions.JSONSchemaPropsOrArray{
 10611  							Schema: &apiextensions.JSONSchemaProps{
 10612  								Type:         "object",
 10613  								XValidations: []apiextensions.ValidationRule{{Rule: "has(self.field)"}},
 10614  								Properties: map[string]apiextensions.JSONSchemaProps{
 10615  									"field": {
 10616  										XValidations: []apiextensions.ValidationRule{{Rule: "self.startsWith('abc')"}},
 10617  										Type:         "string",
 10618  									},
 10619  								},
 10620  							},
 10621  						},
 10622  					},
 10623  				},
 10624  			},
 10625  		},
 10626  	}
 10627  	for _, tt := range tests {
 10628  		t.Run(tt.name, func(t *testing.T) {
 10629  			// simulate the recursive validation calls
 10630  			conversionCount := 0
 10631  			converter := func(schema *apiextensions.JSONSchemaProps, isRoot bool) (*CELTypeInfo, error) {
 10632  				conversionCount++
 10633  				return defaultConverter(schema, isRoot)
 10634  			}
 10635  			celContext := RootCELContext(tt.schema)
 10636  			celContext.converter = converter
 10637  			opts := validationOptions{
 10638  				celEnvironmentSet: environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()),
 10639  			}
 10640  			openAPIV3Schema := &specStandardValidatorV3{
 10641  				allowDefaults:            opts.allowDefaults,
 10642  				disallowDefaultsReason:   opts.disallowDefaultsReason,
 10643  				requireValidPropertyType: opts.requireValidPropertyType,
 10644  			}
 10645  			errors := ValidateCustomResourceDefinitionOpenAPISchema(tt.schema, field.NewPath("openAPIV3Schema"), openAPIV3Schema, true, &opts, celContext).AllErrors()
 10646  			if len(errors) != 0 {
 10647  				t.Errorf("Expected no validate errors but got %v", errors)
 10648  			}
 10649  			if conversionCount != 1 {
 10650  				t.Errorf("Expected 1 conversion to be performed by cel context during schema traversal but observed %d conversions", conversionCount)
 10651  			}
 10652  		})
 10653  	}
 10654  }
 10655  
 10656  func TestPerCRDEstimatedCost(t *testing.T) {
 10657  	tests := []struct {
 10658  		name              string
 10659  		costs             []uint64
 10660  		expectedExpensive []uint64
 10661  		expectedTotal     uint64
 10662  	}{
 10663  		{
 10664  			name:              "no costs",
 10665  			costs:             []uint64{},
 10666  			expectedExpensive: []uint64{},
 10667  			expectedTotal:     uint64(0),
 10668  		},
 10669  		{
 10670  			name:              "one cost",
 10671  			costs:             []uint64{1000000},
 10672  			expectedExpensive: []uint64{1000000},
 10673  			expectedTotal:     uint64(1000000),
 10674  		},
 10675  		{
 10676  			name:              "one cost, ignored", // costs < 1% of the per-CRD cost limit are not considered expensive
 10677  			costs:             []uint64{900000},
 10678  			expectedExpensive: []uint64{},
 10679  			expectedTotal:     uint64(900000),
 10680  		},
 10681  		{
 10682  			name:              "2 costs",
 10683  			costs:             []uint64{5000000, 25000000},
 10684  			expectedExpensive: []uint64{25000000, 5000000},
 10685  			expectedTotal:     uint64(30000000),
 10686  		},
 10687  		{
 10688  			name:              "3 costs, one ignored",
 10689  			costs:             []uint64{5000000, 25000000, 900000},
 10690  			expectedExpensive: []uint64{25000000, 5000000},
 10691  			expectedTotal:     uint64(30900000),
 10692  		},
 10693  		{
 10694  			name:              "4 costs",
 10695  			costs:             []uint64{16000000, 50000000, 34000000, 50000000},
 10696  			expectedExpensive: []uint64{50000000, 50000000, 34000000, 16000000},
 10697  			expectedTotal:     uint64(150000000),
 10698  		},
 10699  		{
 10700  			name:              "5 costs, one trimmed, one ignored", // only the top 4 most expensive are tracked
 10701  			costs:             []uint64{16000000, 50000000, 900000, 34000000, 50000000, 50000001},
 10702  			expectedExpensive: []uint64{50000001, 50000000, 50000000, 34000000},
 10703  			expectedTotal:     uint64(200900001),
 10704  		},
 10705  		{
 10706  			name:              "costs do not overflow",
 10707  			costs:             []uint64{math.MaxUint64 / 2, math.MaxUint64 / 2, 1, 10, 100, 1000},
 10708  			expectedExpensive: []uint64{math.MaxUint64 / 2, math.MaxUint64 / 2},
 10709  			expectedTotal:     uint64(math.MaxUint64),
 10710  		},
 10711  	}
 10712  	for _, tt := range tests {
 10713  		t.Run(tt.name, func(t *testing.T) {
 10714  			crdCost := TotalCost{}
 10715  			for _, cost := range tt.costs {
 10716  				crdCost.ObserveExpressionCost(nil, cost)
 10717  			}
 10718  			if len(crdCost.MostExpensive) != len(tt.expectedExpensive) {
 10719  				t.Fatalf("expected %d largest costs but got %d: %v", len(tt.expectedExpensive), len(crdCost.MostExpensive), crdCost.MostExpensive)
 10720  			}
 10721  			for i, expensive := range crdCost.MostExpensive {
 10722  				if tt.expectedExpensive[i] != expensive.Cost {
 10723  					t.Errorf("expected largest cost of %d at index %d but got %d", tt.expectedExpensive[i], i, expensive.Cost)
 10724  				}
 10725  			}
 10726  			if tt.expectedTotal != crdCost.Total {
 10727  				t.Errorf("expected total cost of %d but got %d", tt.expectedTotal, crdCost.Total)
 10728  			}
 10729  		})
 10730  	}
 10731  }
 10732  
 10733  func int64ptr(i int64) *int64 {
 10734  	return &i
 10735  }
 10736  

View as plain text