mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 09:52:49 +00:00
Merge pull request #111071 from cici37/updateCEL
Pick up major changes from cel-go latest release
This commit is contained in:
commit
303f47c0c0
4
go.mod
4
go.mod
@ -176,7 +176,7 @@ require (
|
||||
github.com/gofrs/uuid v4.0.0+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/cel-go v0.12.3 // indirect
|
||||
github.com/google/cel-go v0.12.4 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
||||
@ -390,7 +390,7 @@ replace (
|
||||
github.com/golangplus/testing => github.com/golangplus/testing v1.0.0
|
||||
github.com/google/btree => github.com/google/btree v1.0.1
|
||||
github.com/google/cadvisor => github.com/google/cadvisor v0.44.1
|
||||
github.com/google/cel-go => github.com/google/cel-go v0.12.3
|
||||
github.com/google/cel-go => github.com/google/cel-go v0.12.4
|
||||
github.com/google/gnostic => github.com/google/gnostic v0.5.7-v3refs
|
||||
github.com/google/go-cmp => github.com/google/go-cmp v0.5.6
|
||||
github.com/google/gofuzz => github.com/google/gofuzz v1.1.0
|
||||
|
4
go.sum
4
go.sum
@ -218,8 +218,8 @@ github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/cadvisor v0.44.1 h1:hsAxDZOY+5xSCXH12d/G9cxYTfP+32cMT3J7aatrgDY=
|
||||
github.com/google/cadvisor v0.44.1/go.mod h1:GQ9KQfz0iNHQk3D6ftzJWK4TXabfIgM10Oy3FkR+Gzg=
|
||||
github.com/google/cel-go v0.12.3 h1:t7A5dK8X/wV/Bex9DzB70QQsIJ9xDcN/CFr/uExOOuw=
|
||||
github.com/google/cel-go v0.12.3/go.mod h1:Av7CU6r6X3YmcHR9GXqVDaEJYfEtSxl6wvIjUQTriCw=
|
||||
github.com/google/cel-go v0.12.4 h1:YINKfuHZ8n72tPOqSPZBwGiDpew2CJS48mdM5W8LZQU=
|
||||
github.com/google/cel-go v0.12.4/go.mod h1:Av7CU6r6X3YmcHR9GXqVDaEJYfEtSxl6wvIjUQTriCw=
|
||||
github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
|
||||
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
|
@ -7,7 +7,7 @@ go 1.18
|
||||
require (
|
||||
github.com/emicklei/go-restful/v3 v3.8.0
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/google/cel-go v0.12.3
|
||||
github.com/google/cel-go v0.12.4
|
||||
github.com/google/gnostic v0.5.7-v3refs
|
||||
github.com/google/go-cmp v0.5.6
|
||||
github.com/google/gofuzz v1.1.0
|
||||
|
@ -198,8 +198,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/cel-go v0.12.3 h1:t7A5dK8X/wV/Bex9DzB70QQsIJ9xDcN/CFr/uExOOuw=
|
||||
github.com/google/cel-go v0.12.3/go.mod h1:Av7CU6r6X3YmcHR9GXqVDaEJYfEtSxl6wvIjUQTriCw=
|
||||
github.com/google/cel-go v0.12.4 h1:YINKfuHZ8n72tPOqSPZBwGiDpew2CJS48mdM5W8LZQU=
|
||||
github.com/google/cel-go v0.12.4/go.mod h1:Av7CU6r6X3YmcHR9GXqVDaEJYfEtSxl6wvIjUQTriCw=
|
||||
github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
|
||||
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -24,10 +24,6 @@ import (
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library"
|
||||
@ -84,7 +80,7 @@ func getBaseEnv() (*cel.Env, error) {
|
||||
// Validate function declarations once during base env initialization,
|
||||
// so they don't need to be evaluated each time a CEL rule is compiled.
|
||||
// This is a relatively expensive operation.
|
||||
opts = append(opts, cel.EagerlyValidateDeclarations(true))
|
||||
opts = append(opts, cel.EagerlyValidateDeclarations(true), cel.DefaultUTCTimeZone(true))
|
||||
opts = append(opts, library.ExtensionLibs...)
|
||||
|
||||
initEnv, initEnvErr = cel.NewEnv(opts...)
|
||||
@ -108,7 +104,7 @@ func Compile(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) ([]
|
||||
}
|
||||
celRules := s.Extensions.XValidations
|
||||
|
||||
var propDecls []*expr.Decl
|
||||
var propDecls []cel.EnvOption
|
||||
var root *celmodel.DeclType
|
||||
var ok bool
|
||||
baseEnv, err := getBaseEnv()
|
||||
@ -136,9 +132,9 @@ func Compile(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) ([]
|
||||
}
|
||||
root = rootDecl.MaybeAssignTypeName(scopedTypeName)
|
||||
}
|
||||
propDecls = append(propDecls, decls.NewVar(ScopedVarName, root.ExprType()))
|
||||
propDecls = append(propDecls, decls.NewVar(OldScopedVarName, root.ExprType()))
|
||||
opts = append(opts, cel.Declarations(propDecls...))
|
||||
propDecls = append(propDecls, cel.Variable(ScopedVarName, root.CelType()))
|
||||
propDecls = append(propDecls, cel.Variable(OldScopedVarName, root.CelType()))
|
||||
opts = append(opts, propDecls...)
|
||||
env, err := baseEnv.Extend(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -165,7 +161,7 @@ func compileRule(rule apiextensions.ValidationRule, env *cel.Env, perCallLimit u
|
||||
compilationResult.Error = &Error{ErrorTypeInvalid, "compilation failed: " + issues.String()}
|
||||
return
|
||||
}
|
||||
if !proto.Equal(ast.ResultType(), decls.Bool) {
|
||||
if ast.OutputType() != cel.BoolType {
|
||||
compilationResult.Error = &Error{ErrorTypeInvalid, "cel expression must evaluate to a bool"}
|
||||
return
|
||||
}
|
||||
|
@ -20,13 +20,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
func TestLibraryCompatibility(t *testing.T) {
|
||||
functionNames := map[string]struct{}{}
|
||||
|
||||
decls := map[cel.Library][]*exprpb.Decl{
|
||||
decls := map[cel.Library]map[string][]cel.FunctionOpt{
|
||||
urlsLib: urlLibraryDecls,
|
||||
listsLib: listsLibraryDecls,
|
||||
regexLib: regexLibraryDecls,
|
||||
@ -34,9 +33,9 @@ func TestLibraryCompatibility(t *testing.T) {
|
||||
if len(k8sExtensionLibs) != len(decls) {
|
||||
t.Errorf("Expected the same number of libraries in the ExtensionLibs as are tested for compatibility")
|
||||
}
|
||||
for _, l := range decls {
|
||||
for _, d := range l {
|
||||
functionNames[d.GetName()] = struct{}{}
|
||||
for _, decl := range decls {
|
||||
for name := range decl {
|
||||
functionNames[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,12 +20,10 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"github.com/google/cel-go/interpreter/functions"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
// Lists provides a CEL function library extension of list utility functions.
|
||||
@ -101,120 +99,84 @@ var listsLib = &lists{}
|
||||
|
||||
type lists struct{}
|
||||
|
||||
var paramA = decls.NewTypeParamType("A")
|
||||
var paramA = cel.TypeParamType("A")
|
||||
|
||||
// CEL typeParams can be used to constraint to a specific trait (e.g. traits.ComparableType) if the 1st operand is the type to constrain.
|
||||
// But the functions we need to constrain are <list<paramType>>, not just <paramType>.
|
||||
var summableTypes = map[string]*exprpb.Type{"int": decls.Int, "uint": decls.Uint, "double": decls.Double, "duration": decls.Duration}
|
||||
var comparableTypes = map[string]*exprpb.Type{"bool": decls.Bool, "int": decls.Int, "uint": decls.Uint, "double": decls.Double,
|
||||
"duration": decls.Duration, "timestamp": decls.Timestamp, "string": decls.String, "bytes": decls.Bytes}
|
||||
// Make sure the order of overload set is deterministic
|
||||
type namedCELType struct {
|
||||
typeName string
|
||||
celType *cel.Type
|
||||
}
|
||||
|
||||
var summableTypes = []namedCELType{
|
||||
{typeName: "int", celType: cel.IntType},
|
||||
{typeName: "uint", celType: cel.UintType},
|
||||
{typeName: "double", celType: cel.DoubleType},
|
||||
{typeName: "duration", celType: cel.DurationType},
|
||||
}
|
||||
|
||||
var zeroValuesOfSummableTypes = map[string]ref.Val{
|
||||
"int": types.Int(0),
|
||||
"uint": types.Uint(0),
|
||||
"double": types.Double(0.0),
|
||||
"duration": types.Duration{Duration: 0},
|
||||
}
|
||||
var comparableTypes = []namedCELType{
|
||||
{typeName: "int", celType: cel.IntType},
|
||||
{typeName: "uint", celType: cel.UintType},
|
||||
{typeName: "double", celType: cel.DoubleType},
|
||||
{typeName: "bool", celType: cel.BoolType},
|
||||
{typeName: "duration", celType: cel.DurationType},
|
||||
{typeName: "timestamp", celType: cel.TimestampType},
|
||||
{typeName: "string", celType: cel.StringType},
|
||||
{typeName: "bytes", celType: cel.BytesType},
|
||||
}
|
||||
|
||||
// WARNING: All library additions or modifications must follow
|
||||
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2876-crd-validation-expression-language#function-library-updates
|
||||
var listsLibraryDecls = []*exprpb.Decl{
|
||||
decls.NewFunction("isSorted",
|
||||
templatedOverloads(comparableTypes, func(name string, paramType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload {
|
||||
return decls.NewInstanceOverload(fmt.Sprintf("list_%s_is_sorted_bool", name),
|
||||
[]*exprpb.Type{decls.NewListType(paramType)},
|
||||
decls.Bool)
|
||||
})...,
|
||||
),
|
||||
decls.NewFunction("sum",
|
||||
templatedOverloads(summableTypes, func(name string, paramType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload {
|
||||
return decls.NewInstanceOverload(fmt.Sprintf("list_%s_sum_%s", name, name),
|
||||
[]*exprpb.Type{decls.NewListType(paramType)},
|
||||
paramType)
|
||||
})...,
|
||||
),
|
||||
decls.NewFunction("max",
|
||||
templatedOverloads(comparableTypes, func(name string, paramType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload {
|
||||
return decls.NewInstanceOverload(fmt.Sprintf("list_%s_max_%s", name, name),
|
||||
[]*exprpb.Type{decls.NewListType(paramType)},
|
||||
paramType)
|
||||
})...,
|
||||
),
|
||||
decls.NewFunction("min",
|
||||
templatedOverloads(comparableTypes, func(name string, paramType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload {
|
||||
return decls.NewInstanceOverload(fmt.Sprintf("list_%s_min_%s", name, name),
|
||||
[]*exprpb.Type{decls.NewListType(paramType)},
|
||||
paramType)
|
||||
})...,
|
||||
),
|
||||
decls.NewFunction("indexOf",
|
||||
decls.NewInstanceOverload("list_a_index_of_int",
|
||||
[]*exprpb.Type{decls.NewListType(paramA), paramA},
|
||||
decls.Int),
|
||||
),
|
||||
decls.NewFunction("lastIndexOf",
|
||||
decls.NewInstanceOverload("list_a_last_index_of_int",
|
||||
[]*exprpb.Type{decls.NewListType(paramA), paramA},
|
||||
decls.Int),
|
||||
),
|
||||
var listsLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"isSorted": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
|
||||
return cel.MemberOverload(fmt.Sprintf("list_%s_is_sorted_bool", name),
|
||||
[]*cel.Type{cel.ListType(paramType)}, cel.BoolType, cel.UnaryBinding(isSorted))
|
||||
}),
|
||||
"sum": templatedOverloads(summableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
|
||||
return cel.MemberOverload(fmt.Sprintf("list_%s_sum_%s", name, name),
|
||||
[]*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(func(list ref.Val) ref.Val {
|
||||
return sum(
|
||||
func() ref.Val {
|
||||
return zeroValuesOfSummableTypes[name]
|
||||
})(list)
|
||||
}))
|
||||
}),
|
||||
"max": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
|
||||
return cel.MemberOverload(fmt.Sprintf("list_%s_max_%s", name, name),
|
||||
[]*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(max()))
|
||||
}),
|
||||
"min": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
|
||||
return cel.MemberOverload(fmt.Sprintf("list_%s_min_%s", name, name),
|
||||
[]*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(min()))
|
||||
}),
|
||||
"indexOf": {
|
||||
cel.MemberOverload("list_a_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType,
|
||||
cel.BinaryBinding(indexOf)),
|
||||
},
|
||||
"lastIndexOf": {
|
||||
cel.MemberOverload("list_a_last_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType,
|
||||
cel.BinaryBinding(lastIndexOf)),
|
||||
},
|
||||
}
|
||||
|
||||
func (*lists) CompileOptions() []cel.EnvOption {
|
||||
return []cel.EnvOption{
|
||||
cel.Declarations(listsLibraryDecls...),
|
||||
options := []cel.EnvOption{}
|
||||
for name, overloads := range listsLibraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (*lists) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{
|
||||
cel.Functions(
|
||||
&functions.Overload{
|
||||
Operator: "isSorted",
|
||||
Unary: isSorted,
|
||||
},
|
||||
// if 'sum' is called directly, it is via dynamic dispatch, and we infer the type from the 1st element of the
|
||||
// list if it has one, otherwise we return int64(0)
|
||||
&functions.Overload{
|
||||
Operator: "sum",
|
||||
Unary: dynSum(),
|
||||
},
|
||||
// use overload names for sum so an initial accumulator value can be assigned to each
|
||||
&functions.Overload{
|
||||
Operator: "list_int_sum_int",
|
||||
Unary: sum(func() ref.Val {
|
||||
return types.Int(0)
|
||||
}),
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "list_uint_sum_uint",
|
||||
Unary: sum(func() ref.Val {
|
||||
return types.Uint(0)
|
||||
}),
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "list_double_sum_double",
|
||||
Unary: sum(func() ref.Val {
|
||||
return types.Double(0.0)
|
||||
}),
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "list_duration_sum_duration",
|
||||
Unary: sum(func() ref.Val {
|
||||
return types.Duration{Duration: 0}
|
||||
}),
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "max",
|
||||
Unary: max(),
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "min",
|
||||
Unary: min(),
|
||||
},
|
||||
// use overload names for indexOf and lastIndexOf to de-conflict with function of same name in strings extension library
|
||||
&functions.Overload{
|
||||
Operator: "list_a_index_of_int",
|
||||
Binary: indexOf,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "list_a_last_index_of_int",
|
||||
Binary: lastIndexOf,
|
||||
},
|
||||
),
|
||||
}
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func isSorted(val ref.Val) ref.Val {
|
||||
@ -240,38 +202,6 @@ func isSorted(val ref.Val) ref.Val {
|
||||
return types.True
|
||||
}
|
||||
|
||||
func dynSum() functions.UnaryOp {
|
||||
return func(val ref.Val) ref.Val {
|
||||
iterable, ok := val.(traits.Iterable)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(val)
|
||||
}
|
||||
it := iterable.Iterator()
|
||||
var initval ref.Val
|
||||
if it.HasNext() == types.True {
|
||||
first := it.Next()
|
||||
switch first.Type() {
|
||||
case types.IntType:
|
||||
initval = types.Int(0)
|
||||
case types.UintType:
|
||||
initval = types.Uint(0)
|
||||
case types.DoubleType:
|
||||
initval = types.Double(0.0)
|
||||
case types.DurationType:
|
||||
initval = types.Duration{Duration: 0}
|
||||
default:
|
||||
return types.MaybeNoSuchOverloadErr(first)
|
||||
}
|
||||
} else {
|
||||
initval = types.Int(0)
|
||||
}
|
||||
initFn := func() ref.Val {
|
||||
return initval
|
||||
}
|
||||
return sum(initFn)(val)
|
||||
}
|
||||
}
|
||||
|
||||
func sum(init func() ref.Val) functions.UnaryOp {
|
||||
return func(val ref.Val) ref.Val {
|
||||
i := init()
|
||||
@ -375,11 +305,11 @@ func lastIndexOf(list ref.Val, item ref.Val) ref.Val {
|
||||
|
||||
// templatedOverloads returns overloads for each of the provided types. The template function is called with each type
|
||||
// name (map key) and type to construct the overloads.
|
||||
func templatedOverloads(types map[string]*exprpb.Type, template func(name string, t *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload) []*exprpb.Decl_FunctionDecl_Overload {
|
||||
overloads := make([]*exprpb.Decl_FunctionDecl_Overload, len(types))
|
||||
func templatedOverloads(types []namedCELType, template func(name string, t *cel.Type) cel.FunctionOpt) []cel.FunctionOpt {
|
||||
overloads := make([]cel.FunctionOpt, len(types))
|
||||
i := 0
|
||||
for name, t := range types {
|
||||
overloads[i] = template(name, t)
|
||||
for _, t := range types {
|
||||
overloads[i] = template(t.typeName, t.celType)
|
||||
i++
|
||||
}
|
||||
return overloads
|
||||
|
@ -20,12 +20,9 @@ import (
|
||||
"regexp"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
"github.com/google/cel-go/interpreter/functions"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
// Regex provides a CEL function library extension of regex utility functions.
|
||||
@ -55,59 +52,33 @@ var regexLib = ®ex{}
|
||||
|
||||
type regex struct{}
|
||||
|
||||
var regexLibraryDecls = []*exprpb.Decl{
|
||||
|
||||
decls.NewFunction("find",
|
||||
decls.NewInstanceOverload("string_find_string",
|
||||
[]*exprpb.Type{decls.String, decls.String},
|
||||
decls.String),
|
||||
),
|
||||
decls.NewFunction("findAll",
|
||||
decls.NewInstanceOverload("string_find_all_string",
|
||||
[]*exprpb.Type{decls.String, decls.String},
|
||||
decls.NewListType(decls.String)),
|
||||
decls.NewInstanceOverload("string_find_all_string_int",
|
||||
[]*exprpb.Type{decls.String, decls.String, decls.Int},
|
||||
decls.NewListType(decls.String)),
|
||||
),
|
||||
var regexLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"find": {
|
||||
cel.MemberOverload("string_find_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType,
|
||||
cel.BinaryBinding(find))},
|
||||
"findAll": {
|
||||
cel.MemberOverload("string_find_all_string", []*cel.Type{cel.StringType, cel.StringType},
|
||||
cel.ListType(cel.StringType),
|
||||
cel.BinaryBinding(func(str, regex ref.Val) ref.Val {
|
||||
return findAll(str, regex, types.Int(-1))
|
||||
})),
|
||||
cel.MemberOverload("string_find_all_string_int",
|
||||
[]*cel.Type{cel.StringType, cel.StringType, cel.IntType},
|
||||
cel.ListType(cel.StringType),
|
||||
cel.FunctionBinding(findAll)),
|
||||
},
|
||||
}
|
||||
|
||||
func (*regex) CompileOptions() []cel.EnvOption {
|
||||
return []cel.EnvOption{
|
||||
cel.Declarations(regexLibraryDecls...),
|
||||
options := []cel.EnvOption{}
|
||||
for name, overloads := range regexLibraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (*regex) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{
|
||||
cel.Functions(
|
||||
&functions.Overload{
|
||||
Operator: "find",
|
||||
Binary: find,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "string_find_string",
|
||||
Binary: find,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "findAll",
|
||||
Binary: func(str, regex ref.Val) ref.Val {
|
||||
return findAll(str, regex, types.Int(-1))
|
||||
},
|
||||
Function: findAll,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "string_find_all_string",
|
||||
Binary: func(str, regex ref.Val) ref.Val {
|
||||
return findAll(str, regex, types.Int(-1))
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "string_find_all_string_int",
|
||||
Function: findAll,
|
||||
},
|
||||
),
|
||||
}
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func find(strVal ref.Val, regexVal ref.Val) ref.Val {
|
||||
|
@ -20,12 +20,8 @@ import (
|
||||
"net/url"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/interpreter/functions"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model"
|
||||
)
|
||||
|
||||
@ -111,124 +107,44 @@ var urlsLib = &urls{}
|
||||
|
||||
type urls struct{}
|
||||
|
||||
var urlLibraryDecls = []*exprpb.Decl{
|
||||
decls.NewFunction("url",
|
||||
decls.NewOverload("string_to_url",
|
||||
[]*exprpb.Type{decls.String},
|
||||
model.URLObject),
|
||||
),
|
||||
decls.NewFunction("getScheme",
|
||||
decls.NewInstanceOverload("url_get_scheme",
|
||||
[]*exprpb.Type{model.URLObject},
|
||||
decls.String),
|
||||
),
|
||||
decls.NewFunction("getHost",
|
||||
decls.NewInstanceOverload("url_get_host",
|
||||
[]*exprpb.Type{model.URLObject},
|
||||
decls.String),
|
||||
),
|
||||
decls.NewFunction("getHostname",
|
||||
decls.NewInstanceOverload("url_get_hostname",
|
||||
[]*exprpb.Type{model.URLObject},
|
||||
decls.String),
|
||||
),
|
||||
decls.NewFunction("getPort",
|
||||
decls.NewInstanceOverload("url_get_port",
|
||||
[]*exprpb.Type{model.URLObject},
|
||||
decls.String),
|
||||
),
|
||||
decls.NewFunction("getEscapedPath",
|
||||
decls.NewInstanceOverload("url_get_escaped_path",
|
||||
[]*exprpb.Type{model.URLObject},
|
||||
decls.String),
|
||||
),
|
||||
decls.NewFunction("getQuery",
|
||||
decls.NewInstanceOverload("url_get_query",
|
||||
[]*exprpb.Type{model.URLObject},
|
||||
decls.NewMapType(decls.String, decls.NewListType(decls.String))),
|
||||
),
|
||||
decls.NewFunction("isURL",
|
||||
decls.NewOverload("is_url_string",
|
||||
[]*exprpb.Type{decls.String},
|
||||
decls.Bool),
|
||||
),
|
||||
var urlLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"url": {
|
||||
cel.Overload("string_to_url", []*cel.Type{cel.StringType}, model.URLType,
|
||||
cel.UnaryBinding(stringToUrl))},
|
||||
"getScheme": {
|
||||
cel.MemberOverload("url_get_scheme", []*cel.Type{model.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getScheme))},
|
||||
"getHost": {
|
||||
cel.MemberOverload("url_get_host", []*cel.Type{model.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getHost))},
|
||||
"getHostname": {
|
||||
cel.MemberOverload("url_get_hostname", []*cel.Type{model.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getHostname))},
|
||||
"getPort": {
|
||||
cel.MemberOverload("url_get_port", []*cel.Type{model.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getPort))},
|
||||
"getEscapedPath": {
|
||||
cel.MemberOverload("url_get_escaped_path", []*cel.Type{model.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getEscapedPath))},
|
||||
"getQuery": {
|
||||
cel.MemberOverload("url_get_query", []*cel.Type{model.URLType},
|
||||
cel.MapType(cel.StringType, cel.ListType(cel.StringType)),
|
||||
cel.UnaryBinding(getQuery))},
|
||||
"isURL": {
|
||||
cel.Overload("is_url_string", []*cel.Type{cel.StringType}, cel.BoolType,
|
||||
cel.UnaryBinding(isURL))},
|
||||
}
|
||||
|
||||
func (*urls) CompileOptions() []cel.EnvOption {
|
||||
return []cel.EnvOption{
|
||||
cel.Declarations(urlLibraryDecls...),
|
||||
options := []cel.EnvOption{}
|
||||
for name, overloads := range urlLibraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (*urls) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{
|
||||
cel.Functions(
|
||||
&functions.Overload{
|
||||
Operator: "url",
|
||||
Unary: stringToUrl,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "string_to_url",
|
||||
Unary: stringToUrl,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "getScheme",
|
||||
Unary: getScheme,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "url_get_scheme",
|
||||
Unary: getScheme,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "getHost",
|
||||
Unary: getHost,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "url_get_host",
|
||||
Unary: getHost,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "getHostname",
|
||||
Unary: getHostname,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "url_get_hostname",
|
||||
Unary: getHostname,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "getPort",
|
||||
Unary: getPort,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "url_get_port",
|
||||
Unary: getPort,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "getEscapedPath",
|
||||
Unary: getEscapedPath,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "url_get_escaped_path",
|
||||
Unary: getEscapedPath,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "getQuery",
|
||||
Unary: getQuery,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "url_get_query",
|
||||
Unary: getQuery,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "isURL",
|
||||
Unary: isURL,
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "is_url_string",
|
||||
Unary: isURL,
|
||||
},
|
||||
),
|
||||
}
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func stringToUrl(arg ref.Val) ref.Val {
|
||||
|
@ -1605,8 +1605,7 @@ func TestValidationExpressions(t *testing.T) {
|
||||
"dyn([1, 2]).sum() == 3",
|
||||
"dyn([1.0, 2.0]).sum() == 3.0",
|
||||
|
||||
// TODO: enable once type system fix it made to CEL
|
||||
//"[].sum() == 0", // An empty list returns an 0 int
|
||||
"[].sum() == 0", // An empty list returns an 0 int
|
||||
},
|
||||
errors: map[string]string{
|
||||
// return an error for min/max on empty list
|
||||
|
@ -17,7 +17,7 @@ package model
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
@ -72,7 +72,7 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
|
||||
// To validate requirements on both the int and string representation:
|
||||
// `type(intOrStringField) == int ? intOrStringField < 5 : double(intOrStringField.replace('%', '')) < 0.5
|
||||
//
|
||||
dyn := newSimpleType("dyn", decls.Dyn, nil)
|
||||
dyn := newSimpleType("dyn", cel.DynType, nil)
|
||||
// handle x-kubernetes-int-or-string by returning the max length of the largest possible string
|
||||
dyn.MaxElements = maxRequestSizeBytes - 2
|
||||
return dyn
|
||||
@ -149,7 +149,7 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
|
||||
if s.ValueValidation != nil {
|
||||
switch s.ValueValidation.Format {
|
||||
case "byte":
|
||||
byteWithMaxLength := newSimpleType("bytes", decls.Bytes, types.Bytes([]byte{}))
|
||||
byteWithMaxLength := newSimpleType("bytes", cel.BytesType, types.Bytes([]byte{}))
|
||||
if s.ValueValidation.MaxLength != nil {
|
||||
byteWithMaxLength.MaxElements = zeroIfNegative(*s.ValueValidation.MaxLength)
|
||||
} else {
|
||||
@ -157,16 +157,16 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
|
||||
}
|
||||
return byteWithMaxLength
|
||||
case "duration":
|
||||
durationWithMaxLength := newSimpleType("duration", decls.Duration, types.Duration{Duration: time.Duration(0)})
|
||||
durationWithMaxLength := newSimpleType("duration", cel.DurationType, types.Duration{Duration: time.Duration(0)})
|
||||
durationWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
|
||||
return durationWithMaxLength
|
||||
case "date", "date-time":
|
||||
timestampWithMaxLength := newSimpleType("timestamp", decls.Timestamp, types.Timestamp{Time: time.Time{}})
|
||||
timestampWithMaxLength := newSimpleType("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}})
|
||||
timestampWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
|
||||
return timestampWithMaxLength
|
||||
}
|
||||
}
|
||||
strWithMaxLength := newSimpleType("string", decls.String, types.String(""))
|
||||
strWithMaxLength := newSimpleType("string", cel.StringType, types.String(""))
|
||||
if s.ValueValidation != nil && s.ValueValidation.MaxLength != nil {
|
||||
// multiply the user-provided max length by 4 in the case of an otherwise-untyped string
|
||||
// we do this because the OpenAPIv3 spec indicates that maxLength is specified in runes/code points,
|
||||
|
@ -107,8 +107,16 @@ func TestSchemaDeclTypes(t *testing.T) {
|
||||
t.Errorf("missing type in rule types: %s", exp)
|
||||
continue
|
||||
}
|
||||
if !proto.Equal(expType.ExprType(), actType.ExprType()) {
|
||||
t.Errorf("incompatible CEL types. got=%v, wanted=%v", actType.ExprType(), expType.ExprType())
|
||||
expT, err := expType.ExprType()
|
||||
if err != nil {
|
||||
t.Errorf("fail to get cel type: %s", err)
|
||||
}
|
||||
actT, err := actType.ExprType()
|
||||
if err != nil {
|
||||
t.Errorf("fail to get cel type: %s", err)
|
||||
}
|
||||
if !proto.Equal(expT, actT) {
|
||||
t.Errorf("incompatible CEL types. got=%v, wanted=%v", expT, actT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
@ -41,7 +40,7 @@ func NewListType(elem *DeclType, maxItems int64) *DeclType {
|
||||
name: "list",
|
||||
ElemType: elem,
|
||||
MaxElements: maxItems,
|
||||
exprType: decls.NewListType(elem.ExprType()),
|
||||
celType: cel.ListType(elem.CelType()),
|
||||
defaultValue: NewListValue(),
|
||||
}
|
||||
}
|
||||
@ -53,7 +52,7 @@ func NewMapType(key, elem *DeclType, maxProperties int64) *DeclType {
|
||||
KeyType: key,
|
||||
ElemType: elem,
|
||||
MaxElements: maxProperties,
|
||||
exprType: decls.NewMapType(key.ExprType(), elem.ExprType()),
|
||||
celType: cel.MapType(key.CelType(), elem.CelType()),
|
||||
defaultValue: NewMapValue(),
|
||||
}
|
||||
}
|
||||
@ -63,39 +62,17 @@ func NewObjectType(name string, fields map[string]*DeclField) *DeclType {
|
||||
t := &DeclType{
|
||||
name: name,
|
||||
Fields: fields,
|
||||
exprType: decls.NewObjectType(name),
|
||||
celType: cel.ObjectType(name),
|
||||
traitMask: traits.FieldTesterType | traits.IndexerType,
|
||||
}
|
||||
t.defaultValue = NewObjectValue(t)
|
||||
return t
|
||||
}
|
||||
|
||||
// NewObjectTypeRef returns a reference to an object type by name
|
||||
func NewObjectTypeRef(name string) *DeclType {
|
||||
t := &DeclType{
|
||||
name: name,
|
||||
exprType: decls.NewObjectType(name),
|
||||
traitMask: traits.FieldTesterType | traits.IndexerType,
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// NewTypeParam creates a type parameter type with a simple name.
|
||||
//
|
||||
// Type parameters are resolved at compilation time to concrete types, or CEL 'dyn' type if no
|
||||
// type assignment can be inferred.
|
||||
func NewTypeParam(name string) *DeclType {
|
||||
return &DeclType{
|
||||
name: name,
|
||||
TypeParam: true,
|
||||
exprType: decls.NewTypeParamType(name),
|
||||
}
|
||||
}
|
||||
|
||||
func newSimpleType(name string, exprType *exprpb.Type, zeroVal ref.Val) *DeclType {
|
||||
func newSimpleType(name string, celType *cel.Type, zeroVal ref.Val) *DeclType {
|
||||
return &DeclType{
|
||||
name: name,
|
||||
exprType: exprType,
|
||||
celType: celType,
|
||||
defaultValue: zeroVal,
|
||||
}
|
||||
}
|
||||
@ -112,7 +89,7 @@ type DeclType struct {
|
||||
Metadata map[string]string
|
||||
MaxElements int64
|
||||
|
||||
exprType *exprpb.Type
|
||||
celType *cel.Type
|
||||
traitMask int
|
||||
defaultValue ref.Val
|
||||
}
|
||||
@ -157,7 +134,7 @@ func (t *DeclType) MaybeAssignTypeName(name string) *DeclType {
|
||||
ElemType: t.ElemType,
|
||||
TypeParam: t.TypeParam,
|
||||
Metadata: t.Metadata,
|
||||
exprType: decls.NewObjectType(name),
|
||||
celType: cel.ObjectType(name),
|
||||
traitMask: t.traitMask,
|
||||
defaultValue: t.defaultValue,
|
||||
}
|
||||
@ -182,8 +159,13 @@ func (t *DeclType) MaybeAssignTypeName(name string) *DeclType {
|
||||
}
|
||||
|
||||
// ExprType returns the CEL expression type of this declaration.
|
||||
func (t *DeclType) ExprType() *exprpb.Type {
|
||||
return t.exprType
|
||||
func (t *DeclType) ExprType() (*exprpb.Type, error) {
|
||||
return cel.TypeToExprType(t.celType)
|
||||
}
|
||||
|
||||
// CelType returns the CEL type of this declaration.
|
||||
func (t *DeclType) CelType() *cel.Type {
|
||||
return t.celType
|
||||
}
|
||||
|
||||
// FindField returns the DeclField with the given name if present.
|
||||
@ -369,7 +351,11 @@ func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
}
|
||||
for name, declType := range rt.ruleSchemaDeclTypes.types {
|
||||
tpType, found := tp.FindType(name)
|
||||
if found && !proto.Equal(tpType, declType.ExprType()) {
|
||||
expT, err := declType.ExprType()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to get cel type: %s", err)
|
||||
}
|
||||
if found && !proto.Equal(tpType, expT) {
|
||||
return nil, fmt.Errorf(
|
||||
"type %s definition differs between CEL environment and rule", name)
|
||||
}
|
||||
@ -377,9 +363,7 @@ func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
return []cel.EnvOption{
|
||||
cel.CustomTypeProvider(rtWithTypes),
|
||||
cel.CustomTypeAdapter(rtWithTypes),
|
||||
cel.Declarations(
|
||||
decls.NewVar("rule", rt.ruleSchemaDeclTypes.root.ExprType()),
|
||||
),
|
||||
cel.Variable("rule", rt.ruleSchemaDeclTypes.root.CelType()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -396,7 +380,11 @@ func (rt *RuleTypes) FindType(typeName string) (*exprpb.Type, bool) {
|
||||
}
|
||||
declType, found := rt.findDeclType(typeName)
|
||||
if found {
|
||||
return declType.ExprType(), found
|
||||
expT, err := declType.ExprType()
|
||||
if err != nil {
|
||||
return expT, false
|
||||
}
|
||||
return expT, found
|
||||
}
|
||||
return rt.TypeProvider.FindType(typeName)
|
||||
}
|
||||
@ -424,15 +412,23 @@ func (rt *RuleTypes) FindFieldType(typeName, fieldName string) (*ref.FieldType,
|
||||
f, found := st.Fields[fieldName]
|
||||
if found {
|
||||
ft := f.Type
|
||||
expT, err := ft.ExprType()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &ref.FieldType{
|
||||
Type: ft.ExprType(),
|
||||
Type: expT,
|
||||
}, true
|
||||
}
|
||||
// This could be a dynamic map.
|
||||
if st.IsMap() {
|
||||
et := st.ElemType
|
||||
expT, err := et.ExprType()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &ref.FieldType{
|
||||
Type: et.ExprType(),
|
||||
Type: expT,
|
||||
}, true
|
||||
}
|
||||
return nil, false
|
||||
@ -517,42 +513,42 @@ type schemaTypeProvider struct {
|
||||
var (
|
||||
// AnyType is equivalent to the CEL 'protobuf.Any' type in that the value may have any of the
|
||||
// types supported.
|
||||
AnyType = newSimpleType("any", decls.Any, nil)
|
||||
AnyType = newSimpleType("any", cel.AnyType, nil)
|
||||
|
||||
// BoolType is equivalent to the CEL 'bool' type.
|
||||
BoolType = newSimpleType("bool", decls.Bool, types.False)
|
||||
BoolType = newSimpleType("bool", cel.BoolType, types.False)
|
||||
|
||||
// BytesType is equivalent to the CEL 'bytes' type.
|
||||
BytesType = newSimpleType("bytes", decls.Bytes, types.Bytes([]byte{}))
|
||||
BytesType = newSimpleType("bytes", cel.BytesType, types.Bytes([]byte{}))
|
||||
|
||||
// DoubleType is equivalent to the CEL 'double' type which is a 64-bit floating point value.
|
||||
DoubleType = newSimpleType("double", decls.Double, types.Double(0))
|
||||
DoubleType = newSimpleType("double", cel.DoubleType, types.Double(0))
|
||||
|
||||
// DurationType is equivalent to the CEL 'duration' type.
|
||||
DurationType = newSimpleType("duration", decls.Duration, types.Duration{Duration: time.Duration(0)})
|
||||
DurationType = newSimpleType("duration", cel.DurationType, types.Duration{Duration: time.Duration(0)})
|
||||
|
||||
// DateType is equivalent to the CEL 'date' type.
|
||||
DateType = newSimpleType("date", decls.Timestamp, types.Timestamp{Time: time.Time{}})
|
||||
DateType = newSimpleType("date", cel.TimestampType, types.Timestamp{Time: time.Time{}})
|
||||
|
||||
// DynType is the equivalent of the CEL 'dyn' concept which indicates that the type will be
|
||||
// determined at runtime rather than compile time.
|
||||
DynType = newSimpleType("dyn", decls.Dyn, nil)
|
||||
DynType = newSimpleType("dyn", cel.DynType, nil)
|
||||
|
||||
// IntType is equivalent to the CEL 'int' type which is a 64-bit signed int.
|
||||
IntType = newSimpleType("int", decls.Int, types.IntZero)
|
||||
IntType = newSimpleType("int", cel.IntType, types.IntZero)
|
||||
|
||||
// NullType is equivalent to the CEL 'null_type'.
|
||||
NullType = newSimpleType("null_type", decls.Null, types.NullValue)
|
||||
NullType = newSimpleType("null_type", cel.NullType, types.NullValue)
|
||||
|
||||
// StringType is equivalent to the CEL 'string' type which is expected to be a UTF-8 string.
|
||||
// StringType values may either be string literals or expression strings.
|
||||
StringType = newSimpleType("string", decls.String, types.String(""))
|
||||
StringType = newSimpleType("string", cel.StringType, types.String(""))
|
||||
|
||||
// TimestampType corresponds to the well-known protobuf.Timestamp type supported within CEL.
|
||||
TimestampType = newSimpleType("timestamp", decls.Timestamp, types.Timestamp{Time: time.Time{}})
|
||||
TimestampType = newSimpleType("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}})
|
||||
|
||||
// UintType is equivalent to the CEL 'uint' type.
|
||||
UintType = newSimpleType("uint", decls.Uint, types.Uint(0))
|
||||
UintType = newSimpleType("uint", cel.UintType, types.Uint(0))
|
||||
|
||||
// ListType is equivalent to the CEL 'list' type.
|
||||
ListType = NewListType(AnyType, noMaxLength)
|
||||
|
@ -37,8 +37,12 @@ func TestTypes_ListType(t *testing.T) {
|
||||
if list.ElemType.TypeName() != "string" {
|
||||
t.Errorf("got %s, wanted elem type of string", list.ElemType.TypeName())
|
||||
}
|
||||
if list.ExprType().GetListType() == nil {
|
||||
t.Errorf("got %v, wanted CEL list type", list.ExprType())
|
||||
expT, err := list.ExprType()
|
||||
if err != nil {
|
||||
t.Errorf("fail to get cel type: %s", err)
|
||||
}
|
||||
if expT.GetListType() == nil {
|
||||
t.Errorf("got %v, wanted CEL list type", expT)
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,8 +63,12 @@ func TestTypes_MapType(t *testing.T) {
|
||||
if mp.ElemType.TypeName() != "int" {
|
||||
t.Errorf("got %s, wanted elem type of int", mp.ElemType.TypeName())
|
||||
}
|
||||
if mp.ExprType().GetMapType() == nil {
|
||||
t.Errorf("got %v, wanted CEL map type", mp.ExprType())
|
||||
expT, err := mp.ExprType()
|
||||
if err != nil {
|
||||
t.Errorf("fail to get cel type: %s", err)
|
||||
}
|
||||
if expT.GetMapType() == nil {
|
||||
t.Errorf("got %v, wanted CEL map type", expT)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
@ -32,6 +33,7 @@ type URL struct {
|
||||
var (
|
||||
URLObject = decls.NewObjectType("kubernetes.URL")
|
||||
typeValue = types.NewTypeValue("kubernetes.URL")
|
||||
URLType = cel.ObjectType("kubernetes.URL")
|
||||
)
|
||||
|
||||
// ConvertToNative implements ref.Val.ConvertToNative.
|
||||
|
112
vendor/github.com/google/cel-go/cel/decls.go
generated
vendored
112
vendor/github.com/google/cel-go/cel/decls.go
generated
vendored
@ -21,6 +21,7 @@ import (
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"github.com/google/cel-go/interpreter/functions"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
@ -162,7 +163,7 @@ type Type struct {
|
||||
|
||||
// isAssignableRuntimeType function determines whether the runtime type (with erasure) is assignable to this type.
|
||||
// A nil value for the isAssignableRuntimeType function falls back to the equality of the type or type name.
|
||||
isAssignableRuntimeType func(other ref.Type) bool
|
||||
isAssignableRuntimeType func(other ref.Val) bool
|
||||
}
|
||||
|
||||
// IsAssignableType determines whether the current type is type-check assignable from the input fromType.
|
||||
@ -177,11 +178,11 @@ func (t *Type) IsAssignableType(fromType *Type) bool {
|
||||
//
|
||||
// At runtime, parameterized types are erased and so a function which type-checks to support a map(string, string)
|
||||
// will have a runtime assignable type of a map.
|
||||
func (t *Type) IsAssignableRuntimeType(runtimeType ref.Type) bool {
|
||||
func (t *Type) IsAssignableRuntimeType(val ref.Val) bool {
|
||||
if t.isAssignableRuntimeType != nil {
|
||||
return t.isAssignableRuntimeType(runtimeType)
|
||||
return t.isAssignableRuntimeType(val)
|
||||
}
|
||||
return t.defaultIsAssignableRuntimeType(runtimeType)
|
||||
return t.defaultIsAssignableRuntimeType(val)
|
||||
}
|
||||
|
||||
// String returns a human-readable definition of the type name.
|
||||
@ -221,7 +222,7 @@ func (t *Type) equals(other *Type) bool {
|
||||
// - The from types are the same instance
|
||||
// - The target type is dynamic
|
||||
// - The fromType has the same kind and type name as the target type, and all parameters of the target type
|
||||
// are IsAssignableType() from the parameters of the fromType.
|
||||
// are IsAssignableType() from the parameters of the fromType.
|
||||
func (t *Type) defaultIsAssignableType(fromType *Type) bool {
|
||||
if t == fromType || t.isDyn() {
|
||||
return true
|
||||
@ -240,8 +241,40 @@ func (t *Type) defaultIsAssignableType(fromType *Type) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *Type) defaultIsAssignableRuntimeType(runtimeType ref.Type) bool {
|
||||
return t.runtimeType == runtimeType || t.isDyn() || t.runtimeType.TypeName() == runtimeType.TypeName()
|
||||
// defaultIsAssignableRuntimeType inspects the type and in the case of list and map elements, the key and element types
|
||||
// to determine whether a ref.Val is assignable to the declared type for a function signature.
|
||||
func (t *Type) defaultIsAssignableRuntimeType(val ref.Val) bool {
|
||||
valType := val.Type()
|
||||
if !(t.runtimeType == valType || t.isDyn() || t.runtimeType.TypeName() == valType.TypeName()) {
|
||||
return false
|
||||
}
|
||||
switch t.runtimeType {
|
||||
case types.ListType:
|
||||
elemType := t.parameters[0]
|
||||
l := val.(traits.Lister)
|
||||
if l.Size() == types.IntZero {
|
||||
return true
|
||||
}
|
||||
it := l.Iterator()
|
||||
for it.HasNext() == types.True {
|
||||
elemVal := it.Next()
|
||||
return elemType.IsAssignableRuntimeType(elemVal)
|
||||
}
|
||||
case types.MapType:
|
||||
keyType := t.parameters[0]
|
||||
elemType := t.parameters[1]
|
||||
m := val.(traits.Mapper)
|
||||
if m.Size() == types.IntZero {
|
||||
return true
|
||||
}
|
||||
it := m.Iterator()
|
||||
for it.HasNext() == types.True {
|
||||
keyVal := it.Next()
|
||||
elemVal := m.Get(keyVal)
|
||||
return keyType.IsAssignableRuntimeType(keyVal) && elemType.IsAssignableRuntimeType(elemVal)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ListType creates an instances of a list type value with the provided element type.
|
||||
@ -273,7 +306,7 @@ func NullableType(wrapped *Type) *Type {
|
||||
isAssignableType: func(other *Type) bool {
|
||||
return NullType.IsAssignableType(other) || wrapped.IsAssignableType(other)
|
||||
},
|
||||
isAssignableRuntimeType: func(other ref.Type) bool {
|
||||
isAssignableRuntimeType: func(other ref.Val) bool {
|
||||
return NullType.IsAssignableRuntimeType(other) || wrapped.IsAssignableRuntimeType(other)
|
||||
},
|
||||
}
|
||||
@ -328,12 +361,26 @@ func Variable(name string, t *Type) EnvOption {
|
||||
// One key difference with using Function() is that each FunctionDecl provided will handle dynamic
|
||||
// dispatch based on the type-signatures of the overloads provided which means overload resolution at
|
||||
// runtime is handled out of the box rather than via a custom binding for overload resolution via
|
||||
// Functions().
|
||||
// Functions():
|
||||
//
|
||||
// - Overloads are searched in the order they are declared
|
||||
// - Dynamic dispatch for lists and maps is limited by inspection of the list and map contents
|
||||
// at runtime. Empty lists and maps will result in a 'default dispatch'
|
||||
// - In the event that a default dispatch occurs, the first overload provided is the one invoked
|
||||
//
|
||||
// If you intend to use overloads which differentiate based on the key or element type of a list or
|
||||
// map, consider using a generic function instead: e.g. func(list(T)) or func(map(K, V)) as this
|
||||
// will allow your implementation to determine how best to handle dispatch and the default behavior
|
||||
// for empty lists and maps whose contents cannot be inspected.
|
||||
//
|
||||
// For functions which use parameterized opaque types (abstract types), consider using a singleton
|
||||
// function which is capable of inspecting the contents of the type and resolving the appropriate
|
||||
// overload as CEL can only make inferences by type-name regarding such types.
|
||||
func Function(name string, opts ...FunctionOpt) EnvOption {
|
||||
return func(e *Env) (*Env, error) {
|
||||
fn := &functionDecl{
|
||||
name: name,
|
||||
overloads: map[string]*overloadDecl{},
|
||||
overloads: []*overloadDecl{},
|
||||
options: opts,
|
||||
}
|
||||
err := fn.init()
|
||||
@ -510,7 +557,7 @@ func OverloadOperandTrait(trait int) OverloadOpt {
|
||||
|
||||
type functionDecl struct {
|
||||
name string
|
||||
overloads map[string]*overloadDecl
|
||||
overloads []*overloadDecl
|
||||
options []FunctionOpt
|
||||
singleton *functions.Overload
|
||||
initialized bool
|
||||
@ -591,22 +638,22 @@ func (f *functionDecl) bindings() ([]*functions.Overload, error) {
|
||||
// performs dynamic dispatch to the proper overload based on the argument types.
|
||||
bindings := append([]*functions.Overload{}, overloads...)
|
||||
funcDispatch := func(args ...ref.Val) ref.Val {
|
||||
for _, overloadDecl := range f.overloads {
|
||||
if !overloadDecl.matchesRuntimeSignature(args...) {
|
||||
for _, o := range f.overloads {
|
||||
if !o.matchesRuntimeSignature(args...) {
|
||||
continue
|
||||
}
|
||||
switch len(args) {
|
||||
case 1:
|
||||
if overloadDecl.unaryOp != nil {
|
||||
return overloadDecl.unaryOp(args[0])
|
||||
if o.unaryOp != nil {
|
||||
return o.unaryOp(args[0])
|
||||
}
|
||||
case 2:
|
||||
if overloadDecl.binaryOp != nil {
|
||||
return overloadDecl.binaryOp(args[0], args[1])
|
||||
if o.binaryOp != nil {
|
||||
return o.binaryOp(args[0], args[1])
|
||||
}
|
||||
}
|
||||
if overloadDecl.functionOp != nil {
|
||||
return overloadDecl.functionOp(args...)
|
||||
if o.functionOp != nil {
|
||||
return o.functionOp(args...)
|
||||
}
|
||||
// eventually this will fall through to the noSuchOverload below.
|
||||
}
|
||||
@ -639,14 +686,12 @@ func (f *functionDecl) merge(other *functionDecl) (*functionDecl, error) {
|
||||
}
|
||||
merged := &functionDecl{
|
||||
name: f.name,
|
||||
overloads: map[string]*overloadDecl{},
|
||||
overloads: make([]*overloadDecl, len(f.overloads)),
|
||||
options: []FunctionOpt{},
|
||||
initialized: true,
|
||||
singleton: f.singleton,
|
||||
}
|
||||
for id, o := range f.overloads {
|
||||
merged.overloads[id] = o
|
||||
}
|
||||
copy(merged.overloads, f.overloads)
|
||||
for _, o := range other.overloads {
|
||||
err := merged.addOverload(o)
|
||||
if err != nil {
|
||||
@ -666,20 +711,21 @@ func (f *functionDecl) merge(other *functionDecl) (*functionDecl, error) {
|
||||
// however, if the function signatures are identical, the implementation may be rewritten as its
|
||||
// difficult to compare functions by object identity.
|
||||
func (f *functionDecl) addOverload(overload *overloadDecl) error {
|
||||
for id, o := range f.overloads {
|
||||
if id != overload.id && o.signatureOverlaps(overload) {
|
||||
for index, o := range f.overloads {
|
||||
if o.id != overload.id && o.signatureOverlaps(overload) {
|
||||
return fmt.Errorf("overload signature collision in function %s: %s collides with %s", f.name, o.id, overload.id)
|
||||
}
|
||||
if id == overload.id {
|
||||
if o.id == overload.id {
|
||||
if o.signatureEquals(overload) && o.nonStrict == overload.nonStrict {
|
||||
// Allow redefinition of an overload implementation so long as the signatures match.
|
||||
f.overloads[id] = overload
|
||||
f.overloads[index] = overload
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("overload redefinition in function. %s: %s has multiple definitions", f.name, o.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
f.overloads[overload.id] = overload
|
||||
f.overloads = append(f.overloads, overload)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -757,19 +803,19 @@ func (o *overloadDecl) matchesRuntimeUnarySignature(arg ref.Val) bool {
|
||||
if o.nonStrict && types.IsUnknownOrError(arg) {
|
||||
return true
|
||||
}
|
||||
return o.argTypes[0].IsAssignableRuntimeType(arg.Type()) && (o.operandTrait == 0 || arg.Type().HasTrait(o.operandTrait))
|
||||
return o.argTypes[0].IsAssignableRuntimeType(arg) && (o.operandTrait == 0 || arg.Type().HasTrait(o.operandTrait))
|
||||
}
|
||||
|
||||
// matchesRuntimeBinarySignature indicates whether the argument types are runtime assiganble to the overload's expected arguments.
|
||||
func (o *overloadDecl) matchesRuntimeBinarySignature(arg1, arg2 ref.Val) bool {
|
||||
if o.nonStrict {
|
||||
if types.IsUnknownOrError(arg1) {
|
||||
return types.IsUnknownOrError(arg2) || o.argTypes[1].IsAssignableRuntimeType(arg2.Type())
|
||||
return types.IsUnknownOrError(arg2) || o.argTypes[1].IsAssignableRuntimeType(arg2)
|
||||
}
|
||||
} else if !o.argTypes[1].IsAssignableRuntimeType(arg2.Type()) {
|
||||
} else if !o.argTypes[1].IsAssignableRuntimeType(arg2) {
|
||||
return false
|
||||
}
|
||||
return o.argTypes[0].IsAssignableRuntimeType(arg1.Type()) && (o.operandTrait == 0 || arg1.Type().HasTrait(o.operandTrait))
|
||||
return o.argTypes[0].IsAssignableRuntimeType(arg1) && (o.operandTrait == 0 || arg1.Type().HasTrait(o.operandTrait))
|
||||
}
|
||||
|
||||
// matchesRuntimeSignature indicates whether the argument types are runtime assiganble to the overload's expected arguments.
|
||||
@ -785,7 +831,7 @@ func (o *overloadDecl) matchesRuntimeSignature(args ...ref.Val) bool {
|
||||
if o.nonStrict && types.IsUnknownOrError(arg) {
|
||||
continue
|
||||
}
|
||||
allArgsMatch = allArgsMatch && o.argTypes[i].IsAssignableRuntimeType(arg.Type())
|
||||
allArgsMatch = allArgsMatch && o.argTypes[i].IsAssignableRuntimeType(arg)
|
||||
}
|
||||
|
||||
arg := args[0]
|
||||
|
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
@ -416,7 +416,7 @@ github.com/google/cadvisor/utils/sysfs
|
||||
github.com/google/cadvisor/utils/sysinfo
|
||||
github.com/google/cadvisor/version
|
||||
github.com/google/cadvisor/watcher
|
||||
# github.com/google/cel-go v0.12.3 => github.com/google/cel-go v0.12.3
|
||||
# github.com/google/cel-go v0.12.4 => github.com/google/cel-go v0.12.4
|
||||
## explicit; go 1.17
|
||||
github.com/google/cel-go/cel
|
||||
github.com/google/cel-go/checker
|
||||
@ -2679,7 +2679,7 @@ sigs.k8s.io/yaml
|
||||
# github.com/golangplus/testing => github.com/golangplus/testing v1.0.0
|
||||
# github.com/google/btree => github.com/google/btree v1.0.1
|
||||
# github.com/google/cadvisor => github.com/google/cadvisor v0.44.1
|
||||
# github.com/google/cel-go => github.com/google/cel-go v0.12.3
|
||||
# github.com/google/cel-go => github.com/google/cel-go v0.12.4
|
||||
# github.com/google/gnostic => github.com/google/gnostic v0.5.7-v3refs
|
||||
# github.com/google/go-cmp => github.com/google/go-cmp v0.5.6
|
||||
# github.com/google/gofuzz => github.com/google/gofuzz v1.1.0
|
||||
|
Loading…
Reference in New Issue
Block a user