Code change:
doesn't do the same embedded resource and x-kubernetes-list-type "map" and "set" invariant validation that happens in custom resource create/update validation. Does that mean embedded objects and list-type fields under custom resource status can contain invalid data? if so, that needs to be fixed in a way that tolerates existing invalid data (ratcheting validation) Originally posted by in /sig api-machinery /kind bug /priority important-longterm
/triage accepted
I would like to collaborate for fixing the validation issue for custom resources and tolerate the existing invalid data. Could you please guide me with some code pointers or files and elaborate on how can I proceed for fixing this bug.
I haven't tested this, but something along the lines of this: we'd need tests to exercise the following scenarios on a CRD that specifies a status subresource with a list property with x-kubernetes-list-type=map and x-kubernetes-list-type=set a custom resource instance that already has duplicate items in the status property list can be updated successfully while still having duplicate items in the list (validation doesn't block an invalid → invalid status update) a custom resource instance that already has duplicate items in the status property list can be updated successfully to set unique items in the list (validation doesn't block an invalid → valid status update) a custom resource instance that does not have duplicate items in the status property list cannot be updated to set duplicate items in the list (validation blocks a valid → invalid status update)
since it's been a couple of months, i'm going to try and take a stab at this /assign
/assign
Hi k8s 1.25 Bug Triage Shadow here, just checking in to see if this is still on track for k8s 1.25? Thank you.
has stalled needing tests and a rebase
Thanks for the update.
Hello with the code freeze approaching, is this still on track for v1.25? Thanks!
not really, the status on is the same (needs tests)
/milestone clear

Related discussion:
text: } } } <ins> const listTypeResourceSchema = ` apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: foos.test spec: group: test names: kind: Foo listKind: FooList plural: foos singular: foo scope: Cluster versions: - name: v1 schema: openAPIV3Schema: type: object properties: numArray: type: array x-kubernetes-list-type: set items: type: object served: true storage: true - name: v2 schema: openAPIV3Schema: type: object properties: numArray2: type: array ` func TestStatusStrategyValidateUpdate(t *testing.T) { crdV1 := &apiextensionsv1.CustomResourceDefinition{} err := yaml.Unmarshal([]byte(listTypeResourceSchema), &crdV1) if err != nil { t.Fatalf("unexpected decoding error: %v", err) } t.Logf("crd v1 details: %v", crdV1) crd := &apiextensions.CustomResourceDefinition{} if err = apiextensionsv1.Convert_v1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(crdV1, crd, nil); err != nil { t.Fatalf("unexpected convert error: %v", err) } t.Logf("crd details: %v", crd) strategy := statusStrategy{} kind := schema.GroupVersionKind{ Version: crd.Spec.Versions[0].Name, Kind:    crd.Spec.Names.Kind, Group:   crd.Spec.Group, } strategy.customResourceStrategy.validator.kind = kind ss, _ := structuralschema.NewStructural(crd.Spec.Versions[0].Schema.OpenAPIV3Schema) strategy.structuralSchemas = map[string]*structuralschema.Structural{ crd.Spec.Versions[0].Name: ss, } ctx := context.TODO() tcs := []struct { name    string old     *unstructured.Unstructured obj     *unstructured.Unstructured isValid bool }{ { name:    "bothValid", old:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "numArray": []interface{}{1, 2}}}, obj:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "numArray": []interface{}{1, 3}, "metadata": map[string]interface{}{"resourceVersion": "1"}}}, isValid: true, }, { name:    "change to invalid", old:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "spec": "old", "numArray": []interface{}{1, 2}}}, obj:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "spec": "new", "numArray": []interface{}{1, 1}, "metadata": map[string]interface{}{"resourceVersion": "1"}}}, isValid: false, }, { name:    "change to valid", old:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "spec": "new", "numArray": []interface{}{1, 1}}}, obj:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "spec": "old", "numArray": []interface{}{1, 2}, "metadata": map[string]interface{}{"resourceVersion": "1"}}}, isValid: true, }, { name:    "keeps invalid", old:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "spec": "new", "numArray": []interface{}{1, 1}}}, obj:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "spec": "old", "numArray": []interface{}{1, 1}, "metadata": map[string]interface{}{"resourceVersion": "1"}}}, isValid: true, }, } for _, tc := range tcs { errs := strategy.ValidateUpdate(ctx, tc.obj, tc.old) if tc.isValid && len(errs) > 0 { t.Errorf("%v: unexpected error: %v", tc.name, errs) } if !tc.isValid && len(errs) == 0 { t.Errorf("%v: unexpected non-error", tc.name) } } } </ins>