diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/BUILD index aed96f7fe35..6226496a6ce 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/BUILD @@ -51,6 +51,7 @@ go_test( "//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/require:go_default_library", + "//vendor/k8s.io/utils/pointer:go_default_library", ], ) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go index d13e0437c26..b77128cb13f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go @@ -334,12 +334,12 @@ func (b *builder) buildRoute(root, path, httpMethod, actionVerb, operationVerb s // buildKubeNative builds input schema with Kubernetes' native object meta, type meta and // extensions -func (b *builder) buildKubeNative(schema *structuralschema.Structural, v2 bool) (ret *spec.Schema) { +func (b *builder) buildKubeNative(schema *structuralschema.Structural, v2 bool, crdPreserveUnknownFields bool) (ret *spec.Schema) { // only add properties if we have a schema. Otherwise, kubectl would (wrongly) assume additionalProperties=false // and forbid anything outside of apiVersion, kind and metadata. We have to fix kubectl to stop doing this, e.g. by // adding additionalProperties=true support to explicitly allow additional fields. // TODO: fix kubectl to understand additionalProperties=true - if schema == nil || (v2 && schema.XPreserveUnknownFields) { + if schema == nil || (v2 && (schema.XPreserveUnknownFields || crdPreserveUnknownFields)) { ret = &spec.Schema{ SchemaProps: spec.SchemaProps{Type: []string{"object"}}, } @@ -506,7 +506,8 @@ func newBuilder(crd *apiextensions.CustomResourceDefinition, version string, sch } // Pre-build schema with Kubernetes native properties - b.schema = b.buildKubeNative(schema, v2) + preserveUnknownFields := crd.Spec.PreserveUnknownFields != nil && *crd.Spec.PreserveUnknownFields + b.schema = b.buildKubeNative(schema, v2, preserveUnknownFields) b.listSchema = b.buildListSchema() return b 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 2f067112be9..65d1b15aa3e 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 @@ -33,6 +33,7 @@ import ( "k8s.io/apiserver/pkg/endpoints" "k8s.io/apiserver/pkg/features" utilfeature "k8s.io/apiserver/pkg/util/feature" + utilpointer "k8s.io/utils/pointer" ) func TestNewBuilder(t *testing.T) { @@ -554,50 +555,65 @@ func schemaDiff(a, b *spec.Schema) string { func TestBuildSwagger(t *testing.T) { tests := []struct { - name string - schema string - wantedSchema string - opts Options + name string + schema string + preserveUnknownFields *bool + wantedSchema string + opts Options }{ { "nil", "", + nil, `{"type":"object","x-kubernetes-group-version-kind":[{"group":"bar.k8s.io","kind":"Foo","version":"v1"}]}`, Options{V2: true, StripDefaults: true}, }, { "with properties", `{"type":"object","properties":{"spec":{"type":"object"},"status":{"type":"object"}}}`, + nil, `{"type":"object","properties":{"apiVersion":{"type":"string"},"kind":{"type":"string"},"metadata":{"$ref":"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"},"spec":{"type":"object"},"status":{"type":"object"}},"x-kubernetes-group-version-kind":[{"group":"bar.k8s.io","kind":"Foo","version":"v1"}]}`, Options{V2: true, StripDefaults: true}, }, { "with invalid-typed properties", `{"type":"object","properties":{"spec":{"type":"bug"},"status":{"type":"object"}}}`, + nil, + `{"type":"object","x-kubernetes-group-version-kind":[{"group":"bar.k8s.io","kind":"Foo","version":"v1"}]}`, + Options{V2: true, StripDefaults: true}, + }, + { + "with spec.preseveUnknownFields=true", + `{"type":"object","properties":{"foo":{"type":"string"}}}`, + utilpointer.BoolPtr(true), `{"type":"object","x-kubernetes-group-version-kind":[{"group":"bar.k8s.io","kind":"Foo","version":"v1"}]}`, Options{V2: true, StripDefaults: true}, }, { "with stripped defaults", `{"type":"object","properties":{"foo":{"type":"string","default":"bar"}}}`, + nil, `{"type":"object","properties":{"apiVersion":{"type":"string"},"kind":{"type":"string"},"metadata":{"$ref":"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"},"foo":{"type":"string"}},"x-kubernetes-group-version-kind":[{"group":"bar.k8s.io","kind":"Foo","version":"v1"}]}`, Options{V2: true, StripDefaults: true}, }, { "with stripped defaults", `{"type":"object","properties":{"foo":{"type":"string","default":"bar"}}}`, + nil, `{"type":"object","properties":{"apiVersion":{"type":"string"},"kind":{"type":"string"},"metadata":{"$ref":"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"},"foo":{"type":"string"}},"x-kubernetes-group-version-kind":[{"group":"bar.k8s.io","kind":"Foo","version":"v1"}]}`, Options{V2: true, StripDefaults: true}, }, { "v2", `{"type":"object","properties":{"foo":{"type":"string","oneOf":[{"pattern":"a"},{"pattern":"b"}]}}}`, + nil, `{"type":"object","properties":{"apiVersion":{"type":"string"},"kind":{"type":"string"},"metadata":{"$ref":"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"},"foo":{"type":"string"}},"x-kubernetes-group-version-kind":[{"group":"bar.k8s.io","kind":"Foo","version":"v1"}]}`, Options{V2: true, StripDefaults: true}, }, { "v3", `{"type":"object","properties":{"foo":{"type":"string","oneOf":[{"pattern":"a"},{"pattern":"b"}]}}}`, + nil, `{"type":"object","properties":{"apiVersion":{"type":"string"},"kind":{"type":"string"},"metadata":{"$ref":"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"},"foo":{"type":"string","oneOf":[{"pattern":"a"},{"pattern":"b"}]}},"x-kubernetes-group-version-kind":[{"group":"bar.k8s.io","kind":"Foo","version":"v1"}]}`, Options{V2: false, StripDefaults: true}, }, @@ -628,8 +644,9 @@ func TestBuildSwagger(t *testing.T) { Kind: "Foo", ListKind: "FooList", }, - Scope: apiextensions.NamespaceScoped, - Validation: validation, + Scope: apiextensions.NamespaceScoped, + Validation: validation, + PreserveUnknownFields: tt.preserveUnknownFields, }, }, "v1", tt.opts) if err != nil {