@@ -26,8 +26,6 @@ import (
2626
2727 "github.com/google/cel-go/cel"
2828
29- "k8s.io/utils/pointer"
30-
3129 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
3230 apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
3331 apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
@@ -42,12 +40,13 @@ import (
4240 "k8s.io/apimachinery/pkg/util/version"
4341 "k8s.io/apiserver/pkg/cel/environment"
4442 "k8s.io/apiserver/pkg/cel/library"
43+ "k8s.io/utils/pointer"
4544)
4645
4746type validationMatch struct {
48- path * field.Path
49- errorType field.ErrorType
50- contains string
47+ path * field.Path
48+ errorType field.ErrorType
49+ containsString string
5150}
5251
5352func required (path ... string ) validationMatch {
@@ -73,7 +72,12 @@ func forbidden(path ...string) validationMatch {
7372}
7473
7574func (v validationMatch ) matches (err * field.Error ) bool {
76- return err .Type == v .errorType && err .Field == v .path .String () && strings .Contains (err .Error (), v .contains )
75+ return err .Type == v .errorType && err .Field == v .path .String () && strings .Contains (err .Error (), v .containsString )
76+ }
77+
78+ func (v validationMatch ) contains (s string ) validationMatch {
79+ v .containsString = s
80+ return v
7781}
7882
7983func strPtr (s string ) * string { return & s }
@@ -9436,6 +9440,100 @@ func TestSchemaHasDefaults(t *testing.T) {
94369440 }
94379441}
94389442
9443+ func TestValidateCustomResourceDefinitionStoredVersions (t * testing.T ) {
9444+ tests := []struct {
9445+ name string
9446+ versions []string
9447+ storageVersion string
9448+ storedVersions []string
9449+ errors []validationMatch
9450+ }{
9451+ {
9452+ name : "one version" ,
9453+ versions : []string {"v1" },
9454+ storageVersion : "v1" ,
9455+ storedVersions : []string {"v1" },
9456+ },
9457+ {
9458+ name : "no stored version" ,
9459+ versions : []string {"v1" },
9460+ storageVersion : "v1" ,
9461+ storedVersions : []string {},
9462+ errors : []validationMatch {
9463+ invalid ("status" , "storedVersions" ).contains ("Invalid value: []string{}: must have at least one stored version" ),
9464+ },
9465+ },
9466+ {
9467+ name : "many versions" ,
9468+ versions : []string {"v1alpha" , "v1beta1" , "v1" },
9469+ storageVersion : "v1" ,
9470+ storedVersions : []string {"v1alpha" , "v1" },
9471+ },
9472+ {
9473+ name : "missing stored versions" ,
9474+ versions : []string {"v1beta1" , "v1" },
9475+ storageVersion : "v1" ,
9476+ storedVersions : []string {"v1alpha" , "v1beta1" , "v1" },
9477+ errors : []validationMatch {
9478+ invalidIndex (0 , "status" , "storedVersions" ).contains ("Invalid value: \" v1alpha\" : must appear in spec.versions" ),
9479+ },
9480+ },
9481+ {
9482+ name : "missing storage versions" ,
9483+ versions : []string {"v1alpha" , "v1beta1" , "v1" },
9484+ storageVersion : "v1" ,
9485+ storedVersions : []string {"v1alpha" , "v1beta1" },
9486+ errors : []validationMatch {
9487+ invalid ("status" , "storedVersions" ).contains ("Invalid value: []string{\" v1alpha\" , \" v1beta1\" }: must have the storage version v1" ),
9488+ },
9489+ },
9490+ }
9491+
9492+ for _ , tc := range tests {
9493+ crd := & apiextensions.CustomResourceDefinition {
9494+ ObjectMeta : metav1.ObjectMeta {Name : "plural.group.com" , ResourceVersion : "1" },
9495+ Spec : apiextensions.CustomResourceDefinitionSpec {
9496+ Group : "group.com" ,
9497+ Scope : "Cluster" ,
9498+ Names : apiextensions.CustomResourceDefinitionNames {Plural : "plural" , Singular : "singular" , Kind : "Plural" , ListKind : "PluralList" },
9499+ },
9500+ Status : apiextensions.CustomResourceDefinitionStatus {StoredVersions : tc .storedVersions },
9501+ }
9502+ for _ , version := range tc .versions {
9503+ v := apiextensions.CustomResourceDefinitionVersion {Name : version }
9504+ if tc .storageVersion == version {
9505+ v .Storage = true
9506+ }
9507+ crd .Spec .Versions = append (crd .Spec .Versions , v )
9508+ }
9509+
9510+ t .Run (tc .name , func (t * testing.T ) {
9511+ errs := ValidateCustomResourceDefinitionStoredVersions (crd .Status .StoredVersions , crd .Spec .Versions , field .NewPath ("status" , "storedVersions" ))
9512+ seenErrs := make ([]bool , len (errs ))
9513+
9514+ for _ , expectedError := range tc .errors {
9515+ found := false
9516+ for i , err := range errs {
9517+ if expectedError .matches (err ) && ! seenErrs [i ] {
9518+ found = true
9519+ seenErrs [i ] = true
9520+ break
9521+ }
9522+ }
9523+
9524+ if ! found {
9525+ t .Errorf ("expected %v at %v, got %v" , expectedError .errorType , expectedError .path .String (), errs )
9526+ }
9527+ }
9528+ for i , seen := range seenErrs {
9529+ if ! seen {
9530+ t .Errorf ("unexpected error: %v" , errs [i ])
9531+ }
9532+ }
9533+ })
9534+ }
9535+ }
9536+
94399537func BenchmarkSchemaHas (b * testing.B ) {
94409538 scheme := runtime .NewScheme ()
94419539 codecs := serializer .NewCodecFactory (scheme )
0 commit comments