Skip to content

Commit 2e18741

Browse files
committed
extend CRD map and set validation
1 parent 7587ab3 commit 2e18741

File tree

4 files changed

+785
-5
lines changed

4 files changed

+785
-5
lines changed

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func ValidateCustomResourceDefinition(obj *apiextensions.CustomResourceDefinitio
6464
requireStructuralSchema: requireStructuralSchema(requestGV, nil),
6565
requirePrunedDefaults: true,
6666
requireAtomicSetType: true,
67+
requireMapListKeysMapSetValidation: true,
6768
}
6869

6970
allErrs := genericvalidation.ValidateObjectMeta(&obj.ObjectMeta, false, nameValidationFn, field.NewPath("metadata"))
@@ -93,6 +94,10 @@ type validationOptions struct {
9394
requirePrunedDefaults bool
9495
// requireAtomicSetType indicates that the items type for a x-kubernetes-list-type=set list must be atomic.
9596
requireAtomicSetType bool
97+
// requireMapListKeysMapSetValidation indicates that:
98+
// 1. For x-kubernetes-list-type=map list, key fields are not nullable, and are required or have a default
99+
// 2. For x-kubernetes-list-type=map or x-kubernetes-list-type=set list, the whole item must not be nullable.
100+
requireMapListKeysMapSetValidation bool
96101
}
97102

98103
// ValidateCustomResourceDefinitionUpdate statically validates
@@ -106,6 +111,7 @@ func ValidateCustomResourceDefinitionUpdate(obj, oldObj *apiextensions.CustomRes
106111
requireStructuralSchema: requireStructuralSchema(requestGV, &oldObj.Spec),
107112
requirePrunedDefaults: requirePrunedDefaults(&oldObj.Spec),
108113
requireAtomicSetType: requireAtomicSetType(&oldObj.Spec),
114+
requireMapListKeysMapSetValidation: requireMapListKeysMapSetValidation(&oldObj.Spec),
109115
}
110116

111117
allErrs := genericvalidation.ValidateObjectMetaUpdate(&obj.ObjectMeta, &oldObj.ObjectMeta, field.NewPath("metadata"))
@@ -866,6 +872,58 @@ func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSch
866872
}
867873
}
868874

875+
if opts.requireMapListKeysMapSetValidation {
876+
allErrs = append(allErrs, validateMapListKeysMapSet(schema, fldPath)...)
877+
}
878+
879+
return allErrs
880+
}
881+
882+
func validateMapListKeysMapSet(schema *apiextensions.JSONSchemaProps, fldPath *field.Path) field.ErrorList {
883+
allErrs := field.ErrorList{}
884+
885+
if schema.Items == nil || schema.Items.Schema == nil {
886+
return nil
887+
}
888+
if schema.XListType == nil {
889+
return nil
890+
}
891+
if *schema.XListType != "set" && *schema.XListType != "map" {
892+
return nil
893+
}
894+
895+
// set and map list items cannot be nullable
896+
if schema.Items.Schema.Nullable {
897+
allErrs = append(allErrs, field.Forbidden(fldPath.Child("items").Child("nullable"), "cannot be nullable when x-kubernetes-list-type is "+*schema.XListType))
898+
}
899+
900+
switch *schema.XListType {
901+
case "map":
902+
// ensure all map keys are required or have a default
903+
isRequired := make(map[string]bool, len(schema.Items.Schema.Required))
904+
for _, required := range schema.Items.Schema.Required {
905+
isRequired[required] = true
906+
}
907+
908+
for _, k := range schema.XListMapKeys {
909+
obj, ok := schema.Items.Schema.Properties[k]
910+
if !ok {
911+
// we validate that all XListMapKeys are existing properties in ValidateCustomResourceDefinitionOpenAPISchema, so skipping here is ok
912+
continue
913+
}
914+
915+
if isRequired[k] == false && obj.Default == nil {
916+
allErrs = append(allErrs, field.Required(fldPath.Child("items").Child("properties").Key(k).Child("default"), "this property is in x-kubernetes-list-map-keys, so it must have a default or be a required property"))
917+
}
918+
919+
if obj.Nullable {
920+
allErrs = append(allErrs, field.Forbidden(fldPath.Child("items").Child("properties").Key(k).Child("nullable"), "this property is in x-kubernetes-list-map-keys, so it cannot be nullable"))
921+
}
922+
}
923+
case "set":
924+
// no other set-specific validation
925+
}
926+
869927
return allErrs
870928
}
871929

@@ -1268,6 +1326,16 @@ func hasNonAtomicSetType(schema *apiextensions.JSONSchemaProps) bool {
12681326
})
12691327
}
12701328

1329+
func requireMapListKeysMapSetValidation(oldCRDSpec *apiextensions.CustomResourceDefinitionSpec) bool {
1330+
return !hasSchemaWith(oldCRDSpec, hasInvalidMapListKeysMapSet)
1331+
}
1332+
1333+
func hasInvalidMapListKeysMapSet(schema *apiextensions.JSONSchemaProps) bool {
1334+
return schemaHas(schema, func(schema *apiextensions.JSONSchemaProps) bool {
1335+
return len(validateMapListKeysMapSet(schema, field.NewPath(""))) > 0
1336+
})
1337+
}
1338+
12711339
// requireValidPropertyType returns true if valid openapi v3 types should be required for the given API version
12721340
func requireValidPropertyType(requestGV schema.GroupVersion, oldCRDSpec *apiextensions.CustomResourceDefinitionSpec) bool {
12731341
if requestGV == apiextensionsv1beta1.SchemeGroupVersion {

0 commit comments

Comments
 (0)