diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation_test.go index bd79af557a4..fdc9d1bac0f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation_test.go @@ -1796,6 +1796,56 @@ func TestValidateCustomResourceDefinition(t *testing.T) { }, enabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, }, + { + name: "additionalProperties at resource root", + resource: &apiextensions.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"}, + Spec: apiextensions.CustomResourceDefinitionSpec{ + Group: "group.com", + Version: "version", + Versions: singleVersionList, + Scope: apiextensions.NamespaceScoped, + Names: apiextensions.CustomResourceDefinitionNames{ + Plural: "plural", + Singular: "singular", + Kind: "Plural", + ListKind: "PluralList", + }, + Validation: &apiextensions.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded1": { + Type: "object", + XEmbeddedResource: true, + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{Type: "string"}, + }, + }, + "embedded2": { + Type: "object", + XEmbeddedResource: true, + XPreserveUnknownFields: pointer.BoolPtr(true), + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{Type: "string"}, + }, + }, + }, + }, + }, + PreserveUnknownFields: pointer.BoolPtr(false), + }, + Status: apiextensions.CustomResourceDefinitionStatus{ + StoredVersions: []string{"version"}, + }, + }, + errors: []validationMatch{ + forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded1]", "additionalProperties"), + required("spec", "validation", "openAPIV3Schema", "properties[embedded1]", "properties"), + forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded2]", "additionalProperties"), + }, + enabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, + }, { name: "contradicting meta field types", resource: &apiextensions.CustomResourceDefinition{ diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation.go index abdcf886589..39ef89dbf06 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation.go @@ -126,6 +126,9 @@ func validateStructuralInvariants(s *Structural, lvl level, fldPath *field.Path) allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty for specified object fields")) } } + if s.XEmbeddedResource && s.AdditionalProperties != nil { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must not be used if x-kubernetes-embedded-resource is set")) + } if lvl == rootLevel && len(s.Type) > 0 && s.Type != "object" { allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), s.Type, "must be object at the root"))