diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder.go index a239d726f5e..ba7722a73ec 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder.go @@ -297,7 +297,7 @@ func (b *builder) buildKubeNative(schema *structuralschema.Structural, v2 bool) // 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 { + if schema == nil || (v2 && schema.XPreserveUnknownFields) { ret = &spec.Schema{ SchemaProps: spec.SchemaProps{Type: []string{"object"}}, } @@ -311,7 +311,7 @@ func (b *builder) buildKubeNative(schema *structuralschema.Structural, v2 bool) ret.SetProperty("metadata", *spec.RefSchema(objectMetaSchemaRef). WithDescription(swaggerPartialObjectMetadataDescriptions["metadata"])) addTypeMetaProperties(ret) - addEmbeddedProperties(ret) + addEmbeddedProperties(ret, v2) } ret.AddExtension(endpoints.ROUTE_META_GVK, []interface{}{ map[string]interface{}{ @@ -324,23 +324,28 @@ func (b *builder) buildKubeNative(schema *structuralschema.Structural, v2 bool) return ret } -func addEmbeddedProperties(s *spec.Schema) { +func addEmbeddedProperties(s *spec.Schema, v2 bool) { if s == nil { return } for k := range s.Properties { v := s.Properties[k] - addEmbeddedProperties(&v) + addEmbeddedProperties(&v, v2) s.Properties[k] = v } if s.Items != nil { - addEmbeddedProperties(s.Items.Schema) + addEmbeddedProperties(s.Items.Schema, v2) } if s.AdditionalProperties != nil { - addEmbeddedProperties(s.AdditionalProperties.Schema) + addEmbeddedProperties(s.AdditionalProperties.Schema, v2) } + if isTrue, ok := s.VendorExtensible.Extensions.GetBool("x-kubernetes-preserve-unknown-fields"); ok && isTrue && v2 { + // don't add metadata properties if we're publishing to openapi v2 and are allowing unknown fields. + // adding these metadata properties makes kubectl refuse to validate unknown fields. + return + } if isTrue, ok := s.VendorExtensible.Extensions.GetBool("x-kubernetes-embedded-resource"); ok && isTrue { s.SetProperty("apiVersion", withDescription(getDefinition(typeMetaType).SchemaProps.Properties["apiVersion"], "apiVersion defines the versioned schema of this representation of an object. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder_test.go index 5c8da1bfde3..89e919cf283 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder_test.go @@ -59,6 +59,18 @@ func TestNewBuilder(t *testing.T) { `{"$ref":"#/definitions/io.k8s.bar.v1.Foo"}`, true, }, + {"preserve unknown at root v2", + `{"type":"object","x-kubernetes-preserve-unknown-fields":true}`, + `{"type":"object","x-kubernetes-group-version-kind":[{"group":"bar.k8s.io","kind":"Foo","version":"v1"}]}`, + `{"$ref":"#/definitions/io.k8s.bar.v1.Foo"}`, + true, + }, + {"preserve unknown at root v3", + `{"type":"object","x-kubernetes-preserve-unknown-fields":true}`, + `{"type":"object","x-kubernetes-preserve-unknown-fields":true,"properties":{"apiVersion":{"type":"string"},"kind":{"type":"string"},"metadata":{"$ref":"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"}},"x-kubernetes-group-version-kind":[{"group":"bar.k8s.io","kind":"Foo","version":"v1"}]}`, + `{"$ref":"#/definitions/io.k8s.bar.v1.Foo"}`, + false, + }, {"with extensions", ` { @@ -155,22 +167,7 @@ func TestNewBuilder(t *testing.T) { "embedded-object": { "x-kubernetes-embedded-resource": true, "x-kubernetes-preserve-unknown-fields": true, - "type": "object", - "required":["kind","apiVersion"], - "properties":{ - "apiVersion":{ - "description":"apiVersion defines the versioned schema of this representation of an object. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources", - "type":"string" - }, - "kind":{ - "description":"kind is a string value representing the type of this object. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "type":"string" - }, - "metadata":{ - "description":"Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref":"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - } - } + "type":"object" } }, "x-kubernetes-group-version-kind":[{"group":"bar.k8s.io","kind":"Foo","version":"v1"}] diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/conversion.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/conversion.go index 55360ea5303..5b998e65bd7 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/conversion.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/conversion.go @@ -65,6 +65,14 @@ func ToStructuralOpenAPIV2(in *structuralschema.Structural) *structuralschema.St changed = true } + if s.XPreserveUnknownFields { + // unknown fields break if items or properties are set in kubectl + s.Items = nil + s.Properties = nil + + changed = true + } + return changed }, // we drop all junctors above, and hence, never reach nested value validations