Skip to content

Commit 40ac701

Browse files
committed
CRD versioning validation and defaulting
1 parent 02a7d79 commit 40ac701

File tree

4 files changed

+424
-29
lines changed

4 files changed

+424
-29
lines changed

staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package apiextensions
1818

1919
import (
20+
"fmt"
2021
"time"
2122

2223
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -116,3 +117,33 @@ func CRDRemoveFinalizer(crd *CustomResourceDefinition, needle string) {
116117
}
117118
crd.Finalizers = newFinalizers
118119
}
120+
121+
// HasServedCRDVersion returns true if `version` is in the list of CRD's versions and the Served flag is set.
122+
func HasServedCRDVersion(crd *CustomResourceDefinition, version string) bool {
123+
for _, v := range crd.Spec.Versions {
124+
if v.Name == version {
125+
return v.Served
126+
}
127+
}
128+
return false
129+
}
130+
131+
// GetCRDStorageVersion returns the storage version for given CRD.
132+
func GetCRDStorageVersion(crd *CustomResourceDefinition) (string, error) {
133+
for _, v := range crd.Spec.Versions {
134+
if v.Storage {
135+
return v.Name, nil
136+
}
137+
}
138+
// This should not happened if crd is valid
139+
return "", fmt.Errorf("invalid CustomResourceDefinition, no storage version")
140+
}
141+
142+
func IsStoredVersion(crd *CustomResourceDefinition, version string) bool {
143+
for _, v := range crd.Status.StoredVersions {
144+
if version == v {
145+
return true
146+
}
147+
}
148+
return false
149+
}

staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/defaults.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ func addDefaultingFuncs(scheme *runtime.Scheme) error {
3131

3232
func SetDefaults_CustomResourceDefinition(obj *CustomResourceDefinition) {
3333
SetDefaults_CustomResourceDefinitionSpec(&obj.Spec)
34+
if len(obj.Status.StoredVersions) == 0 {
35+
for _, v := range obj.Spec.Versions {
36+
if v.Storage {
37+
obj.Status.StoredVersions = append(obj.Status.StoredVersions, v.Name)
38+
break
39+
}
40+
}
41+
}
3442
}
3543

3644
func SetDefaults_CustomResourceDefinitionSpec(obj *CustomResourceDefinitionSpec) {
@@ -43,4 +51,16 @@ func SetDefaults_CustomResourceDefinitionSpec(obj *CustomResourceDefinitionSpec)
4351
if len(obj.Names.ListKind) == 0 && len(obj.Names.Kind) > 0 {
4452
obj.Names.ListKind = obj.Names.Kind + "List"
4553
}
54+
// If there is no list of versions, create on using deprecated Version field.
55+
if len(obj.Versions) == 0 && len(obj.Version) != 0 {
56+
obj.Versions = []CustomResourceDefinitionVersion{{
57+
Name: obj.Version,
58+
Storage: true,
59+
Served: true,
60+
}}
61+
}
62+
// For backward compatibility set the version field to the first item in versions list.
63+
if len(obj.Version) == 0 && len(obj.Versions) != 0 {
64+
obj.Version = obj.Versions[0].Name
65+
}
4666
}

staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)