diff --git a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/escaping.go b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/escaping.go index 238762e8595..2f841b58bfc 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/escaping.go +++ b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/escaping.go @@ -17,14 +17,11 @@ limitations under the License. package model import ( - "fmt" - "strings" + "regexp" "k8s.io/apimachinery/pkg/util/sets" ) -// TODO: replace escaping with new rules described in kEP update - // celReservedSymbols is a list of RESERVED symbols defined in the CEL lexer. // No identifiers are allowed to collide with these symbols. // https://github.com/google/cel-spec/blob/master/doc/langdef.md#syntax @@ -36,47 +33,78 @@ var celReservedSymbols = sets.NewString( "var", "void", "while", ) -// celLanguageIdentifiers is a list of identifiers that are part of the CEL language. -// This does NOT include builtin macro or function identifiers. -// https://github.com/google/cel-spec/blob/master/doc/langdef.md#values -var celLanguageIdentifiers = sets.NewString( - "int", "uint", "double", "bool", "string", "bytes", "list", "map", "null_type", "type", -) +// expandMatcher matches the escape sequence, characters that are escaped, and characters that are unsupported +var expandMatcher = regexp.MustCompile(`(__|[-./]|[^a-zA-Z0-9-./_])`) -// IsRootReserved returns true if an identifier is reserved by CEL. Declaring root variables in CEL with -// these identifiers is not allowed and would result in an "overlapping identifier for name ''" -// CEL compilation error. -func IsRootReserved(prop string) bool { - return celLanguageIdentifiers.Has(prop) -} - -// Escape escapes identifiers in the AlwaysReservedIdentifiers set by prefixing ident with "_" and by prefixing -// any ident already prefixed with N '_' with N+1 '_'. -// For an identifier that does not require escaping, the identifier is returned as-is. -func Escape(ident string) string { - if strings.HasPrefix(ident, "_") || celReservedSymbols.Has(ident) { - return "_" + ident +// Escape escapes ident and returns a CEL identifier (of the form '[a-zA-Z_][a-zA-Z0-9_]*'), or returns +// false if the ident does not match the supported input format of `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*`. +// Escaping Rules: +// - '__' escapes to '__underscores__' +// - '.' escapes to '__dot__' +// - '-' escapes to '__dash__' +// - '/' escapes to '__slash__' +// - Identifiers that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are: "true", "false", +// "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if", "import", "let", loop", "package", +// "namespace", "return". +func Escape(ident string) (string, bool) { + if len(ident) == 0 || ('0' <= ident[0] && ident[0] <= '9') { + return "", false } - return ident -} - -// EscapeSlice returns identifiers with Escape applied to each. -func EscapeSlice(idents []string) []string { - result := make([]string, len(idents)) - for i, prop := range idents { - result[i] = Escape(prop) + if celReservedSymbols.Has(ident) { + return "__" + ident + "__", true } - return result -} - -// Unescape unescapes an identifier escaped by Escape. -func Unescape(escaped string) string { - if strings.HasPrefix(escaped, "_") { - trimmed := strings.TrimPrefix(escaped, "_") - if strings.HasPrefix(trimmed, "_") || celReservedSymbols.Has(trimmed) { - return trimmed + ok := true + ident = expandMatcher.ReplaceAllStringFunc(ident, func(s string) string { + switch s { + case "__": + return "__underscores__" + case ".": + return "__dot__" + case "-": + return "__dash__" + case "/": + return "__slash__" + default: // matched a unsupported supported + ok = false + return "" } - panic(fmt.Sprintf("failed to unescape improperly escaped string: %v", escaped)) + }) + if !ok { + return "", false } - return escaped + return ident, true +} + +var unexpandMatcher = regexp.MustCompile(`(_{2}[^_]+_{2})`) + +// Unescape unescapes an CEL identifier containing the escape sequences described in Escape, or return false if the +// string contains invalid escape sequences. The escaped input is expected to be a valid CEL identifier, but is +// not checked. +func Unescape(escaped string) (string, bool) { + ok := true + escaped = unexpandMatcher.ReplaceAllStringFunc(escaped, func(s string) string { + contents := s[2 : len(s)-2] + switch contents { + case "underscores": + return "__" + case "dot": + return "." + case "dash": + return "-" + case "slash": + return "/" + } + if celReservedSymbols.Has(contents) { + if len(s) != len(escaped) { + ok = false + } + return contents + } + ok = false + return "" + }) + if !ok { + return "", false + } + return escaped, true } diff --git a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/escaping_test.go b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/escaping_test.go index c0fee93ecc5..04f26b639f1 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/escaping_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/escaping_test.go @@ -17,99 +17,156 @@ limitations under the License. package model import ( + "fmt" + "regexp" "testing" + + fuzz "github.com/google/gofuzz" ) // TestEscaping tests that property names are escaped as expected. func TestEscaping(t *testing.T) { - cases := []struct{ - unescaped string - escaped string - reservedAtRoot bool - } { + cases := []struct { + unescaped string + escaped string + unescapable bool + }{ + // '.', '-', '/' and '__' are escaped since + // CEL only allows identifiers of the form: [a-zA-Z_][a-zA-Z0-9_]* + {unescaped: "a.a", escaped: "a__dot__a"}, + {unescaped: "a-a", escaped: "a__dash__a"}, + {unescaped: "a__a", escaped: "a__underscores__a"}, + {unescaped: "a.-/__a", escaped: "a__dot____dash____slash____underscores__a"}, + {unescaped: "a._a", escaped: "a__dot___a"}, + {unescaped: "a__.__a", escaped: "a__underscores____dot____underscores__a"}, + {unescaped: "a___a", escaped: "a__underscores___a"}, + {unescaped: "a____a", escaped: "a__underscores____underscores__a"}, + {unescaped: "a__dot__a", escaped: "a__underscores__dot__underscores__a"}, + {unescaped: "a__underscores__a", escaped: "a__underscores__underscores__underscores__a"}, // CEL lexer RESERVED keywords must be escaped - { unescaped: "true", escaped: "_true" }, - { unescaped: "false", escaped: "_false" }, - { unescaped: "null", escaped: "_null" }, - { unescaped: "in", escaped: "_in" }, - { unescaped: "as", escaped: "_as" }, - { unescaped: "break", escaped: "_break" }, - { unescaped: "const", escaped: "_const" }, - { unescaped: "continue", escaped: "_continue" }, - { unescaped: "else", escaped: "_else" }, - { unescaped: "for", escaped: "_for" }, - { unescaped: "function", escaped: "_function" }, - { unescaped: "if", escaped: "_if" }, - { unescaped: "import", escaped: "_import" }, - { unescaped: "let", escaped: "_let" }, - { unescaped: "loop", escaped: "_loop" }, - { unescaped: "package", escaped: "_package" }, - { unescaped: "namespace", escaped: "_namespace" }, - { unescaped: "return", escaped: "_return" }, - { unescaped: "var", escaped: "_var" }, - { unescaped: "void", escaped: "_void" }, - { unescaped: "while", escaped: "_while" }, - // CEL language identifiers do not need to be escaped, but collide with builtin language identifier if bound as - // root variable names. - // i.e. "self.int == 1" is legal, but "int == 1" is not. - { unescaped: "int", escaped: "int", reservedAtRoot: true }, - { unescaped: "uint", escaped: "uint", reservedAtRoot: true }, - { unescaped: "double", escaped: "double", reservedAtRoot: true }, - { unescaped: "bool", escaped: "bool", reservedAtRoot: true }, - { unescaped: "string", escaped: "string", reservedAtRoot: true }, - { unescaped: "bytes", escaped: "bytes", reservedAtRoot: true }, - { unescaped: "list", escaped: "list", reservedAtRoot: true }, - { unescaped: "map", escaped: "map", reservedAtRoot: true }, - { unescaped: "null_type", escaped: "null_type", reservedAtRoot: true }, - { unescaped: "type", escaped: "type", reservedAtRoot: true }, - // To prevent escaping from colliding with other identifiers, all identifiers prefixed by _s are escaped by - // prefixing them with N+1 _s. - { unescaped: "_if", escaped: "__if" }, - { unescaped: "__if", escaped: "___if" }, - { unescaped: "___if", escaped: "____if" }, - { unescaped: "_has", escaped: "__has" }, - { unescaped: "_int", escaped: "__int" }, - { unescaped: "_anything", escaped: "__anything" }, - // CEL macro and function names do not need to be escaped because the parser can disambiguate them from the function and - // macro identifiers. - { unescaped: "has", escaped: "has" }, - { unescaped: "all", escaped: "all" }, - { unescaped: "exists", escaped: "exists" }, - { unescaped: "exists_one", escaped: "exists_one" }, - { unescaped: "filter", escaped: "filter" }, - { unescaped: "size", escaped: "size" }, - { unescaped: "contains", escaped: "contains" }, - { unescaped: "startsWith", escaped: "startsWith" }, - { unescaped: "endsWith", escaped: "endsWith" }, - { unescaped: "matches", escaped: "matches" }, - { unescaped: "duration", escaped: "duration" }, - { unescaped: "timestamp", escaped: "timestamp" }, - { unescaped: "getDate", escaped: "getDate" }, - { unescaped: "getDayOfMonth", escaped: "getDayOfMonth" }, - { unescaped: "getDayOfWeek", escaped: "getDayOfWeek" }, - { unescaped: "getFullYear", escaped: "getFullYear" }, - { unescaped: "getHours", escaped: "getHours" }, - { unescaped: "getMilliseconds", escaped: "getMilliseconds" }, - { unescaped: "getMinutes", escaped: "getMinutes" }, - { unescaped: "getMonth", escaped: "getMonth" }, - { unescaped: "getSeconds", escaped: "getSeconds" }, + {unescaped: "true", escaped: "__true__"}, + {unescaped: "false", escaped: "__false__"}, + {unescaped: "null", escaped: "__null__"}, + {unescaped: "in", escaped: "__in__"}, + {unescaped: "as", escaped: "__as__"}, + {unescaped: "break", escaped: "__break__"}, + {unescaped: "const", escaped: "__const__"}, + {unescaped: "continue", escaped: "__continue__"}, + {unescaped: "else", escaped: "__else__"}, + {unescaped: "for", escaped: "__for__"}, + {unescaped: "function", escaped: "__function__"}, + {unescaped: "if", escaped: "__if__"}, + {unescaped: "import", escaped: "__import__"}, + {unescaped: "let", escaped: "__let__"}, + {unescaped: "loop", escaped: "__loop__"}, + {unescaped: "package", escaped: "__package__"}, + {unescaped: "namespace", escaped: "__namespace__"}, + {unescaped: "return", escaped: "__return__"}, + {unescaped: "var", escaped: "__var__"}, + {unescaped: "void", escaped: "__void__"}, + {unescaped: "while", escaped: "__while__"}, + // Not all property names are escapable + {unescaped: "@", unescapable: true}, + {unescaped: "1up", unescapable: true}, + {unescaped: "👑", unescapable: true}, + // CEL macro and function names do not need to be escaped because the parser keeps identifiers in a + // different namespace than function and macro names. + {unescaped: "has", escaped: "has"}, + {unescaped: "all", escaped: "all"}, + {unescaped: "exists", escaped: "exists"}, + {unescaped: "exists_one", escaped: "exists_one"}, + {unescaped: "filter", escaped: "filter"}, + {unescaped: "size", escaped: "size"}, + {unescaped: "contains", escaped: "contains"}, + {unescaped: "startsWith", escaped: "startsWith"}, + {unescaped: "endsWith", escaped: "endsWith"}, + {unescaped: "matches", escaped: "matches"}, + {unescaped: "duration", escaped: "duration"}, + {unescaped: "timestamp", escaped: "timestamp"}, + {unescaped: "getDate", escaped: "getDate"}, + {unescaped: "getDayOfMonth", escaped: "getDayOfMonth"}, + {unescaped: "getDayOfWeek", escaped: "getDayOfWeek"}, + {unescaped: "getFullYear", escaped: "getFullYear"}, + {unescaped: "getHours", escaped: "getHours"}, + {unescaped: "getMilliseconds", escaped: "getMilliseconds"}, + {unescaped: "getMinutes", escaped: "getMinutes"}, + {unescaped: "getMonth", escaped: "getMonth"}, + {unescaped: "getSeconds", escaped: "getSeconds"}, + // we don't escape a single _ + {unescaped: "_if", escaped: "_if"}, + {unescaped: "_has", escaped: "_has"}, + {unescaped: "_int", escaped: "_int"}, + {unescaped: "_anything", escaped: "_anything"}, } for _, tc := range cases { t.Run(tc.unescaped, func(t *testing.T) { - e := Escape(tc.unescaped) + e, escapable := Escape(tc.unescaped) + if tc.unescapable { + if escapable { + t.Errorf("Expected escapable=false, but got %t", escapable) + } + return + } + if !escapable { + t.Fatalf("Expected escapable=true, but got %t", escapable) + } if tc.escaped != e { t.Errorf("Expected %s to escape to %s, but got %s", tc.unescaped, tc.escaped, e) } - u := Unescape(tc.escaped) - if tc.unescaped != u { - t.Errorf("Expected %s to unescape to %s, but got %s", tc.escaped, tc.unescaped, e) + + if !validCelIdent.MatchString(e) { + t.Errorf("Expected %s to escape to a valid CEL identifier, but got %s", tc.unescaped, e) } - isRootReserved := IsRootReserved(tc.unescaped) - if tc.reservedAtRoot != isRootReserved { - t.Errorf("Expected isRootReserved=%t for %s, but got %t", tc.reservedAtRoot, tc.unescaped, isRootReserved) + u, ok := Unescape(tc.escaped) + if !ok { + t.Fatalf("Expected %s to be escapable, but it was not", tc.escaped) + } + if tc.unescaped != u { + t.Errorf("Expected %s to unescape to %s, but got %s", tc.escaped, tc.unescaped, u) } }) } } + +func TestUnescapeMalformed(t *testing.T) { + for _, s := range []string{"__int__extra", "__illegal__"} { + t.Run(s, func(t *testing.T) { + e, ok := Unescape(s) + if ok { + t.Fatalf("Expected %s to be unescapable, but it escaped to: %s", s, e) + } + }) + } +} + +func TestEscapingFuzz(t *testing.T) { + fuzzer := fuzz.New() + for i := 0; i < 1000; i++ { + var unescaped string + fuzzer.Fuzz(&unescaped) + t.Run(fmt.Sprintf("%d - '%s'", i, unescaped), func(t *testing.T) { + if len(unescaped) == 0 { + return + } + escaped, ok := Escape(unescaped) + if !ok { + return + } + + if !validCelIdent.MatchString(escaped) { + t.Errorf("Expected %s to escape to a valid CEL identifier, but got %s", unescaped, escaped) + } + u, ok := Unescape(escaped) + if !ok { + t.Fatalf("Expected %s to be unescapable, but it was not", escaped) + } + if unescaped != u { + t.Errorf("Expected %s to unescape to %s, but got %s", escaped, unescaped, u) + } + }) + } +} + +var validCelIdent = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`) diff --git a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas.go b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas.go index 74b1c5ef009..7d8e910da0c 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas.go +++ b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas.go @@ -18,45 +18,61 @@ import ( "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" ) - -// SchemaDeclTypes constructs a top-down set of DeclType instances whose name is derived from the root -// type name provided on the call, if not set to a custom type. -func SchemaDeclTypes(s *schema.Structural, maybeRootType string) (*DeclType, map[string]*DeclType) { - root := SchemaDeclType(s).MaybeAssignTypeName(maybeRootType) - types := FieldTypeMap(maybeRootType, root) - return root, types -} - -// SchemaDeclType returns the cel type name associated with the schema element. -func SchemaDeclType(s *schema.Structural) *DeclType { +// SchemaDeclType converts the structural schema to a CEL declaration, or returns nil if the +// the structural schema should not be exposed in CEL expressions. +// Set isResourceRoot to true for the root of a custom resource or embedded resource. +// +// Schemas with XPreserveUnknownFields not exposed unless they are objects. Array and "maps" schemas +// are not exposed if their items or additionalProperties schemas are not exposed. Object Properties are not exposed +// if their schema is not exposed. +// +// The CEL declaration for objects with XPreserveUnknownFields does not expose unknown fields. +func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType { if s == nil { return nil } if s.XIntOrString { - // schemas using this extension are not required to have a type, so they must be handled before type lookup - return intOrStringType - } - declType, found := openAPISchemaTypes[s.Type] - if !found { - return nil + // schemas using XIntOrString are not required to have a type. + + // intOrStringType represents the x-kubernetes-int-or-string union type in CEL expressions. + // In CEL, the type is represented as dynamic value, which can be thought of as a union type of all types. + // All type checking for XIntOrString is deferred to runtime, so all access to values of this type must + // be guarded with a type check, e.g.: + // + // To require that the string representation be a percentage: + // `type(intOrStringField) == string && intOrStringField.matches(r'(\d+(\.\d+)?%)')` + // To validate requirements on both the int and string representation: + // `type(intOrStringField) == int ? intOrStringField < 5 : double(intOrStringField.replace('%', '')) < 0.5 + // + return DynType } // We ignore XPreserveUnknownFields since we don't support validation rules on // data that we don't have schema information for. - if s.XEmbeddedResource { - // 'apiVersion', 'kind', 'metadata.name' and 'metadata.generateName' are always accessible - // to validation rules since this part of the schema is well known and validated when CRDs - // are created and updated. + if isResourceRoot { + // 'apiVersion', 'kind', 'metadata.name' and 'metadata.generateName' are always accessible to validator rules + // at the root of resources, even if not specified in the schema. + // This includes the root of a custom resource and the root of XEmbeddedResource objects. s = WithTypeAndObjectMeta(s) } - switch declType.TypeName() { - case ListType.TypeName(): - return NewListType(SchemaDeclType(s.Items)) - case MapType.TypeName(): + switch s.Type { + case "array": + if s.Items != nil { + itemsType := SchemaDeclType(s.Items, s.Items.XEmbeddedResource) + if itemsType != nil { + return NewListType(itemsType) + } + } + return nil + case "object": if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil { - return NewMapType(StringType, SchemaDeclType(s.AdditionalProperties.Structural)) + propsType := SchemaDeclType(s.AdditionalProperties.Structural, s.AdditionalProperties.Structural.XEmbeddedResource) + if propsType != nil { + return NewMapType(StringType, propsType) + } + return nil } fields := make(map[string]*DeclField, len(s.Properties)) @@ -73,23 +89,23 @@ func SchemaDeclType(s *schema.Structural) *DeclType { enumValues = append(enumValues, e.Object) } } - if fieldType := SchemaDeclType(&prop); fieldType != nil { - fields[Escape(name)] = &DeclField{ - Name: Escape(name), - Required: required[name], - Type: fieldType, - defaultValue: prop.Default.Object, - enumValues: enumValues, // Enum values are represented as strings in CEL + if fieldType := SchemaDeclType(&prop, prop.XEmbeddedResource); fieldType != nil { + if propName, ok := Escape(name); ok { + fields[propName] = &DeclField{ + Name: propName, + Required: required[name], + Type: fieldType, + defaultValue: prop.Default.Object, + enumValues: enumValues, // Enum values are represented as strings in CEL + } } } } return NewObjectType("object", fields) - case StringType.TypeName(): + case "string": if s.ValueValidation != nil { switch s.ValueValidation.Format { case "byte": - return StringType // OpenAPIv3 byte format represents base64 encoded string - case "binary": return BytesType case "duration": return DurationType @@ -97,39 +113,17 @@ func SchemaDeclType(s *schema.Structural) *DeclType { return TimestampType } } + return StringType + case "boolean": + return BoolType + case "number": + return DoubleType + case "integer": + return IntType } - return declType + return nil } -var ( - openAPISchemaTypes = map[string]*DeclType{ - "boolean": BoolType, - "number": DoubleType, - "integer": IntType, - "null": NullType, - "string": StringType, - "date": DateType, - "array": ListType, - "object": MapType, - } - - // intOrStringType represents the x-kubernetes-int-or-string union type in CEL expressions. - // In CEL, the type is represented as an object where either the srtVal - // or intVal field is set. In CEL, this allows for typesafe expressions like: - // - // require that the string representation be a percentage: - // `has(intOrStringField.strVal) && intOrStringField.strVal.matches(r'(\d+(\.\d+)?%)')` - // validate requirements on both the int and string representation: - // `has(intOrStringField.intVal) ? intOrStringField.intVal < 5 : double(intOrStringField.strVal.replace('%', '')) < 0.5 - // - intOrStringType = NewObjectType("intOrString", map[string]*DeclField{ - "strVal": {Name: "strVal", Type: StringType}, - "intVal": {Name: "intVal", Type: IntType}, - }) -) - -// TODO: embedded objects should have objectMeta only, and name and generateName are both optional - // WithTypeAndObjectMeta ensures the kind, apiVersion and // metadata.name and metadata.generateName properties are specified, making a shallow copy of the provided schema if needed. func WithTypeAndObjectMeta(s *schema.Structural) *schema.Structural { diff --git a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas_test.go b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas_test.go index 52a412364a3..37b7705597d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas_test.go @@ -21,12 +21,13 @@ import ( "github.com/google/cel-go/common/types" "google.golang.org/protobuf/proto" + "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" ) func TestSchemaDeclType(t *testing.T) { ts := testSchema() - cust := SchemaDeclType(ts) + cust := SchemaDeclType(ts, false) if cust.TypeName() != "object" { t.Errorf("incorrect type name, got %v, wanted object", cust.TypeName()) } @@ -82,7 +83,8 @@ func TestSchemaDeclType(t *testing.T) { func TestSchemaDeclTypes(t *testing.T) { ts := testSchema() - cust, typeMap := SchemaDeclTypes(ts, "CustomObject") + cust := SchemaDeclType(ts, true).MaybeAssignTypeName("CustomObject") + typeMap := FieldTypeMap("CustomObject", cust) nested, _ := cust.FindField("nested") metadata, _ := cust.FindField("metadata") expectedObjTypeMap := map[string]*DeclType{ diff --git a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types.go b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types.go index 69e895b370a..0822e1d7477 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types.go +++ b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types.go @@ -24,9 +24,9 @@ import ( "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/traits" + exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" "google.golang.org/protobuf/proto" - exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" ) @@ -306,15 +306,19 @@ func (f *DeclField) EnumValues() []ref.Val { // NewRuleTypes returns an Open API Schema-based type-system which is CEL compatible. func NewRuleTypes(kind string, schema *schema.Structural, + isResourceRoot bool, res Resolver) (*RuleTypes, error) { // Note, if the schema indicates that it's actually based on another proto // then prefer the proto definition. For expressions in the proto, a new field // annotation will be needed to indicate the expected environment and type of // the expression. - schemaTypes, err := newSchemaTypeProvider(kind, schema) + schemaTypes, err := newSchemaTypeProvider(kind, schema, isResourceRoot) if err != nil { return nil, err } + if schemaTypes == nil { + return nil, nil + } return &RuleTypes{ Schema: schema, ruleSchemaDeclTypes: schemaTypes, @@ -467,7 +471,6 @@ func (rt *RuleTypes) convertToCustomType(dyn *DynValue, declType *DeclType) *Dyn dyn.SetValue(obj) return dyn } - // TODO: handle complex map types which have non-string keys. fieldType := declType.ElemType for _, f := range v.fieldMap { f.Ref = rt.convertToCustomType(f.Ref, fieldType) @@ -485,8 +488,12 @@ func (rt *RuleTypes) convertToCustomType(dyn *DynValue, declType *DeclType) *Dyn } } -func newSchemaTypeProvider(kind string, schema *schema.Structural) (*schemaTypeProvider, error) { - root := SchemaDeclType(schema).MaybeAssignTypeName(kind) +func newSchemaTypeProvider(kind string, schema *schema.Structural, isResourceRoot bool) (*schemaTypeProvider, error) { + delType := SchemaDeclType(schema, isResourceRoot) + if delType == nil { + return nil, nil + } + root := delType.MaybeAssignTypeName(kind) types := FieldTypeMap(kind, root) return &schemaTypeProvider{ root: root, diff --git a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types_test.go b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types_test.go index 1251715cbd5..b4a8da8dfa2 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types_test.go @@ -67,7 +67,7 @@ func TestTypes_MapType(t *testing.T) { func TestTypes_RuleTypesFieldMapping(t *testing.T) { stdEnv, _ := cel.NewEnv() reg := NewRegistry(stdEnv) - rt, err := NewRuleTypes("CustomObject", testSchema(), reg) + rt, err := NewRuleTypes("CustomObject", testSchema(), true, reg) if err != nil { t.Fatal(err) } diff --git a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/value.go b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/value.go index 0861748d24a..eb827893b8f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/value.go +++ b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/value.go @@ -205,8 +205,6 @@ func (sv *structValue) ConvertToNative(typeDesc reflect.Type) (interface{}, erro return nil, fmt.Errorf("type conversion error from object to '%v'", typeDesc) } - // TODO: Special case handling for protobuf Struct and Any if needed - // Unwrap pointers, but track their use. isPtr := false if typeDesc.Kind() == reflect.Ptr {