diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 76b1abb9173..6530028ee8b 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -993,7 +993,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS genericfeatures.OpenAPIEnums: {Default: true, PreRelease: featuregate.Beta}, genericfeatures.CustomResourceValidationExpressions: {Default: false, PreRelease: featuregate.Alpha}, genericfeatures.OpenAPIV3: {Default: false, PreRelease: featuregate.Alpha}, - genericfeatures.ServerSideFieldValidation: {Default: false, PreRelease: featuregate.Alpha}, + genericfeatures.ServerSideFieldValidation: {Default: true, PreRelease: featuregate.Beta}, // features that enable backwards compatibility but are scheduled to be removed // ... HPAScaleToZero: {Default: false, PreRelease: featuregate.Alpha}, diff --git a/staging/src/k8s.io/apiserver/pkg/features/kube_features.go b/staging/src/k8s.io/apiserver/pkg/features/kube_features.go index f5ff7f073dd..c4f4d0b837c 100644 --- a/staging/src/k8s.io/apiserver/pkg/features/kube_features.go +++ b/staging/src/k8s.io/apiserver/pkg/features/kube_features.go @@ -174,6 +174,7 @@ const ( // owner: @kevindelgado // kep: http://kep.k8s.io/2885 // alpha: v1.23 + // beta: v1.24 // // Enables server-side field validation. ServerSideFieldValidation featuregate.Feature = "ServerSideFieldValidation" @@ -205,5 +206,5 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS OpenAPIEnums: {Default: true, PreRelease: featuregate.Beta}, CustomResourceValidationExpressions: {Default: false, PreRelease: featuregate.Alpha}, OpenAPIV3: {Default: false, PreRelease: featuregate.Alpha}, - ServerSideFieldValidation: {Default: false, PreRelease: featuregate.Alpha}, + ServerSideFieldValidation: {Default: true, PreRelease: featuregate.Beta}, } diff --git a/test/conformance/testdata/conformance.yaml b/test/conformance/testdata/conformance.yaml index 784be43e76a..795dddf6ef9 100755 --- a/test/conformance/testdata/conformance.yaml +++ b/test/conformance/testdata/conformance.yaml @@ -251,8 +251,8 @@ works for CRD preserving unknown fields at the schema root [Conformance]' description: Register a custom resource definition with x-preserve-unknown-fields in the schema root. Attempt to create and apply a change a custom resource, via - kubectl; client-side validation MUST accept unknown properties. Attempt kubectl - explain; the output MUST show the custom resource KIND. + kubectl; kubectl validation MUST accept unknown properties. Attempt kubectl explain; + the output MUST show the custom resource KIND. release: v1.16 file: test/e2e/apimachinery/crd_publish_openapi.go - testname: Custom Resource OpenAPI Publish, with x-preserve-unknown-fields in embedded @@ -261,7 +261,7 @@ works for CRD preserving unknown fields in an embedded object [Conformance]' description: Register a custom resource definition with x-preserve-unknown-fields in an embedded object. Attempt to create and apply a change a custom resource, - via kubectl; client-side validation MUST accept unknown properties. Attempt kubectl + via kubectl; kubectl validation MUST accept unknown properties. Attempt kubectl explain; the output MUST show that x-preserve-unknown-properties is used on the nested field. release: v1.16 @@ -271,12 +271,12 @@ works for CRD with validation schema [Conformance]' description: Register a custom resource definition with a validating schema consisting of objects, arrays and primitives. Attempt to create and apply a change a custom - resource using valid properties, via kubectl; client-side validation MUST pass. - Attempt both operations with unknown properties and without required properties; - client-side validation MUST reject the operations. Attempt kubectl explain; the - output MUST explain the custom resource properties. Attempt kubectl explain on - custom resource properties; the output MUST explain the nested custom resource - properties. + resource using valid properties, via kubectl; kubectl validation MUST pass. Attempt + both operations with unknown properties and without required properties; kubectl + validation MUST reject the operations. Attempt kubectl explain; the output MUST + explain the custom resource properties. Attempt kubectl explain on custom resource + properties; the output MUST explain the nested custom resource properties. All + validation should be the same. release: v1.16 file: test/e2e/apimachinery/crd_publish_openapi.go - testname: Custom Resource OpenAPI Publish, with x-preserve-unknown-fields in object @@ -284,7 +284,7 @@ works for CRD without validation schema [Conformance]' description: Register a custom resource definition with x-preserve-unknown-fields in the top level object. Attempt to create and apply a change a custom resource, - via kubectl; client-side validation MUST accept unknown properties. Attempt kubectl + via kubectl; kubectl validation MUST accept unknown properties. Attempt kubectl explain; the output MUST contain a valid DESCRIPTION stanza. release: v1.16 file: test/e2e/apimachinery/crd_publish_openapi.go diff --git a/test/e2e/apimachinery/crd_publish_openapi.go b/test/e2e/apimachinery/crd_publish_openapi.go index 4558840c1d8..ca77c70e22d 100644 --- a/test/e2e/apimachinery/crd_publish_openapi.go +++ b/test/e2e/apimachinery/crd_publish_openapi.go @@ -58,10 +58,11 @@ var _ = SIGDescribe("CustomResourcePublishOpenAPI [Privileged:ClusterAdmin]", fu Testname: Custom Resource OpenAPI Publish, with validation schema Description: Register a custom resource definition with a validating schema consisting of objects, arrays and primitives. Attempt to create and apply a change a custom resource using valid properties, via kubectl; - client-side validation MUST pass. Attempt both operations with unknown properties and without required - properties; client-side validation MUST reject the operations. Attempt kubectl explain; the output MUST + kubectl validation MUST pass. Attempt both operations with unknown properties and without required + properties; kubectl validation MUST reject the operations. Attempt kubectl explain; the output MUST explain the custom resource properties. Attempt kubectl explain on custom resource properties; the output MUST explain the nested custom resource properties. + All validation should be the same. */ framework.ConformanceIt("works for CRD with validation schema", func() { crd, err := setupCRD(f, schemaFoo, "foo", "v1") @@ -72,7 +73,7 @@ var _ = SIGDescribe("CustomResourcePublishOpenAPI [Privileged:ClusterAdmin]", fu meta := fmt.Sprintf(metaPattern, crd.Crd.Spec.Names.Kind, crd.Crd.Spec.Group, crd.Crd.Spec.Versions[0].Name, "test-foo") ns := fmt.Sprintf("--namespace=%v", f.Namespace.Name) - ginkgo.By("client-side validation (kubectl create and apply) allows request with known and required properties") + ginkgo.By("kubectl validation (kubectl create and apply) allows request with known and required properties") validCR := fmt.Sprintf(`{%s,"spec":{"bars":[{"name":"test-bar"}]}}`, meta) if _, err := framework.RunKubectlInput(f.Namespace.Name, validCR, ns, "create", "-f", "-"); err != nil { framework.Failf("failed to create valid CR %s: %v", validCR, err) @@ -87,13 +88,15 @@ var _ = SIGDescribe("CustomResourcePublishOpenAPI [Privileged:ClusterAdmin]", fu framework.Failf("failed to delete valid CR: %v", err) } - ginkgo.By("client-side validation (kubectl create and apply) rejects request with value outside defined enum values") + ginkgo.By("kubectl validation (kubectl create and apply) rejects request with value outside defined enum values") badEnumValueCR := fmt.Sprintf(`{%s,"spec":{"bars":[{"name":"test-bar", "feeling":"NonExistentValue"}]}}`, meta) if _, err := framework.RunKubectlInput(f.Namespace.Name, badEnumValueCR, ns, "create", "-f", "-"); err == nil || !strings.Contains(err.Error(), `Unsupported value: "NonExistentValue"`) { framework.Failf("unexpected no error when creating CR with unknown enum value: %v", err) } - ginkgo.By("client-side validation (kubectl create and apply) rejects request with unknown properties when disallowed by the schema") + // TODO: server-side validation and client-side validation produce slightly different error messages. + // Because server-side is default in beta but not GA yet, we will produce different behaviors in the default vs GA only conformance tests. We have made the error generic enough to pass both, but should go back and make the error more specific once server-side validation goes GA. + ginkgo.By("kubectl validation (kubectl create and apply) rejects request with unknown properties when disallowed by the schema") unknownCR := fmt.Sprintf(`{%s,"spec":{"foo":true}}`, meta) if _, err := framework.RunKubectlInput(f.Namespace.Name, unknownCR, ns, "create", "-f", "-"); err == nil || (!strings.Contains(err.Error(), `unknown field "foo"`) && !strings.Contains(err.Error(), `unknown field "spec.foo"`)) { framework.Failf("unexpected no error when creating CR with unknown field: %v", err) @@ -102,7 +105,8 @@ var _ = SIGDescribe("CustomResourcePublishOpenAPI [Privileged:ClusterAdmin]", fu framework.Failf("unexpected no error when applying CR with unknown field: %v", err) } - ginkgo.By("client-side validation (kubectl create and apply) rejects request without required properties") + // TODO: see above note, we should check the value of the error once server-side validation is GA. + ginkgo.By("kubectl validation (kubectl create and apply) rejects request without required properties") noRequireCR := fmt.Sprintf(`{%s,"spec":{"bars":[{"age":"10"}]}}`, meta) if _, err := framework.RunKubectlInput(f.Namespace.Name, noRequireCR, ns, "create", "-f", "-"); err == nil || (!strings.Contains(err.Error(), `missing required field "name"`) && !strings.Contains(err.Error(), `spec.bars[0].name: Required value`)) { framework.Failf("unexpected no error when creating CR without required field: %v", err) @@ -141,7 +145,7 @@ var _ = SIGDescribe("CustomResourcePublishOpenAPI [Privileged:ClusterAdmin]", fu Release: v1.16 Testname: Custom Resource OpenAPI Publish, with x-preserve-unknown-fields in object Description: Register a custom resource definition with x-preserve-unknown-fields in the top level object. - Attempt to create and apply a change a custom resource, via kubectl; client-side validation MUST accept unknown + Attempt to create and apply a change a custom resource, via kubectl; kubectl validation MUST accept unknown properties. Attempt kubectl explain; the output MUST contain a valid DESCRIPTION stanza. */ framework.ConformanceIt("works for CRD without validation schema", func() { @@ -153,7 +157,7 @@ var _ = SIGDescribe("CustomResourcePublishOpenAPI [Privileged:ClusterAdmin]", fu meta := fmt.Sprintf(metaPattern, crd.Crd.Spec.Names.Kind, crd.Crd.Spec.Group, crd.Crd.Spec.Versions[0].Name, "test-cr") ns := fmt.Sprintf("--namespace=%v", f.Namespace.Name) - ginkgo.By("client-side validation (kubectl create and apply) allows request with any unknown properties") + ginkgo.By("kubectl validation (kubectl create and apply) allows request with any unknown properties") randomCR := fmt.Sprintf(`{%s,"a":{"b":[{"c":"d"}]}}`, meta) if _, err := framework.RunKubectlInput(f.Namespace.Name, randomCR, ns, "create", "-f", "-"); err != nil { framework.Failf("failed to create random CR %s for CRD without schema: %v", randomCR, err) @@ -182,7 +186,7 @@ var _ = SIGDescribe("CustomResourcePublishOpenAPI [Privileged:ClusterAdmin]", fu Release: v1.16 Testname: Custom Resource OpenAPI Publish, with x-preserve-unknown-fields at root Description: Register a custom resource definition with x-preserve-unknown-fields in the schema root. - Attempt to create and apply a change a custom resource, via kubectl; client-side validation MUST accept unknown + Attempt to create and apply a change a custom resource, via kubectl; kubectl validation MUST accept unknown properties. Attempt kubectl explain; the output MUST show the custom resource KIND. */ framework.ConformanceIt("works for CRD preserving unknown fields at the schema root", func() { @@ -194,7 +198,7 @@ var _ = SIGDescribe("CustomResourcePublishOpenAPI [Privileged:ClusterAdmin]", fu meta := fmt.Sprintf(metaPattern, crd.Crd.Spec.Names.Kind, crd.Crd.Spec.Group, crd.Crd.Spec.Versions[0].Name, "test-cr") ns := fmt.Sprintf("--namespace=%v", f.Namespace.Name) - ginkgo.By("client-side validation (kubectl create and apply) allows request with any unknown properties") + ginkgo.By("kubectl validation (kubectl create and apply) allows request with any unknown properties") randomCR := fmt.Sprintf(`{%s,"a":{"b":[{"c":"d"}]}}`, meta) if _, err := framework.RunKubectlInput(f.Namespace.Name, randomCR, ns, "create", "-f", "-"); err != nil { framework.Failf("failed to create random CR %s for CRD that allows unknown properties at the root: %v", randomCR, err) @@ -223,7 +227,7 @@ var _ = SIGDescribe("CustomResourcePublishOpenAPI [Privileged:ClusterAdmin]", fu Release: v1.16 Testname: Custom Resource OpenAPI Publish, with x-preserve-unknown-fields in embedded object Description: Register a custom resource definition with x-preserve-unknown-fields in an embedded object. - Attempt to create and apply a change a custom resource, via kubectl; client-side validation MUST accept unknown + Attempt to create and apply a change a custom resource, via kubectl; kubectl validation MUST accept unknown properties. Attempt kubectl explain; the output MUST show that x-preserve-unknown-properties is used on the nested field. */ @@ -236,7 +240,7 @@ var _ = SIGDescribe("CustomResourcePublishOpenAPI [Privileged:ClusterAdmin]", fu meta := fmt.Sprintf(metaPattern, crd.Crd.Spec.Names.Kind, crd.Crd.Spec.Group, crd.Crd.Spec.Versions[0].Name, "test-cr") ns := fmt.Sprintf("--namespace=%v", f.Namespace.Name) - ginkgo.By("client-side validation (kubectl create and apply) allows request with any unknown properties") + ginkgo.By("kubectl validation (kubectl create and apply) allows request with any unknown properties") randomCR := fmt.Sprintf(`{%s,"spec":{"a":null,"b":[{"c":"d"}]}}`, meta) if _, err := framework.RunKubectlInput(f.Namespace.Name, randomCR, ns, "create", "-f", "-"); err != nil { framework.Failf("failed to create random CR %s for CRD that allows unknown properties in a nested object: %v", randomCR, err) diff --git a/test/e2e/kubectl/kubectl.go b/test/e2e/kubectl/kubectl.go index c0f238c4272..2678a3da0cc 100644 --- a/test/e2e/kubectl/kubectl.go +++ b/test/e2e/kubectl/kubectl.go @@ -1003,7 +1003,7 @@ metadata: return nil } - ginkgo.Describe("Kubectl client-side validation", func() { + ginkgo.Describe("Kubectl validation", func() { ginkgo.It("should create/apply a CR with unknown fields for CRD with no validation schema", func() { ginkgo.By("create CRD with no validation schema") crd, err := crd.CreateTestCRD(f) @@ -1048,7 +1048,7 @@ metadata: } }) - ginkgo.It("should create/apply a valid CR with arbitrary-extra properties for CRD with partially-specified validation schema", func() { + ginkgo.It("should create/apply an invalid/valid CR with arbitrary-extra properties for CRD with partially-specified validation schema", func() { ginkgo.By("prepare CRD with partially-specified validation schema") crd, err := crd.CreateTestCRD(f, func(crd *apiextensionsv1.CustomResourceDefinition) { props := &apiextensionsv1.JSONSchemaProps{} @@ -1074,6 +1074,15 @@ metadata: meta := fmt.Sprintf(metaPattern, crd.Crd.Spec.Names.Kind, crd.Crd.Spec.Group, crd.Crd.Spec.Versions[0].Name, "test-cr") + // XPreserveUnknownFields is defined on the root of the schema so unknown fields within the spec + // are still considered invalid + invalidArbitraryCR := fmt.Sprintf(`{%s,"spec":{"bars":[{"name":"test-bar"}],"extraProperty":"arbitrary-value"}}`, meta) + err = createApplyCustomResource(invalidArbitraryCR, f.Namespace.Name, "test-cr", crd) + framework.ExpectError(err, "creating custom resource") + if !strings.Contains(err.Error(), `unknown field "spec.extraProperty"`) { + framework.Failf("incorrect error from createApplyCustomResource: %v", err) + } + // unknown fields on the root are considered valid validArbitraryCR := fmt.Sprintf(`{%s,"spec":{"bars":[{"name":"test-bar"}]},"extraProperty":"arbitrary-value"}`, meta) err = createApplyCustomResource(validArbitraryCR, f.Namespace.Name, "test-cr", crd) @@ -2153,7 +2162,7 @@ func startLocalProxy() (srv *httptest.Server, logs *bytes.Buffer) { } // createApplyCustomResource asserts that given CustomResource be created and applied -// without being rejected by client-side validation +// without being rejected by kubectl validation func createApplyCustomResource(resource, namespace, name string, crd *crd.TestCrd) error { ginkgo.By("successfully create CR") if _, err := framework.RunKubectlInput(namespace, resource, "create", "--validate=true", "-f", "-"); err != nil {