Change:
While migrating webhooks to CEL for one of the structs in our CRD api, I noticed that the empty object in CEL is interpreted wrongly. Empty map object seems to be interpreted correctly. In the example below, both map and object are sent empty () in json output. returns true json (captured on the client side) sent to the apiserver CRD schema No response <details</details<details</details<details</details<details</details<details</details<details</details
/sig api-machinery
CEL only sees the JSON sent to the server, and is correctly evaluated as present. The issue is with the client sending the request populating a value in that field. How is the request being constructed? /remove-kind bug /kind support
/cc /triage accepted
Upon investigation, the problem lies there: We requires either or be set.
Thanks for raising this issue! I was debugging offline with and seems like CRD/CR validation didn't handle the situation of unsetting properties with field type specified . It is a corner case in CRD since we normally would ask for properties defined if the field type is (I could not think of use case which having properties unset). But it might ok to allow it through since it won't hurt from CRD perspective. We will fix in CEL values handling which treat this as empty map passed. /assign
GitHub didn't allow me to assign the following users: jiahui. Note that only with read permissions, repo collaborators and people who have commented on this issue/PR can be assigned. Additionally, issues/PRs can only have 10 assignees at the same time. For more information please see <detailsIn response to : Thanks for raising this issue! I was debugging offline with and seems like CRD/CR validation didn't handle the situation of unsetting properties with field type specified 'Object1object`(I could not think of use case which having properties unset). But it might ok to allow it through since it won't hurt from CRD perspective. We will fix in CEL values handling which treat this as empty map passed. /assign </details
also check if the CRD validation allows the or values, and if so, if the CEL code handles those properly as well
FYI: We do not allow in crd structured schema now:

Discussion:
}, } } <del> // A object with x-kubernetes-preserve-unknown-fields but no properties or additionalProperties is treated // as an empty object. if schema.IsXPreserveUnknownFields() { return &unstructuredMap{ value:  m, schema: schema, propSchema: func(key string) (Schema, bool) { return nil, false }, } </del> <ins> // properties and additionalProperties are mutual exclusive, but nothing prevents the situation // where both are missing. // An object that (1) has no properties (2) has no additionalProperties or additionalProperties == false // is treated as an empty object. // An object that has additionalProperties == true is treated as an unstructured map. // An object that has x-kubernetes-preserve-unknown-field extension set is treated as an unstructured map. // Empty object vs unstructured map is differentiated by unstructuredMap implementation with the set schema. // The resulting result remains the same. return &unstructuredMap{ value:  m, schema: schema, propSchema: func(key string) (Schema, bool) { return nil, false }, </ins> } <del> return types.NewErr("invalid object type, expected either Properties or AdditionalProperties with Allows=true and non-empty Schema") </del> } if schema.Type() == "array" {

Type:                 []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{Schema: stringSchema}, }} <ins> emptyObjectSchema = &spec.Schema{ SchemaProps: spec.SchemaProps{Type: []string{"object"}}, } </ins> ) func TestEquality(t *testing.T) {

equal: false, }, { <ins> name:  "empty objects are equal", lhs:   UnstructuredToVal(map[string]interface{}{}, emptyObjectSchema), rhs:   UnstructuredToVal(map[string]interface{}{}, emptyObjectSchema), equal: true, }, { </ins> name:  "identical objects are equal", lhs:   UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "b"}, objectSchema), rhs:   UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "b"}, objectSchema),

t.Error("Unexpected error creating custom resource but metadata validation rule") } }) <ins> t.Run("CRD creation MUST pass for an CRD with empty field", func(t *testing.T) { structuralWithValidators := crdWithSchema(t, "WithEmptyObject", structuralSchemaWithEmptyObject) _, err := fixtures.CreateNewV1CustomResourceDefinition(structuralWithValidators, apiExtensionClient, dynamicClient) if err != nil { t.Errorf("unexpected error creating CRD with empty field: %v", err) } }) </ins> t.Run("CR creation MUST fail if a x-kubernetes-validations rule exceeds the runtime cost limit", func(t *testing.T) { structuralWithValidators := crdWithSchema(t, "RuntimeCostLimit", structuralSchemaWithCostLimit) crd, err := fixtures.CreateNewV1CustomResourceDefinition(structuralWithValidators, apiExtensionClient, dynamicClient)

} } }`) <ins> var structuralSchemaWithEmptyObject = []byte(` { "openAPIV3Schema": { "description": "weird CRD with empty spec, unstructured status. designed to fit test fixtures.", "type": "object", "x-kubernetes-validations": [ { "rule": "[has(self.spec), has(self.status)].exists_one(x, x)" } ], "properties": { "spec": { "type": "object" }, "status": { "type": "object", "additionalProperties": true } } } } `) </ins>