diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/BUILD index a2315adafae..9628ceca431 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/BUILD @@ -16,21 +16,17 @@ go_library( "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library", - "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta:go_default_library", - "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning:go_default_library", + "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/validation:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library", - "//vendor/github.com/go-openapi/strfmt:go_default_library", - "//vendor/github.com/go-openapi/validate:go_default_library", ], ) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go index 1591ad8a6dd..31240cc211d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go @@ -21,14 +21,10 @@ import ( "reflect" "strings" - "github.com/go-openapi/strfmt" - govalidate "github.com/go-openapi/validate" - schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta" - "k8s.io/apiextensions-apiserver/pkg/apihelpers" + structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting" apiequality "k8s.io/apimachinery/pkg/api/equality" genericvalidation "k8s.io/apimachinery/pkg/api/validation" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" utilvalidation "k8s.io/apimachinery/pkg/util/validation" @@ -39,7 +35,6 @@ import ( "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" - "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning" apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation" apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" ) @@ -659,6 +654,7 @@ func validateCustomResourceDefinitionValidation(customResourceValidation *apiext allowDefaults: opts.allowDefaults, requireValidPropertyType: opts.requireValidPropertyType, } + allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema, fldPath.Child("openAPIV3Schema"), openAPIV3Schema, true)...) if opts.requireStructuralSchema { @@ -667,8 +663,13 @@ func validateCustomResourceDefinitionValidation(customResourceValidation *apiext if len(allErrs) == 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("openAPIV3Schema"), "", err.Error())) } + } else if validationErrors := structuralschema.ValidateStructural(fldPath.Child("openAPIV3Schema"), ss); len(validationErrors) > 0 { + allErrs = append(allErrs, validationErrors...) + } else if validationErrors, err := structuraldefaulting.ValidateDefaults(fldPath.Child("openAPIV3Schema"), ss, true); err != nil { + // this should never happen + allErrs = append(allErrs, field.Invalid(fldPath.Child("openAPIV3Schema"), "", err.Error())) } else { - allErrs = append(allErrs, structuralschema.ValidateStructural(ss, fldPath.Child("openAPIV3Schema"))...) + allErrs = append(allErrs, validationErrors...) } } } @@ -682,7 +683,7 @@ func validateCustomResourceDefinitionValidation(customResourceValidation *apiext return allErrs } -var metaFields = sets.NewString("metadata", "apiVersion", "kind") +var metaFields = sets.NewString("metadata", "kind", "apiVersion") // ValidateCustomResourceDefinitionOpenAPISchema statically validates func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSchemaProps, fldPath *field.Path, ssv specStandardValidator, isRoot bool) field.ErrorList { @@ -726,6 +727,7 @@ func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSch if len(schema.Properties) != 0 { for property, jsonSchema := range schema.Properties { subSsv := ssv + if (isRoot || schema.XEmbeddedResource) && metaFields.Has(property) { // we recurse into the schema that applies to ObjectMeta. subSsv = ssv.withInsideResourceMeta() @@ -825,43 +827,12 @@ func (v *specStandardValidatorV3) validate(schema *apiextensions.JSONSchemaProps allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), schema.Type, openapiV3Types.List())) } - if schema.Default != nil { - if v.allowDefaults { - if s, err := structuralschema.NewStructural(schema); err == nil { - // ignore errors here locally. They will show up for the root of the schema. - - clone := runtime.DeepCopyJSONValue(interface{}(*schema.Default)) - if !v.isInsideResourceMeta { - // If we are under metadata, there are implicitly specified fields like kind, apiVersion, metadata, labels. - // We cannot prune as they are pruned as well. This allows more defaults than we would like to. - // TODO: be precise about pruning under metadata - pruning.Prune(clone, s, s.XEmbeddedResource) - - // TODO: coerce correctly if we are not at the object root, but somewhere below. - if err := schemaobjectmeta.Coerce(fldPath, clone, s, s.XEmbeddedResource, false); err != nil { - allErrs = append(allErrs, err) - } - - if !reflect.DeepEqual(clone, interface{}(*schema.Default)) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("default"), schema.Default, "must not have unknown fields")) - } else if s.XEmbeddedResource { - // validate an embedded resource - schemaobjectmeta.Validate(fldPath, interface{}(*schema.Default), nil, true) - } - } - - // validate the default value with user the provided schema. - validator := govalidate.NewSchemaValidator(s.ToGoOpenAPI(), nil, "", strfmt.Default) - - allErrs = append(allErrs, apiservervalidation.ValidateCustomResource(fldPath.Child("default"), interface{}(*schema.Default), validator)...) - } - } else { - detail := "must not be set" - if len(v.disallowDefaultsReason) > 0 { - detail += " " + v.disallowDefaultsReason - } - allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), detail)) + if schema.Default != nil && !v.allowDefaults { + detail := "must not be set" + if len(v.disallowDefaultsReason) > 0 { + detail += " " + v.disallowDefaultsReason } + allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), detail)) } if schema.ID != "" { @@ -1212,7 +1183,7 @@ func schemaIsNonStructural(schema *apiextensions.JSONSchemaProps) bool { if err != nil { return true } - return len(structuralschema.ValidateStructural(ss, nil)) > 0 + return len(structuralschema.ValidateStructural(nil, ss)) > 0 } // requireValidPropertyType returns true if valid openapi v3 types should be required for the given API version 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 9a8631767b9..cdeb690164d 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 @@ -2084,7 +2084,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) { }, "bad": { Type: "string", - Pattern: "+", + Pattern: "b", }, }, Default: jsonPtr(map[string]interface{}{ @@ -2169,7 +2169,8 @@ func TestValidateCustomResourceDefinition(t *testing.T) { "apiVersion": "foo/v1", "kind": "v1", "metadata": map[string]interface{}{ - "name": "foo", + "name": "foo", + // allow: unknown fields under metadata are not rejected during CRD validation, but only pruned in storage creation "unspecified": "bar", }, "bar": int64(42), @@ -2188,12 +2189,10 @@ func TestValidateCustomResourceDefinition(t *testing.T) { invalid("spec", "validation", "openAPIV3Schema", "properties[a]", "default"), invalid("spec", "validation", "openAPIV3Schema", "properties[c]", "default", "foo"), invalid("spec", "validation", "openAPIV3Schema", "properties[d]", "default", "bad"), - invalid("spec", "validation", "openAPIV3Schema", "properties[d]", "properties[bad]", "pattern"), // we also expected unpruned and valid defaults under x-kubernetes-preserve-unknown-fields. We could be more // strict here, but want to encourage proper specifications by forbidding other defaults. invalid("spec", "validation", "openAPIV3Schema", "properties[e]", "properties[preserveUnknownFields]", "default"), invalid("spec", "validation", "openAPIV3Schema", "properties[e]", "properties[nestedProperties]", "default"), - invalid("spec", "validation", "openAPIV3Schema", "properties[embedded-preserve-unpruned-objectmeta]", "default"), }, enabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, @@ -2249,7 +2248,8 @@ func TestValidateCustomResourceDefinition(t *testing.T) { enabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, }, { - name: "metadata defaults", + // TODO: remove in a follow-up. This blocks is here for easy review. + name: "v1.15 era tests for metadata defaults", resource: &apiextensions.CustomResourceDefinition{ ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"}, Spec: apiextensions.CustomResourceDefinitionSpec{ @@ -2279,7 +2279,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) { Type: "object", Default: jsonPtr(map[string]interface{}{ "name": "foo", - // TODO: forbid unknown field under metadata + // allow: unknown fields under metadata are not rejected during CRD validation, but only pruned in storage creation "unknown": int64(42), }), }, @@ -2352,14 +2352,14 @@ func TestValidateCustomResourceDefinition(t *testing.T) { }, "kind": { Type: "string", - // TODO: forbid non-validating nested values in metadata + // invalid: non-validating value in TypeMeta Default: jsonPtr("%"), }, "metadata": { Type: "object", Default: jsonPtr(map[string]interface{}{ "labels": map[string]interface{}{ - // TODO: forbid non-validating nested field in meta + // invalid: non-validating nested field in ObjectMeta "bar": "x y", }, }), @@ -2387,26 +2387,16 @@ func TestValidateCustomResourceDefinition(t *testing.T) { Properties: map[string]apiextensions.JSONSchemaProps{ "name": { Type: "string", - // TODO: forbid wrongly typed nested fields in metadata - Default: jsonPtr("%"), + // invalid: wrongly typed nested fields in ObjectMeta + Default: jsonPtr(int64(42)), }, "labels": { Type: "object", Properties: map[string]apiextensions.JSONSchemaProps{ "bar": { Type: "string", - // TODO: forbid non-validating nested fields in metadata - Default: jsonPtr("x y"), - }, - }, - }, - "annotations": { - Type: "object", - AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ - Schema: &apiextensions.JSONSchemaProps{ - Type: "string", - // forbidden: no default under additionalProperties inside of metadata - Default: jsonPtr("abc"), + // invalid: wrong typed nested fields in ObjectMeta + Default: jsonPtr(int64(42)), }, }, }, @@ -2435,23 +2425,1508 @@ func TestValidateCustomResourceDefinition(t *testing.T) { errors: []validationMatch{ // Forbidden: must not be set in top-level metadata forbidden("spec", "versions[0]", "schema", "openAPIV3Schema", "properties[metadata]", "default"), - // Invalid value: map[string]interface {}{"name":"foo", "unknown":42}: must not have unknown fields - // TODO: invalid("spec", "versions[0]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "default"), // Forbidden: must not be set in top-level metadata forbidden("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[metadata]", "properties[name]", "default"), // Invalid value: "x y" - // TODO: invalid("spec", "versions[2]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "default"), + invalid("spec", "versions[2]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "default"), // Invalid value: "%": kind: Invalid value: "%" - // TODO: invalid("spec", "versions[2]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[kind]", "default"), + invalid("spec", "versions[2]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[kind]", "default"), - // Invalid value: "%" - // TODO: invalid("spec", "versions[3]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "properties[labels]", "properties[bar]", "default"), - // Invalid value: "x y" - // TODO: invalid("spec", "versions[3]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "properties[name]", "default"), + // Invalid value: wrongly typed + invalid("spec", "versions[3]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "properties[labels]", "properties[bar]", "default"), + // Invalid value: wrongly typed + invalid("spec", "versions[3]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "properties[name]", "default"), + }, + enabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, + }, + { + name: "default inside additionalSchema", + resource: &apiextensions.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"}, + Spec: apiextensions.CustomResourceDefinitionSpec{ + Group: "group.com", + Version: "v1", + Versions: []apiextensions.CustomResourceDefinitionVersion{ + { + Name: "v1", + Served: true, + Storage: true, + }, + }, + Validation: &apiextensions.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "annotations": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + // forbidden: no default under additionalProperties inside of metadata + Default: jsonPtr("abc"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Scope: apiextensions.NamespaceScoped, + Names: apiextensions.CustomResourceDefinitionNames{ + Plural: "plural", + Singular: "singular", + Kind: "Plural", + ListKind: "PluralList", + }, + PreserveUnknownFields: pointer.BoolPtr(false), + }, + Status: apiextensions.CustomResourceDefinitionStatus{ + StoredVersions: []string{"v1"}, + }, + }, + errors: []validationMatch{ // Forbidden: must not be set inside additionalProperties applying to object metadata - forbidden("spec", "versions[3]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "properties[annotations]", "additionalProperties", "default"), + forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "properties[annotations]", "additionalProperties", "default"), + }, + enabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, + }, + { + name: "top-level metadata default", + resource: &apiextensions.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"}, + Spec: apiextensions.CustomResourceDefinitionSpec{ + Group: "group.com", + Version: "v1", + Versions: []apiextensions.CustomResourceDefinitionVersion{ + { + Name: "v1", + Served: true, + Storage: true, + }, + }, + Validation: &apiextensions.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "metadata": { + Type: "object", + // forbidden: no default for top-level metadata + Default: jsonPtr(map[string]interface{}{ + "name": "foo", + }), + }, + }, + }, + }, + Scope: apiextensions.NamespaceScoped, + Names: apiextensions.CustomResourceDefinitionNames{ + Plural: "plural", + Singular: "singular", + Kind: "Plural", + ListKind: "PluralList", + }, + PreserveUnknownFields: pointer.BoolPtr(false), + }, + Status: apiextensions.CustomResourceDefinitionStatus{ + StoredVersions: []string{"v1"}, + }, + }, + errors: []validationMatch{ + forbidden("spec", "validation", "openAPIV3Schema", "properties[metadata]", "default"), + }, + enabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, + }, + { + name: "embedded metadata defaults", + resource: &apiextensions.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"}, + Spec: apiextensions.CustomResourceDefinitionSpec{ + Group: "group.com", + Version: "v1", + Versions: []apiextensions.CustomResourceDefinitionVersion{ + { + Name: "v1", + Served: true, + Storage: true, + }, + }, + Validation: &apiextensions.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "metadata": { + Type: "object", + Default: jsonPtr(map[string]interface{}{ + "name": "foo", + }), + }, + }, + }, + + "allowed-in-object-defaults": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Default: jsonPtr("v1"), + }, + "kind": { + Type: "string", + Default: jsonPtr("Pod"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Default: jsonPtr("foo"), + }, + }, + // allowed: unknown fields outside metadata + Default: jsonPtr(map[string]interface{}{ + "unknown": int64(42), + }), + }, + }, + }, + "allowed-object-defaults": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + XEmbeddedResource: true, + Default: jsonPtr(map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": "foo", + "unknown": int64(42), + }, + }), + }, + "allowed-spanning-object-defaults": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": "foo", + "unknown": int64(42), + }, + }, + }), + }, + + "unknown-field-object-defaults": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + Default: jsonPtr(map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": "foo", + // allowed: unspecified field in ObjectMeta + "unknown": int64(42), + }, + // forbidden: unspecified field + "unknown": int64(42), + }), + }, + "unknown-field-spanning-object-defaults": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": "foo", + // allowed: unspecified field in ObjectMeta + "unknown": int64(42), + }, + // forbidden: unspecified field + "unknown": int64(42), + }, + // forbidden: unspecified field + "unknown": int64(42), + }), + }, + + "x-preserve-unknown-fields-unknown-field-object-defaults": { + Type: "object", + XEmbeddedResource: true, + XPreserveUnknownFields: pointer.BoolPtr(true), + Properties: map[string]apiextensions.JSONSchemaProps{}, + Default: jsonPtr(map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": "foo", + // allowed: unspecified field in ObjectMeta + "unknown": int64(42), + }, + // allowed: because x-kubernetes-preserve-unknown-fields: true + "unknown": int64(42), + }), + }, + "x-preserve-unknown-fields-unknown-field-spanning-object-defaults": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + XPreserveUnknownFields: pointer.BoolPtr(true), + Properties: map[string]apiextensions.JSONSchemaProps{}, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": "foo", + // allowed: unspecified field in ObjectMeta + "unknown": int64(42), + }, + // allowed: because x-kubernetes-preserve-unknown-fields: true + "unknown": int64(42), + }, + }), + }, + "x-preserve-unknown-fields-unknown-field-outside": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + XPreserveUnknownFields: pointer.BoolPtr(true), + Properties: map[string]apiextensions.JSONSchemaProps{}, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": "foo", + // allowed: unspecified field in ObjectMeta + "unknown": int64(42), + }, + // allowed: because x-kubernetes-preserve-unknown-fields: true + "unknown": int64(42), + }, + // forbidden: unspecified field + "unknown": int64(42), + }), + }, + + "wrongly-typed-in-object-defaults": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + // invalid: wrong type + Default: jsonPtr(int64(42)), + }, + "kind": { + Type: "string", + // invalid: wrong type + Default: jsonPtr(int64(42)), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + // invalid: wrong type + Default: jsonPtr(int64(42)), + }, + "annotations": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + }, + }, + // invalid: wrong type + Default: jsonPtr(int64(42)), + }, + }, + }, + }, + }, + "wrongly-typed-object-defaults-apiVersion": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + Default: jsonPtr(map[string]interface{}{ + // invalid: wrong type + "apiVersion": int64(42), + }), + }, + "wrongly-typed-object-defaults-kind": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + Default: jsonPtr(map[string]interface{}{ + // invalid: wrong type + "kind": int64(42), + }), + }, + "wrongly-typed-object-defaults-name": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + Default: jsonPtr(map[string]interface{}{ + "metadata": map[string]interface{}{ + // invalid: wrong type + "name": int64(42), + }, + }), + }, + "wrongly-typed-object-defaults-labels": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + Default: jsonPtr(map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + // invalid: wrong type + "foo": int64(42), + }, + }, + }), + }, + "wrongly-typed-object-defaults-annotations": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + Default: jsonPtr(map[string]interface{}{ + "metadata": map[string]interface{}{ + // invalid: wrong type + "annotations": int64(42), + }, + }), + }, + "wrongly-typed-object-defaults-metadata": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + Default: jsonPtr(map[string]interface{}{ + // invalid: wrong type + "metadata": int64(42), + }), + }, + + "wrongly-typed-spanning-object-defaults-apiVersion": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + // invalid: wrong type + "apiVersion": int64(42), + }, + }), + }, + "wrongly-typed-spanning-object-defaults-kind": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + // invalid: wrong type + "kind": int64(42), + }, + }), + }, + "wrongly-typed-spanning-object-defaults-name": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": int64(42), + }, + }, + }), + }, + "wrongly-typed-spanning-object-defaults-labels": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + // invalid: wrong type + "foo": int64(42), + }, + }, + }, + }), + }, + "wrongly-typed-spanning-object-defaults-annotations": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "metadata": map[string]interface{}{ + // invalid: wrong type + "annotations": int64(42), + }, + }, + }), + }, + "wrongly-typed-spanning-object-defaults-metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "metadata": int64(42), + }, + }), + }, + + "invalid-in-object-defaults": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "kind": { + Type: "string", + // invalid + Default: jsonPtr("%"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + // invalid + Default: jsonPtr("%"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + }, + }, + // invalid + Default: jsonPtr(map[string]interface{}{ + "foo": "x y", + }), + }, + }, + }, + }, + }, + "invalid-object-defaults-kind": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + Default: jsonPtr(map[string]interface{}{ + "apiVersion": "foo/v1", + // invalid: wrongly typed + "kind": "%", + }), + }, + "invalid-object-defaults-name": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + Default: jsonPtr(map[string]interface{}{ + "apiVersion": "foo/v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + // invalid: wrongly typed + "name": "%", + }, + }), + }, + "invalid-object-defaults-labels": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + Default: jsonPtr(map[string]interface{}{ + "apiVersion": "foo/v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + // invalid: wrongly typed + "foo": "x y", + }, + }, + }), + }, + "invalid-spanning-object-defaults-kind": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "apiVersion": "foo/v1", + // invalid: wrongly typed + "kind": "%", + }, + }), + }, + "invalid-spanning-object-defaults-name": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "apiVersion": "foo/v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + // invalid: wrongly typed + "name": "%", + }, + }, + }), + }, + "invalid-spanning-object-defaults-labels": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "something": { + Type: "string", + }, + }, + }, + }, + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "apiVersion": "foo/v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + // invalid: wrongly typed + "foo": "x y", + }, + }, + }, + }), + }, + + "in-object-defaults-with-valid-constraints": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + // valid + Default: jsonPtr("foo/v1"), + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + // valid + Default: jsonPtr("Foo"), + Enum: jsonSlice("Foo"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + // valid + Default: jsonPtr("foo"), + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + // valid + Default: jsonPtr(map[string]interface{}{ + "foo": "foo", + }), + }, + }, + }, + }, + }, + "metadata-defaults-with-valid-constraints": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + // valid + Default: jsonPtr(map[string]interface{}{ + "name": "foo", + "labels": map[string]interface{}{ + "foo": "foo", + }, + }), + }, + }, + }, + "object-defaults-with-valid-constraints": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + Enum: jsonSlice("Foo"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + }, + }, + // valid + Default: jsonPtr(map[string]interface{}{ + "apiVersion": "foo/v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + "name": "foo", + "labels": map[string]interface{}{ + "foo": "foo", + }, + }, + }), + }, + "spanning-defaults-with-valid-constraints": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + Enum: jsonSlice("Foo"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + }, + }, + }, + }, + // valid + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "apiVersion": "foo/v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + "name": "foo", + "labels": map[string]interface{}{ + "foo": "foo", + }, + }, + }, + }), + }, + + "in-object-defaults-with-invalid-constraints": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Description: "BREAK", + // invalid + Default: jsonPtr("bar/v1"), + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + // invalid + Default: jsonPtr("Bar"), + Enum: jsonSlice("Foo"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + // invalid + Default: jsonPtr("bar"), + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + // invalid + Default: jsonPtr(map[string]interface{}{ + "foo": "bar", + }), + }, + }, + }, + }, + }, + "metadata-defaults-with-invalid-constraints-name": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + // invalid name + Default: jsonPtr(map[string]interface{}{ + "name": "bar", + }), + }, + }, + }, + "metadata-defaults-with-invalid-constraints-labels": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + // invalid labels + Default: jsonPtr(map[string]interface{}{ + "name": "foo", + "labels": map[string]interface{}{ + "foo": "bar", + }, + }), + }, + }, + }, + "object-defaults-with-invalid-constraints-name": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + Enum: jsonSlice("Foo"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + }, + }, + // invalid + Default: jsonPtr(map[string]interface{}{ + "apiVersion": "foo/v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + "name": "bar", + }, + }), + }, + "object-defaults-with-invalid-constraints-labels": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + Enum: jsonSlice("Foo"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + }, + }, + // invalid + Default: jsonPtr(map[string]interface{}{ + "apiVersion": "foo/v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + "name": "foo", + "labels": map[string]interface{}{ + "foo": "bar", + }, + }, + }), + }, + "object-defaults-with-invalid-constraints-apiVersion": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + Enum: jsonSlice("Foo"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + }, + }, + // invalid + Default: jsonPtr(map[string]interface{}{ + "apiVersion": "bar/v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + "name": "foo", + }, + }), + }, + "object-defaults-with-invalid-constraints-kind": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + Enum: jsonSlice("Foo"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + }, + }, + // invalid + Default: jsonPtr(map[string]interface{}{ + "apiVersion": "foo/v1", + "kind": "Bar", + "metadata": map[string]interface{}{ + "name": "foo", + }, + }), + }, + "spanning-defaults-with-invalid-constraints-name": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + Enum: jsonSlice("Foo"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + }, + }, + }, + }, + // invalid + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "apiVersion": "foo/v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + "name": "bar", + "labels": map[string]interface{}{ + "foo": "foo", + }, + }, + }, + }), + }, + "spanning-defaults-with-invalid-constraints-labels": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + Enum: jsonSlice("Foo"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + }, + }, + }, + }, + // invalid + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "apiVersion": "foo/v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + "name": "foo", + "labels": map[string]interface{}{ + "foo": "bar", + }, + }, + }, + }), + }, + "spanning-defaults-with-invalid-constraints-apiVersion": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + Enum: jsonSlice("Foo"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + }, + }, + }, + }, + // invalid + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "apiVersion": "bar/v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + "name": "foo", + "labels": map[string]interface{}{ + "foo": "foo", + }, + }, + }, + }), + }, + "spanning-defaults-with-invalid-constraints-kind": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + Enum: jsonSlice("Foo"), + }, + "metadata": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "name": { + Type: "string", + Enum: jsonSlice("foo"), + }, + "labels": { + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Type: "string", + Enum: jsonSlice("foo"), + }, + }, + }, + }, + }, + }, + }, + }, + // invalid + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "apiVersion": "foo/v1", + "kind": "Bar", + "metadata": map[string]interface{}{ + "name": "foo", + "labels": map[string]interface{}{ + "foo": "foo", + }, + }, + }, + }), + }, + + "object-defaults-with-missing-typemeta": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + Enum: jsonSlice("Foo"), + }, + }, + // invalid: kind and apiVersion are missing + Default: jsonPtr(map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "bar", + }, + }), + }, + "spanning-defaults-with-missing-typemeta": { + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "embedded": { + Type: "object", + XEmbeddedResource: true, + Properties: map[string]apiextensions.JSONSchemaProps{ + "apiVersion": { + Type: "string", + Enum: jsonSlice("foo/v1"), + }, + "kind": { + Type: "string", + Enum: jsonSlice("Foo"), + }, + }, + }, + }, + // invalid: kind and apiVersion are missing + Default: jsonPtr(map[string]interface{}{ + "embedded": map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "bar", + }, + }, + }), + }, + }, + }, + }, + Scope: apiextensions.NamespaceScoped, + Names: apiextensions.CustomResourceDefinitionNames{ + Plural: "plural", + Singular: "singular", + Kind: "Plural", + ListKind: "PluralList", + }, + PreserveUnknownFields: pointer.BoolPtr(false), + }, + Status: apiextensions.CustomResourceDefinitionStatus{ + StoredVersions: []string{"v1"}, + }, + }, + errors: []validationMatch{ + invalid("spec", "validation", "openAPIV3Schema", "properties[unknown-field-object-defaults]", "default"), + invalid("spec", "validation", "openAPIV3Schema", "properties[unknown-field-spanning-object-defaults]", "default"), + + invalid("spec", "validation", "openAPIV3Schema", "properties[x-preserve-unknown-fields-unknown-field-outside]", "default"), + + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-in-object-defaults]", "properties[kind]", "default"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-in-object-defaults]", "properties[apiVersion]", "default"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-in-object-defaults]", "properties[metadata]", "properties[name]", "default"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-in-object-defaults]", "properties[metadata]", "properties[annotations]", "default"), + + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-metadata]", "default", "metadata"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-apiVersion]", "default", "apiVersion"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-kind]", "default", "kind"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-name]", "default", "metadata"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-labels]", "default", "metadata"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-annotations]", "default", "metadata"), + + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-metadata]", "default", "embedded", "metadata"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-apiVersion]", "default", "embedded", "apiVersion"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-kind]", "default", "embedded", "kind"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-name]", "default", "embedded", "metadata"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-labels]", "default", "embedded", "metadata"), + invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-annotations]", "default", "embedded", "metadata"), + + invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-in-object-defaults]", "properties[metadata]", "properties[name]", "default"), + invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-in-object-defaults]", "properties[metadata]", "properties[labels]", "default"), + invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-in-object-defaults]", "properties[kind]", "default"), + + invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-object-defaults-kind]", "default", "kind"), + invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-object-defaults-name]", "default", "metadata", "name"), + invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-object-defaults-labels]", "default", "metadata", "labels"), + + invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-spanning-object-defaults-kind]", "default", "embedded", "kind"), + invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-spanning-object-defaults-name]", "default", "embedded", "metadata", "name"), + invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-spanning-object-defaults-labels]", "default", "embedded", "metadata", "labels"), + + unsupported("spec", "validation", "openAPIV3Schema", "properties[in-object-defaults-with-invalid-constraints]", "properties[apiVersion]", "default"), + unsupported("spec", "validation", "openAPIV3Schema", "properties[in-object-defaults-with-invalid-constraints]", "properties[kind]", "default"), + unsupported("spec", "validation", "openAPIV3Schema", "properties[in-object-defaults-with-invalid-constraints]", "properties[metadata]", "properties[name]", "default"), + unsupported("spec", "validation", "openAPIV3Schema", "properties[in-object-defaults-with-invalid-constraints]", "properties[metadata]", "properties[labels]", "default", "foo"), + + unsupported("spec", "validation", "openAPIV3Schema", "properties[metadata-defaults-with-invalid-constraints-name]", "properties[metadata]", "default", "name"), + unsupported("spec", "validation", "openAPIV3Schema", "properties[metadata-defaults-with-invalid-constraints-labels]", "properties[metadata]", "default", "labels", "foo"), + + unsupported("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-invalid-constraints-name]", "default", "metadata", "name"), + unsupported("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-invalid-constraints-labels]", "default", "metadata", "labels", "foo"), + unsupported("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-invalid-constraints-apiVersion]", "default", "apiVersion"), + unsupported("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-invalid-constraints-kind]", "default", "kind"), + + unsupported("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-invalid-constraints-kind]", "default", "embedded", "kind"), + unsupported("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-invalid-constraints-labels]", "default", "embedded", "metadata", "labels", "foo"), + unsupported("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-invalid-constraints-apiVersion]", "default", "embedded", "apiVersion"), + unsupported("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-invalid-constraints-name]", "default", "embedded", "metadata", "name"), + + required("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-missing-typemeta]", "default", "apiVersion"), + required("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-missing-typemeta]", "default", "kind"), + + required("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-missing-typemeta]", "default", "embedded", "apiVersion"), + required("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-missing-typemeta]", "default", "embedded", "kind"), }, enabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, }, @@ -4922,3 +6397,14 @@ func jsonPtr(x interface{}) *apiextensions.JSON { ret := apiextensions.JSON(x) return &ret } + +func jsonSlice(l ...interface{}) []apiextensions.JSON { + if len(l) == 0 { + return nil + } + ret := make([]apiextensions.JSON, 0, len(l)) + for _, x := range l { + ret = append(ret, x) + } + return ret +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert.go index eae33266bf9..3025d39e004 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert.go @@ -248,7 +248,7 @@ func newExtensions(s *apiextensions.JSONSchemaProps) (*Extensions, error) { if s.XPreserveUnknownFields != nil { if !*s.XPreserveUnknownFields { - return nil, fmt.Errorf("'x-kubernetes-preserve-unknown-fields' must be true or undefined") + return nil, fmt.Errorf("internal error: 'x-kubernetes-preserve-unknown-fields' must be true or undefined") } ret.XPreserveUnknownFields = true } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/BUILD index b2168fca3a8..6c01389d9e5 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/BUILD @@ -2,13 +2,24 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", - srcs = ["algorithm.go"], + srcs = [ + "algorithm.go", + "surroundingobject.go", + "validation.go", + ], importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting", importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting", visibility = ["//visibility:public"], deps = [ "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library", + "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta:go_default_library", + "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning:go_default_library", + "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/github.com/go-openapi/strfmt:go_default_library", + "//vendor/github.com/go-openapi/validate:go_default_library", ], ) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/surroundingobject.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/surroundingobject.go new file mode 100644 index 00000000000..cb75c2b9fd3 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/surroundingobject.go @@ -0,0 +1,147 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package defaulting + +import ( + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// AccessorFunc returns a node x in obj on a fixed (implicitly encoded) JSON path +// if that path exists in obj (found==true). If it does not exist, found is false. +// If on the path the type of a field is wrong, an error is returned. +type AccessorFunc func(obj map[string]interface{}) (x interface{}, found bool, err error) + +// SurroundingObjectFunc is a surrounding object builder with a given x at a leaf. +// Which leave is determined by the series of Index() and Child(k) calls. +// It also returns the inverse of the builder, namely the accessor that extracts x +// from the test object. +// +// With obj, acc, _ := someSurroundingObjectFunc(x) we get: +// +// acc(obj) == x +// reflect.DeepEqual(acc(DeepCopy(obj), x) == x +// +// where x is the original instance for slices and maps. +// +// If after computation of acc the node holding x in obj is mutated (e.g. pruned), +// the accessor will return that mutated node value (e.g. the pruned x). +// +// Example (ignoring the last two return values): +// +// NewRootObjectFunc()(x) == x +// NewRootObjectFunc().Index()(x) == [x] +// NewRootObjectFunc().Index().Child("foo") == [{"foo": x}] +// NewRootObjectFunc().Index().Child("foo").Child("bar") == [{"foo": {"bar":x}}] +// NewRootObjectFunc().Index().Child("foo").Child("bar").Index() == [{"foo": {"bar":[x]}}] +// +// and: +// +// NewRootObjectFunc(), then acc(x) == x +// NewRootObjectFunc().Index(), then acc([x]) == x +// NewRootObjectFunc().Index().Child("foo"), then acc([{"foo": x}]) == x +// NewRootObjectFunc().Index().Child("foo").Child("bar"), then acc([{"foo": {"bar":x}}]) == x +// NewRootObjectFunc().Index().Child("foo").Child("bar").Index(), then acc([{"foo": {"bar":[x]}}]) == x +type SurroundingObjectFunc func(focus interface{}) (map[string]interface{}, AccessorFunc, error) + +// NewRootObjectFunc returns the identity function. The passed focus value +// must be an object. +func NewRootObjectFunc() SurroundingObjectFunc { + return func(x interface{}) (map[string]interface{}, AccessorFunc, error) { + obj, ok := x.(map[string]interface{}) + if !ok { + return nil, nil, fmt.Errorf("object root default value must be of object type") + } + return obj, func(root map[string]interface{}) (interface{}, bool, error) { + return root, true, nil + }, nil + } +} + +// WithTypeMeta returns a closure with the TypeMeta fields set if they are defined. +// This mutates f(x). +func (f SurroundingObjectFunc) WithTypeMeta(meta metav1.TypeMeta) SurroundingObjectFunc { + return func(x interface{}) (map[string]interface{}, AccessorFunc, error) { + obj, acc, err := f(x) + if err != nil { + return nil, nil, err + } + if obj == nil { + obj = map[string]interface{}{} + } + if _, found := obj["kind"]; !found { + obj["kind"] = meta.Kind + } + if _, found := obj["apiVersion"]; !found { + obj["apiVersion"] = meta.APIVersion + } + return obj, acc, err + } +} + +// Child returns a function x => f({k: x}) and the corresponding accessor. +func (f SurroundingObjectFunc) Child(k string) SurroundingObjectFunc { + return func(x interface{}) (map[string]interface{}, AccessorFunc, error) { + obj, acc, err := f(map[string]interface{}{k: x}) + if err != nil { + return nil, nil, err + } + return obj, func(obj map[string]interface{}) (interface{}, bool, error) { + x, found, err := acc(obj) + if err != nil { + return nil, false, fmt.Errorf(".%s%v", k, err) + } + if !found { + return nil, false, nil + } + if x, ok := x.(map[string]interface{}); !ok { + return nil, false, fmt.Errorf(".%s must be of object type", k) + } else if v, found := x[k]; !found { + return nil, false, nil + } else { + return v, true, nil + } + }, err + } +} + +// Index returns a function x => f([x]) and the corresponding accessor. +func (f SurroundingObjectFunc) Index() SurroundingObjectFunc { + return func(focus interface{}) (map[string]interface{}, AccessorFunc, error) { + obj, acc, err := f([]interface{}{focus}) + if err != nil { + return nil, nil, err + } + return obj, func(obj map[string]interface{}) (interface{}, bool, error) { + x, found, err := acc(obj) + if err != nil { + return nil, false, fmt.Errorf("[]%v", err) + } + if !found { + return nil, false, nil + } + if x, ok := x.([]interface{}); !ok { + return nil, false, fmt.Errorf("[] must be of array type") + } else if len(x) == 0 { + return nil, false, nil + } else { + return x[0], true, nil + } + }, err + } +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/validation.go new file mode 100644 index 00000000000..d65b47c98fb --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/validation.go @@ -0,0 +1,131 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package defaulting + +import ( + "fmt" + "reflect" + + "github.com/go-openapi/strfmt" + goopenapivalidate "github.com/go-openapi/validate" + + structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" + schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta" + "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning" + apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// ValidateDefaults checks that default values validate and are properly pruned. +func ValidateDefaults(pth *field.Path, s *structuralschema.Structural, isResourceRoot bool) (field.ErrorList, error) { + f := NewRootObjectFunc().WithTypeMeta(metav1.TypeMeta{APIVersion: "validation/v1", Kind: "Validation"}) + + if isResourceRoot { + if s == nil { + s = &structuralschema.Structural{} + } + if !s.XEmbeddedResource { + clone := *s + clone.XEmbeddedResource = true + s = &clone + } + } + + return validate(pth, s, s, f, false) +} + +// validate is the recursive step func for the validation. insideMeta is true if s specifies +// TypeMeta or ObjectMeta. The SurroundingObjectFunc f is used to validate defaults of +// TypeMeta or ObjectMeta fields. +func validate(pth *field.Path, s *structuralschema.Structural, rootSchema *structuralschema.Structural, f SurroundingObjectFunc, insideMeta bool) (field.ErrorList, error) { + if s == nil { + return nil, nil + } + + if s.XEmbeddedResource { + insideMeta = false + f = NewRootObjectFunc().WithTypeMeta(metav1.TypeMeta{APIVersion: "validation/v1", Kind: "Validation"}) + rootSchema = s + } + + allErrs := field.ErrorList{} + + if s.Default.Object != nil { + validator := goopenapivalidate.NewSchemaValidator(s.ToGoOpenAPI(), nil, "", strfmt.Default) + + if insideMeta { + obj, _, err := f(runtime.DeepCopyJSONValue(s.Default.Object)) + if err != nil { + // this should never happen. f(s.Default.Object) only gives an error if f is the + // root object func, but the default value is not a map. But then we wouldn't be + // in this case. + return nil, fmt.Errorf("failed to validate default value inside metadata: %v", err) + } + + // check ObjectMeta/TypeMeta and everything else + if err := schemaobjectmeta.Coerce(nil, obj, rootSchema, true, false); err != nil { + allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", err))) + } else if errs := schemaobjectmeta.Validate(nil, obj, rootSchema, true); len(errs) > 0 { + allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", errs.ToAggregate()))) + } else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 { + allErrs = append(allErrs, errs...) + } + } else { + // check whether default is pruned + pruned := runtime.DeepCopyJSONValue(s.Default.Object) + pruning.Prune(pruned, s, s.XEmbeddedResource) + if !reflect.DeepEqual(pruned, s.Default.Object) { + allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, "must not have unknown fields")) + } + + // check ObjectMeta/TypeMeta and everything else + if err := schemaobjectmeta.Coerce(pth.Child("default"), s.Default.Object, s, s.XEmbeddedResource, false); err != nil { + allErrs = append(allErrs, err) + } else if errs := schemaobjectmeta.Validate(pth.Child("default"), s.Default.Object, s, s.XEmbeddedResource); len(errs) > 0 { + allErrs = append(allErrs, errs...) + } else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 { + allErrs = append(allErrs, errs...) + } + } + } + + // do not follow additionalProperties because defaults are forbidden there + + if s.Items != nil { + errs, err := validate(pth.Child("items"), s.Items, rootSchema, f.Index(), insideMeta) + if err != nil { + return nil, err + } + allErrs = append(allErrs, errs...) + } + + for k, subSchema := range s.Properties { + subInsideMeta := insideMeta + if s.XEmbeddedResource && (k == "metadata" || k == "apiVersion" || k == "kind") { + subInsideMeta = true + } + errs, err := validate(pth.Child("properties").Key(k), &subSchema, rootSchema, f.Child(k), subInsideMeta) + if err != nil { + return nil, err + } + allErrs = append(allErrs, errs...) + } + + return allErrs, nil +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning/algorithm.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning/algorithm.go index 5a6a8c7c8c4..a1fd711c6a5 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning/algorithm.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning/algorithm.go @@ -21,7 +21,8 @@ import ( ) // Prune removes object fields in obj which are not specified in s. It skips TypeMeta and ObjectMeta fields -// if XEmbeddedResource is set to true, or for the root if isResourceRoot=true. +// if XEmbeddedResource is set to true, or for the root if isResourceRoot=true, i.e. it does not +// prune unknown metadata fields. func Prune(obj interface{}, s *structuralschema.Structural, isResourceRoot bool) { if isResourceRoot { if s == nil { 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 3c057c7e414..487731fd887 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 @@ -60,7 +60,7 @@ const ( // * every specified field or array in s is also specified outside of value validation. // * metadata at the root can only restrict the name and generateName, and not be specified at all in nested contexts. // * additionalProperties at the root is not allowed. -func ValidateStructural(s *Structural, fldPath *field.Path) field.ErrorList { +func ValidateStructural(fldPath *field.Path, s *Structural) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validateStructuralInvariants(s, rootLevel, fldPath)...) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema/nonstructuralschema_controller.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema/nonstructuralschema_controller.go index fb473e90a37..1c090be2fde 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema/nonstructuralschema_controller.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema/nonstructuralschema_controller.go @@ -97,7 +97,7 @@ func calculateCondition(in *apiextensions.CustomResourceDefinition) *apiextensio pth := field.NewPath("spec", "validation", "openAPIV3Schema") - allErrs = append(allErrs, schema.ValidateStructural(s, pth)...) + allErrs = append(allErrs, schema.ValidateStructural(pth, s)...) } for _, v := range in.Spec.Versions { @@ -114,7 +114,7 @@ func calculateCondition(in *apiextensions.CustomResourceDefinition) *apiextensio pth := field.NewPath("spec", "version").Key(v.Name).Child("schema", "openAPIV3Schema") - allErrs = append(allErrs, schema.ValidateStructural(s, pth)...) + allErrs = append(allErrs, schema.ValidateStructural(pth, s)...) } if len(allErrs) == 0 { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder_test.go index 69eb3964b75..cbe045e9334 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder_test.go @@ -362,7 +362,7 @@ func TestNewBuilder(t *testing.T) { if err != nil { t.Fatalf("structural schema error: %v", err) } - if errs := structuralschema.ValidateStructural(schema, nil); len(errs) > 0 { + if errs := structuralschema.ValidateStructural(nil, schema); len(errs) > 0 { t.Fatalf("structural schema validation error: %v", errs.ToAggregate()) } schema = schema.Unfold() diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/objectmeta_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/objectmeta_test.go index c2515d3a842..63577889deb 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/objectmeta_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/objectmeta_test.go @@ -308,20 +308,6 @@ properties: kind: Pod labels: foo: bar - invalidDefaults: - type: object - properties: - embedded: - type: object - x-kubernetes-embedded-resource: true - x-kubernetes-preserve-unknown-fields: true - default: - apiVersion: "foo/v1" - kind: "%" - metadata: - labels: - foo: bar - abc: "x y" ` embeddedResourceInstance = ` @@ -501,8 +487,6 @@ func TestEmbeddedResources(t *testing.T) { ` embeddedNested.metadata.name: Invalid value: ".."`, ` embeddedNested.embedded.kind: Invalid value: "%"`, ` embeddedNested.embedded.metadata.name: Invalid value: ".."`, - ` invalidDefaults.embedded.kind: Invalid value: "%"`, - ` invalidDefaults.embedded.metadata.labels: Invalid value: "x y"`, } for _, s := range invalidErrors { if !strings.Contains(err.Error(), s) {