mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 10:20:51 +00:00
composited type systems for CEL.
This commit is contained in:
parent
0270fc75d0
commit
9633cb8d7e
119
staging/src/k8s.io/apiserver/pkg/cel/composited.go
Normal file
119
staging/src/k8s.io/apiserver/pkg/cel/composited.go
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cel
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
var _ ref.TypeProvider = (*CompositedTypeProvider)(nil)
|
||||
var _ ref.TypeAdapter = (*CompositedTypeAdapter)(nil)
|
||||
|
||||
// CompositedTypeProvider is the provider that tries each of the underlying
|
||||
// providers in order, and returns result of the first successful attempt.
|
||||
type CompositedTypeProvider struct {
|
||||
// Providers contains the underlying type providers.
|
||||
// If Providers is empty, the CompositedTypeProvider becomes no-op provider.
|
||||
Providers []ref.TypeProvider
|
||||
}
|
||||
|
||||
// EnumValue finds out the numeric value of the given enum name.
|
||||
// The result comes from first provider that returns non-nil.
|
||||
func (c *CompositedTypeProvider) EnumValue(enumName string) ref.Val {
|
||||
for _, p := range c.Providers {
|
||||
val := p.EnumValue(enumName)
|
||||
if val != nil {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindIdent takes a qualified identifier name and returns a Value if one
|
||||
// exists. The result comes from first provider that returns non-nil.
|
||||
func (c *CompositedTypeProvider) FindIdent(identName string) (ref.Val, bool) {
|
||||
for _, p := range c.Providers {
|
||||
val, ok := p.FindIdent(identName)
|
||||
if ok {
|
||||
return val, ok
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// FindType finds the Type given a qualified type name, or return false
|
||||
// if none of the providers finds the type.
|
||||
// If any of the providers find the type, the first provider that returns true
|
||||
// will be the result.
|
||||
func (c *CompositedTypeProvider) FindType(typeName string) (*exprpb.Type, bool) {
|
||||
for _, p := range c.Providers {
|
||||
typ, ok := p.FindType(typeName)
|
||||
if ok {
|
||||
return typ, ok
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// FindFieldType returns the field type for a checked type value. Returns
|
||||
// false if none of the providers can find the type.
|
||||
// If multiple providers can find the field, the result is taken from
|
||||
// the first that does.
|
||||
func (c *CompositedTypeProvider) FindFieldType(messageType string, fieldName string) (*ref.FieldType, bool) {
|
||||
for _, p := range c.Providers {
|
||||
ft, ok := p.FindFieldType(messageType, fieldName)
|
||||
if ok {
|
||||
return ft, ok
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// NewValue creates a new type value from a qualified name and map of field
|
||||
// name to value.
|
||||
// If multiple providers can create the new type, the first that returns
|
||||
// non-nil will decide the result.
|
||||
func (c *CompositedTypeProvider) NewValue(typeName string, fields map[string]ref.Val) ref.Val {
|
||||
for _, p := range c.Providers {
|
||||
v := p.NewValue(typeName, fields)
|
||||
if v != nil {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompositedTypeAdapter is the adapter that tries each of the underlying
|
||||
// type adapter in order until the first successfully conversion.
|
||||
type CompositedTypeAdapter struct {
|
||||
// Adapters contains underlying type adapters.
|
||||
// If Adapters is empty, the CompositedTypeAdapter becomes a no-op adapter.
|
||||
Adapters []ref.TypeAdapter
|
||||
}
|
||||
|
||||
// NativeToValue takes the value and convert it into a ref.Val
|
||||
// The result comes from the first TypeAdapter that returns non-nil.
|
||||
func (c *CompositedTypeAdapter) NativeToValue(value interface{}) ref.Val {
|
||||
for _, a := range c.Adapters {
|
||||
v := a.NativeToValue(value)
|
||||
if v != nil {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
176
staging/src/k8s.io/apiserver/pkg/cel/openapi/compiling_test.go
Normal file
176
staging/src/k8s.io/apiserver/pkg/cel/openapi/compiling_test.go
Normal file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/common"
|
||||
"k8s.io/apiserver/pkg/cel/library"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
)
|
||||
|
||||
func TestMultipleTypes(t *testing.T) {
|
||||
env, err := buildTestEnv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
expression string
|
||||
expectCompileError bool
|
||||
expectEvalResult bool
|
||||
}{
|
||||
{
|
||||
expression: "foo.foo == bar.bar",
|
||||
expectEvalResult: true,
|
||||
},
|
||||
{
|
||||
expression: "foo.bar == 'value'",
|
||||
expectCompileError: true,
|
||||
},
|
||||
{
|
||||
expression: "foo.foo == 'value'",
|
||||
expectEvalResult: true,
|
||||
},
|
||||
{
|
||||
expression: "bar.bar == 'value'",
|
||||
expectEvalResult: true,
|
||||
},
|
||||
{
|
||||
expression: "foo.common + bar.common <= 2",
|
||||
expectEvalResult: false, // 3 > 2
|
||||
},
|
||||
{
|
||||
expression: "foo.confusion == bar.confusion",
|
||||
expectCompileError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.expression, func(t *testing.T) {
|
||||
ast, issues := env.Compile(tc.expression)
|
||||
if issues != nil {
|
||||
if tc.expectCompileError {
|
||||
return
|
||||
}
|
||||
t.Fatalf("compile error: %v", issues)
|
||||
}
|
||||
if issues != nil {
|
||||
t.Fatal(issues)
|
||||
}
|
||||
p, err := env.Program(ast)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ret, _, err := p.Eval(&simpleActivation{
|
||||
foo: map[string]any{"foo": "value", "common": 1, "confusion": "114514"},
|
||||
bar: map[string]any{"bar": "value", "common": 2, "confusion": 114514},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ret.Type() != types.BoolType {
|
||||
t.Errorf("bad result type: %v", ret.Type())
|
||||
}
|
||||
if res := ret.Value().(bool); tc.expectEvalResult != res {
|
||||
t.Errorf("expectEvalResult expression evaluates to %v, got %v", tc.expectEvalResult, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// buildTestEnv sets up an environment that contains two variables, "foo" and
|
||||
// "bar".
|
||||
// foo is an object with a string field "foo", an integer field "common", and a string field "confusion"
|
||||
// bar is an object with a string field "bar", an integer field "common", and an integer field "confusion"
|
||||
func buildTestEnv() (*cel.Env, error) {
|
||||
var opts []cel.EnvOption
|
||||
opts = append(opts, cel.HomogeneousAggregateLiterals())
|
||||
opts = append(opts, cel.EagerlyValidateDeclarations(true), cel.DefaultUTCTimeZone(true))
|
||||
opts = append(opts, library.ExtensionLibs...)
|
||||
env, err := cel.NewEnv(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reg := apiservercel.NewRegistry(env)
|
||||
|
||||
declType := common.SchemaDeclType(simpleMapSchema("foo", spec.StringProperty()), true)
|
||||
fooRT, err := apiservercel.NewRuleTypes("fooType", declType, reg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fooRT, err = fooRT.WithTypeProvider(env.TypeProvider())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fooType, _ := fooRT.FindDeclType("fooType")
|
||||
|
||||
declType = common.SchemaDeclType(simpleMapSchema("bar", spec.Int64Property()), true)
|
||||
barRT, err := apiservercel.NewRuleTypes("barType", declType, reg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
barRT, err = barRT.WithTypeProvider(env.TypeProvider())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
barType, _ := barRT.FindDeclType("barType")
|
||||
|
||||
opts = append(opts, cel.CustomTypeProvider(&apiservercel.CompositedTypeProvider{Providers: []ref.TypeProvider{fooRT, barRT}}))
|
||||
opts = append(opts, cel.CustomTypeAdapter(&apiservercel.CompositedTypeAdapter{Adapters: []ref.TypeAdapter{fooRT, barRT}}))
|
||||
opts = append(opts, cel.Variable("foo", fooType.CelType()))
|
||||
opts = append(opts, cel.Variable("bar", barType.CelType()))
|
||||
return env.Extend(opts...)
|
||||
}
|
||||
|
||||
func simpleMapSchema(fieldName string, confusionSchema *spec.Schema) common.Schema {
|
||||
return &Schema{Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
fieldName: *spec.StringProperty(),
|
||||
"common": *spec.Int64Property(),
|
||||
"confusion": *confusionSchema,
|
||||
},
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
type simpleActivation struct {
|
||||
foo any
|
||||
bar any
|
||||
}
|
||||
|
||||
func (a *simpleActivation) ResolveName(name string) (interface{}, bool) {
|
||||
switch name {
|
||||
case "foo":
|
||||
return a.foo, true
|
||||
case "bar":
|
||||
return a.bar, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func (a *simpleActivation) Parent() interpreter.Activation {
|
||||
return nil
|
||||
}
|
@ -360,6 +360,23 @@ func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
if rt == nil {
|
||||
return []cel.EnvOption{}, nil
|
||||
}
|
||||
rtWithTypes, err := rt.WithTypeProvider(tp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []cel.EnvOption{
|
||||
cel.CustomTypeProvider(rtWithTypes),
|
||||
cel.CustomTypeAdapter(rtWithTypes),
|
||||
cel.Variable("rule", rt.ruleSchemaDeclTypes.root.CelType()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithTypeProvider returns a new RuleTypes that sets the given TypeProvider
|
||||
// If the original RuleTypes is nil, the returned RuleTypes is still nil.
|
||||
func (rt *RuleTypes) WithTypeProvider(tp ref.TypeProvider) (*RuleTypes, error) {
|
||||
if rt == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var ta ref.TypeAdapter = types.DefaultTypeAdapter
|
||||
tpa, ok := tp.(ref.TypeAdapter)
|
||||
if ok {
|
||||
@ -382,11 +399,7 @@ func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
"type %s definition differs between CEL environment and rule", name)
|
||||
}
|
||||
}
|
||||
return []cel.EnvOption{
|
||||
cel.CustomTypeProvider(rtWithTypes),
|
||||
cel.CustomTypeAdapter(rtWithTypes),
|
||||
cel.Variable("rule", rt.ruleSchemaDeclTypes.root.CelType()),
|
||||
}, nil
|
||||
return rtWithTypes, nil
|
||||
}
|
||||
|
||||
// FindType attempts to resolve the typeName provided from the rule's rule-schema, or if not
|
||||
|
Loading…
Reference in New Issue
Block a user