From 8ff3c8d21e47adb57278a1f83cf0253134339b37 Mon Sep 17 00:00:00 2001 From: Amine Date: Mon, 23 Dec 2024 16:27:56 -0400 Subject: [PATCH] feat: parse expressions in `x-kubernetes-preserve-unknown` fields Previously, fields marked with `x-kubernetes-preserve-unknown-fields` were skipped during parsing. Now these fields are parsed to extract expressions, allowing for variable substitution in schemaless fields while still preserving unknown fields as specified by the k8s-open-api extension. --- pkg/graph/parser/parser.go | 13 +++++++--- pkg/graph/parser/parser_test.go | 45 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/pkg/graph/parser/parser.go b/pkg/graph/parser/parser.go index e761274..34ec81f 100644 --- a/pkg/graph/parser/parser.go +++ b/pkg/graph/parser/parser.go @@ -96,10 +96,17 @@ func parseObject(field map[string]interface{}, schema *spec.Schema, path, expect // Look for vendor schema extensions first if len(schema.VendorExtensible.Extensions) > 0 { - // If the schema has the x-kubernetes-preserve-unknown-fields extension, we should not - // parse the object and return an empty list of expressions. + // If the schema has the x-kubernetes-preserve-unknown-fields extension, we need to parse + // this field using the schemaless parser. This allows us to extract CEL expressions from + // fields that don't have a strict schema definition, while still preserving any unknown + // fields. This is particularly important for handling custom resources and fields that + // may contain arbitrary nested structures with potential CEL expressions. if enabled, ok := schema.VendorExtensible.Extensions[xKubernetesPreserveUnknownFields]; ok && enabled.(bool) { - return nil, nil + expressions, err := parseSchemalessResource(field, path) + if err != nil { + return nil, err + } + return expressions, nil } } diff --git a/pkg/graph/parser/parser_test.go b/pkg/graph/parser/parser_test.go index a885eee..57dca04 100644 --- a/pkg/graph/parser/parser_test.go +++ b/pkg/graph/parser/parser_test.go @@ -48,6 +48,14 @@ func TestParseResource(t *testing.T) { "${value}", }, }, + "schemalessField": map[string]interface{}{ + "key": "value", + "something": "${schemaless.value}", + "nestedSomething": map[string]interface{}{ + "key": "value", + "nested": "${schemaless.nested.value}", + }, + }, } schema := &spec.Schema{ @@ -107,6 +115,16 @@ func TestParseResource(t *testing.T) { }, }, }, + "schemalessField": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-preserve-unknown-fields": true, + }, + }, + }, }, }, } @@ -125,6 +143,8 @@ func TestParseResource(t *testing.T) { {Path: "specialCharacters[\"doted.annotation.key\"]", Expressions: []string{"dotedannotationvalue"}, ExpectedType: "string", StandaloneExpression: true}, {Path: "specialCharacters[\"\"]", Expressions: []string{"emptyannotation"}, ExpectedType: "string", StandaloneExpression: true}, {Path: "specialCharacters[\"array.name.with.dots\"][0]", Expressions: []string{"value"}, ExpectedType: "string", StandaloneExpression: true}, + {Path: "schemalessField.something", Expressions: []string{"schemaless.value"}, ExpectedType: "string", StandaloneExpression: true}, + {Path: "schemalessField.nestedSomething.nested", Expressions: []string{"schemaless.nested.value"}, ExpectedType: "string", StandaloneExpression: true}, } expressions, err := ParseResource(resource, schema) @@ -618,6 +638,31 @@ func TestParserEdgeCases(t *testing.T) { resource: map[string]interface{}{"name": "John", "age": 30}, expectedError: "", }, + { + name: "structured object with nested x-kubernetes-preserve-unknown-fields", + schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "id": {SchemaProps: spec.SchemaProps{Type: []string{"string"}}}, + "metadata": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-preserve-unknown-fields": true, + }, + }, + }, + }, + }, + }, + resource: map[string]interface{}{"id": "123", "metadata": map[string]interface{}{ + "name": "John", "age": 30, "test": "${test.value}", + }}, + expectedError: "", + }, } for _, tc := range testCases {