From c56f07672bea7f51c74c037764a4d3d63c8c3cd1 Mon Sep 17 00:00:00 2001 From: "Dr. Stefan Schimanski" Date: Mon, 4 Nov 2019 11:44:03 +0100 Subject: [PATCH 1/2] apiextensions-apiserver: promote defaulting to GA --- pkg/features/kube_features.go | 2 +- .../apiextensions/validation/validation.go | 3 - .../validation/validation_test.go | 505 ------------------ .../pkg/features/kube_features.go | 6 +- 4 files changed, 6 insertions(+), 510 deletions(-) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index a0e6312d152..976595cf7f5 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -593,7 +593,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS apiextensionsfeatures.CustomResourceSubresources: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, apiextensionsfeatures.CustomResourceWebhookConversion: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, apiextensionsfeatures.CustomResourcePublishOpenAPI: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, - apiextensionsfeatures.CustomResourceDefaulting: {Default: true, PreRelease: featuregate.Beta}, + apiextensionsfeatures.CustomResourceDefaulting: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // TODO: remove in 1.18 // features that enable backwards compatibility but are scheduled to be removed // ... 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 c3d29df6f26..688f7b494a0 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 @@ -1048,9 +1048,6 @@ func allowDefaults(requestGV schema.GroupVersion, oldCRDSpec *apiextensions.Cust // don't tighten validation on existing persisted data return true } - if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceDefaulting) { - return false - } if requestGV == apiextensionsv1beta1.SchemeGroupVersion { return false } 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 3678c485ca2..3359213bfc2 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 @@ -1613,44 +1613,6 @@ func TestValidateCustomResourceDefinition(t *testing.T) { invalid("spec", "versions[3]", "subresources", "scale", "labelSelectorPath"), }, }, - { - name: "defaults with disabled feature gate", - resource: &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"}, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "group.com", - Version: "version", - Versions: singleVersionList, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: "plural", - Singular: "singular", - Kind: "Plural", - ListKind: "PluralList", - }, - Validation: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "a": { - Type: "number", - Default: jsonPtr(42.0), - }, - }, - }, - }, - PreserveUnknownFields: pointer.BoolPtr(true), - }, - Status: apiextensions.CustomResourceDefinitionStatus{ - StoredVersions: []string{"version"}, - }, - }, - requestGV: apiextensionsv1beta1.SchemeGroupVersion, - disabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, - errors: []validationMatch{ - forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "default"), // disabled feature-gate - }, - }, { name: "defaults with enabled feature gate via v1beta1", resource: &apiextensions.CustomResourceDefinition{ @@ -5871,473 +5833,6 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) { errors: []validationMatch{}, enabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, }, - { - name: "ratcheting validation of defaults with disabled feature gate via v1beta1", - old: &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "plural.group.com", - ResourceVersion: "42", - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "group.com", - Version: "version", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "version", - Served: true, - Storage: true, - }, - }, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: "plural", - Singular: "singular", - Kind: "Plural", - ListKind: "PluralList", - }, - Validation: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "a": { - Type: "number", - Default: jsonPtr(42.0), - }, - }, - }, - }, - PreserveUnknownFields: pointer.BoolPtr(false), - }, - Status: apiextensions.CustomResourceDefinitionStatus{ - StoredVersions: []string{"version"}, - }, - }, - resource: &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "plural.group.com", - ResourceVersion: "42", - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "group.com", - Version: "version", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "version", - Served: true, - Storage: true, - }, - }, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: "plural", - Singular: "singular", - Kind: "Plural", - ListKind: "PluralList", - }, - Validation: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "a": { - Type: "number", - Default: jsonPtr(42.0), - }, - "b": { - Type: "number", - Default: jsonPtr(43.0), - }, - }, - }, - }, - PreserveUnknownFields: pointer.BoolPtr(false), - }, - Status: apiextensions.CustomResourceDefinitionStatus{ - StoredVersions: []string{"version"}, - }, - }, - requestGV: apiextensionsv1beta1.SchemeGroupVersion, - errors: []validationMatch{}, - disabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, - }, - { - name: "ratcheting validation of defaults with disabled feature gate via v1", - old: &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "plural.group.com", - ResourceVersion: "42", - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "group.com", - Version: "version", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "version", - Served: true, - Storage: true, - }, - }, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: "plural", - Singular: "singular", - Kind: "Plural", - ListKind: "PluralList", - }, - Validation: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "a": { - Type: "number", - Default: jsonPtr(42.0), - }, - }, - }, - }, - PreserveUnknownFields: pointer.BoolPtr(false), - }, - Status: apiextensions.CustomResourceDefinitionStatus{ - StoredVersions: []string{"version"}, - }, - }, - resource: &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "plural.group.com", - ResourceVersion: "42", - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "group.com", - Version: "version", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "version", - Served: true, - Storage: true, - }, - }, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: "plural", - Singular: "singular", - Kind: "Plural", - ListKind: "PluralList", - }, - Validation: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "a": { - Type: "number", - Default: jsonPtr(42.0), - }, - "b": { - Type: "number", - Default: jsonPtr(43.0), - }, - }, - }, - }, - PreserveUnknownFields: pointer.BoolPtr(false), - }, - Status: apiextensions.CustomResourceDefinitionStatus{ - StoredVersions: []string{"version"}, - }, - }, - requestGV: apiextensionsv1.SchemeGroupVersion, - errors: []validationMatch{}, - disabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, - }, - { - name: "ratcheting validation of defaults with disabled feature gate via v1, non-structural, no defaults before", - old: &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "plural.group.com", - ResourceVersion: "42", - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "group.com", - Version: "version", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "version", - Served: true, - Storage: true, - }, - }, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: "plural", - Singular: "singular", - Kind: "Plural", - ListKind: "PluralList", - }, - Validation: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "a": {}, - }, - }, - }, - PreserveUnknownFields: pointer.BoolPtr(false), - }, - Status: apiextensions.CustomResourceDefinitionStatus{ - StoredVersions: []string{"version"}, - }, - }, - resource: &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "plural.group.com", - ResourceVersion: "42", - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "group.com", - Version: "version", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "version", - Served: true, - Storage: true, - }, - }, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: "plural", - Singular: "singular", - Kind: "Plural", - ListKind: "PluralList", - }, - Validation: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "a": { - Type: "number", - Default: jsonPtr(42.0), - }, - }, - }, - }, - PreserveUnknownFields: pointer.BoolPtr(false), - }, - Status: apiextensions.CustomResourceDefinitionStatus{ - StoredVersions: []string{"version"}, - }, - }, - requestGV: apiextensionsv1.SchemeGroupVersion, - errors: []validationMatch{ - forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "default"), - }, - disabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, - }, - { - name: "ratcheting validation of defaults with disabled feature gate via v1, unpruned => unpruned", - old: &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "plural.group.com", - ResourceVersion: "42", - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "group.com", - Version: "version", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "version", - Served: true, - Storage: true, - }, - }, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: "plural", - Singular: "singular", - Kind: "Plural", - ListKind: "PluralList", - }, - Validation: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "a": { - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "foo": {Type: "string"}, - }, - Default: jsonPtr(map[string]interface{}{ - "unknown": "unknown", - }), - }, - }, - }, - }, - PreserveUnknownFields: pointer.BoolPtr(false), - }, - Status: apiextensions.CustomResourceDefinitionStatus{ - StoredVersions: []string{"version"}, - }, - }, - resource: &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "plural.group.com", - ResourceVersion: "42", - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "group.com", - Version: "version", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "version", - Served: true, - Storage: true, - }, - }, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: "plural", - Singular: "singular", - Kind: "Plural", - ListKind: "PluralList", - }, - Validation: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "a": { - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "foo": {Type: "string"}, - }, - Default: jsonPtr(map[string]interface{}{ - "unknown": "unknown", - }), - }, - "b": { - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "foo": {Type: "string"}, - }, - Default: jsonPtr(map[string]interface{}{ - "unknown": "unknown", - }), - }, - }, - }, - }, - PreserveUnknownFields: pointer.BoolPtr(false), - }, - Status: apiextensions.CustomResourceDefinitionStatus{ - StoredVersions: []string{"version"}, - }, - }, - requestGV: apiextensionsv1.SchemeGroupVersion, - errors: []validationMatch{}, - disabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, - }, - { - name: "ratcheting validation of defaults with disabled feature gate via v1, pruned => unpruned", - old: &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "plural.group.com", - ResourceVersion: "42", - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "group.com", - Version: "version", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "version", - Served: true, - Storage: true, - }, - }, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: "plural", - Singular: "singular", - Kind: "Plural", - ListKind: "PluralList", - }, - Validation: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "a": { - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "foo": {Type: "string"}, - }, - Default: jsonPtr(map[string]interface{}{ - "foo": "foo", - }), - }, - }, - }, - }, - PreserveUnknownFields: pointer.BoolPtr(false), - }, - Status: apiextensions.CustomResourceDefinitionStatus{ - StoredVersions: []string{"version"}, - }, - }, - resource: &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "plural.group.com", - ResourceVersion: "42", - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "group.com", - Version: "version", - Versions: []apiextensions.CustomResourceDefinitionVersion{ - { - Name: "version", - Served: true, - Storage: true, - }, - }, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Plural: "plural", - Singular: "singular", - Kind: "Plural", - ListKind: "PluralList", - }, - Validation: &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "a": { - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "foo": {Type: "string"}, - }, - Default: jsonPtr(map[string]interface{}{ - "foo": "foo", - }), - }, - "b": { - Type: "object", - Properties: map[string]apiextensions.JSONSchemaProps{ - "foo": {Type: "string"}, - }, - Default: jsonPtr(map[string]interface{}{ - "unknown": "unknown", - }), - }, - }, - }, - }, - PreserveUnknownFields: pointer.BoolPtr(false), - }, - Status: apiextensions.CustomResourceDefinitionStatus{ - StoredVersions: []string{"version"}, - }, - }, - requestGV: apiextensionsv1.SchemeGroupVersion, - errors: []validationMatch{ - invalid("spec", "validation", "openAPIV3Schema", "properties[b]", "default"), - }, - disabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting}, - }, { name: "add default with enabled feature gate, structural schema, without pruning", old: &apiextensions.CustomResourceDefinition{ diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go index 178e91eedc2..dd5a3a48a03 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go @@ -62,8 +62,12 @@ const ( // owner: @sttts // alpha: v1.15 + // beta: v1.16 + // GA: v1.17 // // CustomResourceDefaulting enables OpenAPI defaulting in CustomResources. + // + // TODO: remove in 1.18 CustomResourceDefaulting featuregate.Feature = "CustomResourceDefaulting" ) @@ -79,5 +83,5 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS CustomResourceSubresources: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, CustomResourceWebhookConversion: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, CustomResourcePublishOpenAPI: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, - CustomResourceDefaulting: {Default: true, PreRelease: featuregate.Beta}, + CustomResourceDefaulting: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, } From 9f73c6ccf66859b367c8ec771f42625e7424fc6f Mon Sep 17 00:00:00 2001 From: "Dr. Stefan Schimanski" Date: Mon, 4 Nov 2019 12:19:31 +0100 Subject: [PATCH 2/2] e2e: add defaulting test --- .../custom_resource_definition.go | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/test/e2e/apimachinery/custom_resource_definition.go b/test/e2e/apimachinery/custom_resource_definition.go index b82e8f86383..17651ac531c 100644 --- a/test/e2e/apimachinery/custom_resource_definition.go +++ b/test/e2e/apimachinery/custom_resource_definition.go @@ -17,6 +17,9 @@ limitations under the License. package apimachinery import ( + "fmt" + "time" + "github.com/onsi/ginkgo" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -26,9 +29,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/uuid" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apiserver/pkg/storage/names" "k8s.io/client-go/dynamic" "k8s.io/client-go/util/retry" "k8s.io/kubernetes/test/e2e/framework" @@ -251,6 +257,128 @@ var _ = SIGDescribe("CustomResourceDefinition resources [Privileged:ClusterAdmin } }) + /* + Release : v1.17 + Testname: Custom Resource Definition, defaulting + Description: Create a custom resource definition without default. Create CR. Add default and read CR until + the default is applied. Create another CR. Remove default, add default for another field and read CR until + new field is defaulted, but old default stays. + */ + ginkgo.It("custom resource defaulting for requests and from storage works ", func() { + config, err := framework.LoadConfig() + framework.ExpectNoError(err, "loading config") + apiExtensionClient, err := clientset.NewForConfig(config) + framework.ExpectNoError(err, "initializing apiExtensionClient") + dynamicClient, err := dynamic.NewForConfig(config) + framework.ExpectNoError(err, "initializing dynamic client") + + // Create CRD without default and waits for the resource to be recognized and available. + crd := fixtures.NewRandomNameV1CustomResourceDefinition(v1.ClusterScoped) + if crd.Spec.Versions[0].Schema.OpenAPIV3Schema.Properties == nil { + crd.Spec.Versions[0].Schema.OpenAPIV3Schema.Properties = map[string]v1.JSONSchemaProps{} + } + crd.Spec.Versions[0].Schema.OpenAPIV3Schema.Properties["a"] = v1.JSONSchemaProps{Type: "string"} + crd.Spec.Versions[0].Schema.OpenAPIV3Schema.Properties["b"] = v1.JSONSchemaProps{Type: "string"} + crd, err = fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient) + framework.ExpectNoError(err, "creating CustomResourceDefinition") + defer func() { + err = fixtures.DeleteV1CustomResourceDefinition(crd, apiExtensionClient) + framework.ExpectNoError(err, "deleting CustomResourceDefinition") + }() + + // create CR without default in storage + name1 := names.SimpleNameGenerator.GenerateName("cr-1") + gvr := schema.GroupVersionResource{ + Group: crd.Spec.Group, + Version: crd.Spec.Versions[0].Name, + Resource: crd.Spec.Names.Plural, + } + crClient := dynamicClient.Resource(gvr) + _, err = crClient.Create(&unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": gvr.Group + "/" + gvr.Version, + "kind": crd.Spec.Names.Kind, + "metadata": map[string]interface{}{ + "name": name1, + }, + }}, metav1.CreateOptions{}) + framework.ExpectNoError(err, "creating CR") + + // Setting default for a to "A" and waiting for the CR to get defaulted on read + crd, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Patch(crd.Name, types.JSONPatchType, []byte(`[ + {"op":"add","path":"/spec/versions/0/schema/openAPIV3Schema/properties/a/default", "value": "A"} + ]`)) + framework.ExpectNoError(err, "setting default for a to \"A\" in schema") + + err = wait.PollImmediate(time.Millisecond*100, wait.ForeverTestTimeout, func() (bool, error) { + u1, err := crClient.Get(name1, metav1.GetOptions{}) + if err != nil { + return false, err + } + a, found, err := unstructured.NestedFieldNoCopy(u1.Object, "a") + if err != nil { + return false, err + } + if !found { + return false, nil + } + if a != "A" { + return false, fmt.Errorf("expected a:\"A\", but got a:%q", a) + } + return true, nil + }) + framework.ExpectNoError(err, "waiting for CR to be defaulted on read") + + // create CR with default in storage + name2 := names.SimpleNameGenerator.GenerateName("cr-2") + u2, err := crClient.Create(&unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": gvr.Group + "/" + gvr.Version, + "kind": crd.Spec.Names.Kind, + "metadata": map[string]interface{}{ + "name": name2, + }, + }}, metav1.CreateOptions{}) + framework.ExpectNoError(err, "creating CR") + v, found, err := unstructured.NestedFieldNoCopy(u2.Object, "a") + framework.ExpectEqual(found, true, "\"a\" is defaulted") + framework.ExpectEqual(v, "A", "\"a\" is defaulted to \"A\"") + + // Deleting default for a, adding default "B" for b and waiting for the CR to get defaulted on read for b + crd, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Patch(crd.Name, types.JSONPatchType, []byte(`[ + {"op":"remove","path":"/spec/versions/0/schema/openAPIV3Schema/properties/a/default"}, + {"op":"add","path":"/spec/versions/0/schema/openAPIV3Schema/properties/b/default", "value": "B"} + ]`)) + framework.ExpectNoError(err, "setting default for b to \"B\" and remove default for a") + + err = wait.PollImmediate(time.Millisecond*100, wait.ForeverTestTimeout, func() (bool, error) { + u2, err := crClient.Get(name2, metav1.GetOptions{}) + if err != nil { + return false, err + } + b, found, err := unstructured.NestedFieldNoCopy(u2.Object, "b") + if err != nil { + return false, err + } + if !found { + return false, nil + } + if b != "B" { + return false, fmt.Errorf("expected b:\"B\", but got b:%q", b) + } + a, found, err := unstructured.NestedFieldNoCopy(u2.Object, "a") + if err != nil { + return false, err + } + if !found { + return false, fmt.Errorf("expected a:\"A\" to be unchanged, but it was removed") + } + if a != "A" { + return false, fmt.Errorf("expected a:\"A\" to be unchanged, but it changed to %q", a) + } + return true, nil + }) + framework.ExpectNoError(err, "waiting for CR to be defaulted on read for b and a staying the same") + }) + }) func unstructuredToCRD(obj *unstructured.Unstructured) *v1.CustomResourceDefinition {