@@ -566,6 +566,11 @@ func ValidateCustomResourceColumnDefinition(col *apiextensions.CustomResourceCol
566
566
// specStandardValidator applies validations for different OpenAPI specification versions.
567
567
type specStandardValidator interface {
568
568
validate (spec * apiextensions.JSONSchemaProps , fldPath * field.Path ) field.ErrorList
569
+ withForbiddenDefaults (reason string ) specStandardValidator
570
+
571
+ // insideResourceMeta returns true when validating either TypeMeta or ObjectMeta, from an embedded resource or on the top-level.
572
+ insideResourceMeta () bool
573
+ withInsideResourceMeta () specStandardValidator
569
574
}
570
575
571
576
// ValidateCustomResourceDefinitionValidation statically validates
@@ -612,7 +617,7 @@ func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiext
612
617
openAPIV3Schema := & specStandardValidatorV3 {
613
618
allowDefaults : allowDefaults ,
614
619
}
615
- allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (schema , fldPath .Child ("openAPIV3Schema" ), openAPIV3Schema )... )
620
+ allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (schema , fldPath .Child ("openAPIV3Schema" ), openAPIV3Schema , true )... )
616
621
617
622
if mustBeStructural {
618
623
if ss , err := structuralschema .NewStructural (schema ); err != nil {
@@ -636,7 +641,7 @@ func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiext
636
641
}
637
642
638
643
// ValidateCustomResourceDefinitionOpenAPISchema statically validates
639
- func ValidateCustomResourceDefinitionOpenAPISchema (schema * apiextensions.JSONSchemaProps , fldPath * field.Path , ssv specStandardValidator ) field.ErrorList {
644
+ func ValidateCustomResourceDefinitionOpenAPISchema (schema * apiextensions.JSONSchemaProps , fldPath * field.Path , ssv specStandardValidator , isRoot bool ) field.ErrorList {
640
645
allErrs := field.ErrorList {}
641
646
642
647
if schema == nil {
@@ -664,53 +669,68 @@ func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSch
664
669
allErrs = append (allErrs , field .Forbidden (fldPath .Child ("additionalProperties" ), "additionalProperties and properties are mutual exclusive" ))
665
670
}
666
671
}
667
- allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (schema .AdditionalProperties .Schema , fldPath .Child ("additionalProperties" ), ssv )... )
672
+ // Note: we forbid additionalProperties at resource root, both embedded and top-level.
673
+ // But further inside, additionalProperites is possible, e.g. for labels or annotations.
674
+ subSsv := ssv
675
+ if ssv .insideResourceMeta () {
676
+ // we have to forbid defaults inside additionalProperties because pruning without actual value is ambiguous
677
+ subSsv = ssv .withForbiddenDefaults ("inside additionalProperties applying to object metadata" )
678
+ }
679
+ allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (schema .AdditionalProperties .Schema , fldPath .Child ("additionalProperties" ), subSsv , false )... )
668
680
}
669
681
670
682
if len (schema .Properties ) != 0 {
671
683
for property , jsonSchema := range schema .Properties {
672
- allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (& jsonSchema , fldPath .Child ("properties" ).Key (property ), ssv )... )
684
+ subSsv := ssv
685
+ if (isRoot || schema .XEmbeddedResource ) && property == "metadata" {
686
+ // we recurse into the schema that applies to ObjectMeta.
687
+ subSsv = ssv .withInsideResourceMeta ()
688
+ if isRoot {
689
+ subSsv = subSsv .withForbiddenDefaults (fmt .Sprintf ("in top-level %s" , property ))
690
+ }
691
+ }
692
+ allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (& jsonSchema , fldPath .Child ("properties" ).Key (property ), subSsv , false )... )
673
693
}
674
694
}
675
695
676
- allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (schema .Not , fldPath .Child ("not" ), ssv )... )
696
+ allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (schema .Not , fldPath .Child ("not" ), ssv , false )... )
677
697
678
698
if len (schema .AllOf ) != 0 {
679
699
for i , jsonSchema := range schema .AllOf {
680
- allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (& jsonSchema , fldPath .Child ("allOf" ).Index (i ), ssv )... )
700
+ allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (& jsonSchema , fldPath .Child ("allOf" ).Index (i ), ssv , false )... )
681
701
}
682
702
}
683
703
684
704
if len (schema .OneOf ) != 0 {
685
705
for i , jsonSchema := range schema .OneOf {
686
- allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (& jsonSchema , fldPath .Child ("oneOf" ).Index (i ), ssv )... )
706
+ allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (& jsonSchema , fldPath .Child ("oneOf" ).Index (i ), ssv , false )... )
687
707
}
688
708
}
689
709
690
710
if len (schema .AnyOf ) != 0 {
691
711
for i , jsonSchema := range schema .AnyOf {
692
- allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (& jsonSchema , fldPath .Child ("anyOf" ).Index (i ), ssv )... )
712
+ allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (& jsonSchema , fldPath .Child ("anyOf" ).Index (i ), ssv , false )... )
693
713
}
694
714
}
695
715
696
716
if len (schema .Definitions ) != 0 {
697
717
for definition , jsonSchema := range schema .Definitions {
698
- allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (& jsonSchema , fldPath .Child ("definitions" ).Key (definition ), ssv )... )
718
+ allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (& jsonSchema , fldPath .Child ("definitions" ).Key (definition ), ssv , false )... )
699
719
}
700
720
}
701
721
702
722
if schema .Items != nil {
703
- allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (schema .Items .Schema , fldPath .Child ("items" ), ssv )... )
723
+ allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (schema .Items .Schema , fldPath .Child ("items" ), ssv , false )... )
704
724
if len (schema .Items .JSONSchemas ) != 0 {
705
725
for i , jsonSchema := range schema .Items .JSONSchemas {
706
- allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (& jsonSchema , fldPath .Child ("items" ).Index (i ), ssv )... )
726
+ allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (& jsonSchema , fldPath .Child ("items" ).Index (i ), ssv , false )... )
707
727
}
708
728
}
709
729
}
710
730
711
731
if schema .Dependencies != nil {
712
732
for dependency , jsonSchemaPropsOrStringArray := range schema .Dependencies {
713
- allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (jsonSchemaPropsOrStringArray .Schema , fldPath .Child ("dependencies" ).Key (dependency ), ssv )... )
733
+ allErrs = append (allErrs , ValidateCustomResourceDefinitionOpenAPISchema (jsonSchemaPropsOrStringArray .Schema , fldPath .Child ("dependencies" ).Key (dependency ), ssv , false )... )
714
734
}
715
735
}
716
736
@@ -722,7 +742,26 @@ func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSch
722
742
}
723
743
724
744
type specStandardValidatorV3 struct {
725
- allowDefaults bool
745
+ allowDefaults bool
746
+ disallowDefaultsReason string
747
+ isInsideResourceMeta bool
748
+ }
749
+
750
+ func (v * specStandardValidatorV3 ) withForbiddenDefaults (reason string ) specStandardValidator {
751
+ clone := * v
752
+ clone .disallowDefaultsReason = reason
753
+ clone .allowDefaults = false
754
+ return & clone
755
+ }
756
+
757
+ func (v * specStandardValidatorV3 ) withInsideResourceMeta () specStandardValidator {
758
+ clone := * v
759
+ clone .isInsideResourceMeta = true
760
+ return & clone
761
+ }
762
+
763
+ func (v * specStandardValidatorV3 ) insideResourceMeta () bool {
764
+ return v .isInsideResourceMeta
726
765
}
727
766
728
767
// validate validates against OpenAPI Schema v3.
@@ -741,23 +780,37 @@ func (v *specStandardValidatorV3) validate(schema *apiextensions.JSONSchemaProps
741
780
if v .allowDefaults {
742
781
if s , err := structuralschema .NewStructural (schema ); err == nil {
743
782
// ignore errors here locally. They will show up for the root of the schema.
744
- pruned := runtime .DeepCopyJSONValue (interface {}(* schema .Default ))
745
- pruning .Prune (pruned , s , false )
746
- if err := schemaobjectmeta .Coerce (fldPath , pruned , s , false , false ); err != nil {
783
+
784
+ clone := runtime .DeepCopyJSONValue (interface {}(* schema .Default ))
785
+ if ! v .isInsideResourceMeta || s .XEmbeddedResource {
786
+ pruning .Prune (clone , s , s .XEmbeddedResource )
787
+ // If we are under metadata, there are implicitly specified fields like kind, apiVersion, metadata, labels.
788
+ // We cannot prune as they are pruned as well. This allows more defaults than we would like to.
789
+ // TODO: be precise about pruning under metadata
790
+ }
791
+ // TODO: coerce correctly if we are not at the object root, but somewhere below.
792
+ if err := schemaobjectmeta .Coerce (fldPath , clone , s , s .XEmbeddedResource , false ); err != nil {
747
793
allErrs = append (allErrs , err )
748
794
}
749
- if ! reflect .DeepEqual (pruned , * schema .Default ) {
750
- allErrs = append (allErrs , field .Invalid (fldPath .Child ("default" ), schema .Default , "must not have unspecified fields" ))
795
+ if ! reflect .DeepEqual (clone , interface {}(* schema .Default )) {
796
+ allErrs = append (allErrs , field .Invalid (fldPath .Child ("default" ), schema .Default , "must not have unknown fields" ))
797
+ } else if s .XEmbeddedResource {
798
+ // validate an embedded resource
799
+ schemaobjectmeta .Validate (fldPath , interface {}(* schema .Default ), nil , true )
751
800
}
752
801
753
- // validate the default value. Only validating and pruned defaults are allowed .
802
+ // validate the default value with user the provided schema .
754
803
validator := govalidate .NewSchemaValidator (s .ToGoOpenAPI (), nil , "" , strfmt .Default )
755
- if err := apiservervalidation .ValidateCustomResource (pruned , validator ); err != nil {
804
+ if err := apiservervalidation .ValidateCustomResource (interface {}( * schema . Default ) , validator ); err != nil {
756
805
allErrs = append (allErrs , field .Invalid (fldPath .Child ("default" ), schema .Default , fmt .Sprintf ("must validate: %v" , err )))
757
806
}
758
807
}
759
808
} else {
760
- allErrs = append (allErrs , field .Forbidden (fldPath .Child ("default" ), "must not be set" ))
809
+ detail := "must not be set"
810
+ if len (v .disallowDefaultsReason ) > 0 {
811
+ detail += " " + v .disallowDefaultsReason
812
+ }
813
+ allErrs = append (allErrs , field .Forbidden (fldPath .Child ("default" ), detail ))
761
814
}
762
815
}
763
816
0 commit comments