mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-08-06 13:00:44 +00:00
There may be some useful enhancements in https://github.com/cel-expr/cel-go/releases/tag/v0.28.0 and https://github.com/cel-expr/cel-go/releases/tag/v0.29.0, but primarily the goal is to get https://github.com/cel-expr/cel-go/pull/1357 (included in v0.29.0) because that is a relevant performance improvement for how DRA uses CEL. Updating to >= v0.28.1 is safe, in contrast to v0.28.0, because v0.28.1 includes https://github.com/cel-expr/cel-go/pull/1303 and therefore doesn't trigger https://github.com/kubernetes/kubernetes/issues/138334. As explained by Tristan Swadell (https://github.com/kubernetes/kubernetes/pull/140205#issuecomment-4877100299), startsWith now scales to the size of the substring rather than the target string, which changes some of the cost estimates. In another case, the substring has to be made larger to trip an expected cost budget error.
164 lines
4.3 KiB
Go
164 lines
4.3 KiB
Go
package cel
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/google/cel-go/common"
|
|
"github.com/google/cel-go/common/types"
|
|
)
|
|
|
|
// fieldPath represents a selection path to a field from a variable in a CEL environment.
|
|
type fieldPath struct {
|
|
celType *Type
|
|
// path represents the selection path to the field.
|
|
path string
|
|
description string
|
|
isLeaf bool
|
|
}
|
|
|
|
// Documentation implements the Documentor interface.
|
|
func (f *fieldPath) Documentation() *common.Doc {
|
|
return common.NewFieldDoc(f.path, f.celType.String(), f.description)
|
|
}
|
|
|
|
type documentationProvider interface {
|
|
// FindStructFieldDescription returns documentation for a field if available.
|
|
// Returns false if the field could not be found.
|
|
FindStructFieldDescription(typeName, fieldName string) (string, bool)
|
|
}
|
|
|
|
type backtrack struct {
|
|
// provider used to resolve types.
|
|
provider types.Provider
|
|
// paths of fields that have been visited along the path.
|
|
path []string
|
|
// types of fields that have been visited along the path. used to avoid cycles.
|
|
types []*Type
|
|
}
|
|
|
|
func (b *backtrack) push(pathStep string, celType *Type) {
|
|
b.path = append(b.path, pathStep)
|
|
b.types = append(b.types, celType)
|
|
}
|
|
|
|
func (b *backtrack) pop() {
|
|
b.path = b.path[:len(b.path)-1]
|
|
b.types = b.types[:len(b.types)-1]
|
|
}
|
|
|
|
func formatPath(path []string) string {
|
|
var buffer strings.Builder
|
|
for i, p := range path {
|
|
if i == 0 {
|
|
buffer.WriteString(p)
|
|
continue
|
|
}
|
|
if strings.HasPrefix(p, "[") {
|
|
buffer.WriteString(p)
|
|
continue
|
|
}
|
|
buffer.WriteString(".")
|
|
buffer.WriteString(p)
|
|
}
|
|
return buffer.String()
|
|
}
|
|
|
|
func (b *backtrack) expandFieldPaths(celType *Type, paths []*fieldPath) []*fieldPath {
|
|
if slices.ContainsFunc(b.types[:len(b.types)-1], func(t *Type) bool { return t.String() == celType.String() }) {
|
|
// Cycle detected, so stop expanding.
|
|
paths[len(paths)-1].isLeaf = false
|
|
return paths
|
|
}
|
|
switch celType.Kind() {
|
|
case types.StructKind:
|
|
fields, ok := b.provider.FindStructFieldNames(celType.String())
|
|
if !ok {
|
|
// Caller added this type to the path, so it must be a leaf.
|
|
paths[len(paths)-1].isLeaf = true
|
|
return paths
|
|
}
|
|
for _, field := range fields {
|
|
fieldType, ok := b.provider.FindStructFieldType(celType.String(), field)
|
|
if !ok {
|
|
// Field not found, either hidden or an error.
|
|
continue
|
|
}
|
|
b.push(field, celType)
|
|
description := ""
|
|
if docProvider, ok := b.provider.(documentationProvider); ok {
|
|
description, _ = docProvider.FindStructFieldDescription(celType.String(), field)
|
|
}
|
|
path := &fieldPath{
|
|
celType: fieldType.Type,
|
|
path: formatPath(b.path),
|
|
description: description,
|
|
isLeaf: false,
|
|
}
|
|
paths = append(paths, path)
|
|
paths = b.expandFieldPaths(fieldType.Type, paths)
|
|
b.pop()
|
|
}
|
|
return paths
|
|
case types.MapKind:
|
|
if len(celType.Parameters()) != 2 {
|
|
// dynamic map, so treat as a leaf.
|
|
paths[len(paths)-1].isLeaf = true
|
|
return paths
|
|
}
|
|
mapKeyType := celType.Parameters()[0]
|
|
mapValueType := celType.Parameters()[1]
|
|
// Add a placeholder for the map key kind (the zero value).
|
|
keyIdentifier := ""
|
|
switch mapKeyType.Kind() {
|
|
case types.StringKind:
|
|
keyIdentifier = "[\"\"]"
|
|
case types.IntKind:
|
|
keyIdentifier = "[0]"
|
|
case types.UintKind:
|
|
keyIdentifier = "[0u]"
|
|
case types.BoolKind:
|
|
keyIdentifier = "[false]"
|
|
default:
|
|
// Caller added this type to the path, so it must be a leaf.
|
|
paths[len(paths)-1].isLeaf = true
|
|
return paths
|
|
}
|
|
b.push(keyIdentifier, mapValueType)
|
|
defer b.pop()
|
|
return b.expandFieldPaths(mapValueType, paths)
|
|
case types.ListKind:
|
|
if len(celType.Parameters()) != 1 {
|
|
// dynamic list, so treat as a leaf.
|
|
paths[len(paths)-1].isLeaf = true
|
|
return paths
|
|
}
|
|
listElemType := celType.Parameters()[0]
|
|
b.push("[0]", listElemType)
|
|
defer b.pop()
|
|
return b.expandFieldPaths(listElemType, paths)
|
|
default:
|
|
paths[len(paths)-1].isLeaf = true
|
|
}
|
|
|
|
return paths
|
|
}
|
|
|
|
// fieldPathsForType expands the reachable fields from the given root identifier.
|
|
func fieldPathsForType(provider types.Provider, identifier string, celType *Type) []*fieldPath {
|
|
b := &backtrack{
|
|
provider: provider,
|
|
path: []string{identifier},
|
|
types: []*Type{celType},
|
|
}
|
|
paths := []*fieldPath{
|
|
{
|
|
celType: celType,
|
|
path: identifier,
|
|
isLeaf: false,
|
|
},
|
|
}
|
|
|
|
return b.expandFieldPaths(celType, paths)
|
|
}
|