@@ -45,6 +45,7 @@ func ValidateCustomResourceDefinition(obj *apiextensions.CustomResourceDefinitio
4545 allErrs := genericvalidation .ValidateObjectMeta (& obj .ObjectMeta , false , nameValidationFn , field .NewPath ("metadata" ))
4646 allErrs = append (allErrs , ValidateCustomResourceDefinitionSpec (& obj .Spec , field .NewPath ("spec" ))... )
4747 allErrs = append (allErrs , ValidateCustomResourceDefinitionStatus (& obj .Status , field .NewPath ("status" ))... )
48+ allErrs = append (allErrs , ValidateCustomResourceDefinitionStoredVersions (obj .Status .StoredVersions , obj .Spec .Versions , field .NewPath ("status" ).Child ("storedVersions" ))... )
4849 return allErrs
4950}
5051
@@ -53,6 +54,34 @@ func ValidateCustomResourceDefinitionUpdate(obj, oldObj *apiextensions.CustomRes
5354 allErrs := genericvalidation .ValidateObjectMetaUpdate (& obj .ObjectMeta , & oldObj .ObjectMeta , field .NewPath ("metadata" ))
5455 allErrs = append (allErrs , ValidateCustomResourceDefinitionSpecUpdate (& obj .Spec , & oldObj .Spec , apiextensions .IsCRDConditionTrue (oldObj , apiextensions .Established ), field .NewPath ("spec" ))... )
5556 allErrs = append (allErrs , ValidateCustomResourceDefinitionStatus (& obj .Status , field .NewPath ("status" ))... )
57+ allErrs = append (allErrs , ValidateCustomResourceDefinitionStoredVersions (obj .Status .StoredVersions , obj .Spec .Versions , field .NewPath ("status" ).Child ("storedVersions" ))... )
58+ return allErrs
59+ }
60+
61+ // ValidateCustomResourceDefinitionStoredVersions statically validates
62+ func ValidateCustomResourceDefinitionStoredVersions (storedVersions []string , versions []apiextensions.CustomResourceDefinitionVersion , fldPath * field.Path ) field.ErrorList {
63+ if len (storedVersions ) == 0 {
64+ return field.ErrorList {field .Invalid (fldPath , storedVersions , "must have at least one stored version" )}
65+ }
66+ allErrs := field.ErrorList {}
67+ storedVersionsMap := map [string ]int {}
68+ for i , v := range storedVersions {
69+ storedVersionsMap [v ] = i
70+ }
71+ for _ , v := range versions {
72+ _ , ok := storedVersionsMap [v .Name ]
73+ if v .Storage && ! ok {
74+ allErrs = append (allErrs , field .Invalid (fldPath , v , "must have the storage version " + v .Name ))
75+ }
76+ if ok {
77+ delete (storedVersionsMap , v .Name )
78+ }
79+ }
80+
81+ for v , i := range storedVersionsMap {
82+ allErrs = append (allErrs , field .Invalid (fldPath .Index (i ), v , "must appear in spec.versions" ))
83+ }
84+
5685 return allErrs
5786}
5887
@@ -75,12 +104,6 @@ func ValidateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefi
75104 allErrs = append (allErrs , field .Invalid (fldPath .Child ("group" ), spec .Group , "should be a domain with at least one dot" ))
76105 }
77106
78- if len (spec .Version ) == 0 {
79- allErrs = append (allErrs , field .Required (fldPath .Child ("version" ), "" ))
80- } else if errs := validationutil .IsDNS1035Label (spec .Version ); len (errs ) > 0 {
81- allErrs = append (allErrs , field .Invalid (fldPath .Child ("version" ), spec .Version , strings .Join (errs , "," )))
82- }
83-
84107 switch spec .Scope {
85108 case "" :
86109 allErrs = append (allErrs , field .Required (fldPath .Child ("scope" ), "" ))
@@ -89,6 +112,37 @@ func ValidateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefi
89112 allErrs = append (allErrs , field .NotSupported (fldPath .Child ("scope" ), spec .Scope , []string {string (apiextensions .ClusterScoped ), string (apiextensions .NamespaceScoped )}))
90113 }
91114
115+ storageFlagCount := 0
116+ versionsMap := map [string ]bool {}
117+ uniqueNames := true
118+ for i , version := range spec .Versions {
119+ if version .Storage {
120+ storageFlagCount ++
121+ }
122+ if versionsMap [version .Name ] {
123+ uniqueNames = false
124+ } else {
125+ versionsMap [version .Name ] = true
126+ }
127+ if errs := validationutil .IsDNS1035Label (version .Name ); len (errs ) > 0 {
128+ allErrs = append (allErrs , field .Invalid (fldPath .Child ("versions" ).Index (i ).Child ("name" ), spec .Versions [i ].Name , strings .Join (errs , "," )))
129+ }
130+ }
131+ if ! uniqueNames {
132+ allErrs = append (allErrs , field .Invalid (fldPath .Child ("versions" ), spec .Versions , "must contain unique version names" ))
133+ }
134+ if storageFlagCount != 1 {
135+ allErrs = append (allErrs , field .Invalid (fldPath .Child ("versions" ), spec .Versions , "must have exactly one version marked as storage version" ))
136+ }
137+ if len (spec .Version ) != 0 {
138+ if errs := validationutil .IsDNS1035Label (spec .Version ); len (errs ) > 0 {
139+ allErrs = append (allErrs , field .Invalid (fldPath .Child ("version" ), spec .Version , strings .Join (errs , "," )))
140+ }
141+ if len (spec .Versions ) >= 1 && spec .Versions [0 ].Name != spec .Version {
142+ allErrs = append (allErrs , field .Invalid (fldPath .Child ("version" ), spec .Version , "must match the first version in spec.versions" ))
143+ }
144+ }
145+
92146 // in addition to the basic name restrictions, some names are required for spec, but not for status
93147 if len (spec .Names .Plural ) == 0 {
94148 allErrs = append (allErrs , field .Required (fldPath .Child ("names" , "plural" ), "" ))
@@ -130,7 +184,6 @@ func ValidateCustomResourceDefinitionSpecUpdate(spec, oldSpec *apiextensions.Cus
130184
131185 if established {
132186 // these effect the storage and cannot be changed therefore
133- allErrs = append (allErrs , genericvalidation .ValidateImmutableField (spec .Version , oldSpec .Version , fldPath .Child ("version" ))... )
134187 allErrs = append (allErrs , genericvalidation .ValidateImmutableField (spec .Scope , oldSpec .Scope , fldPath .Child ("scope" ))... )
135188 allErrs = append (allErrs , genericvalidation .ValidateImmutableField (spec .Names .Kind , oldSpec .Names .Kind , fldPath .Child ("names" , "kind" ))... )
136189 }
0 commit comments