Allowing direct CEL reserved keyword usage in CRD (#126188)
* automatically escape reserved keywords for direct usage * Add reserved keyword support in a ratcheting way, add tests. --------- Co-authored-by: Wenxue Zhao <ballista01@outlook.com> Kubernetes-commit: a48a92c72ec7d4e2a8da396309abff9360faae75
This commit is contained in:
parent
c90207143c
commit
92ee9330ce
6
go.mod
6
go.mod
|
@ -44,9 +44,9 @@ require (
|
|||
gopkg.in/evanphx/json-patch.v4 v4.12.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
k8s.io/api v0.0.0-20240723194852-871340c2e998
|
||||
k8s.io/api v0.0.0-20240724010313-f04ea0bc861d
|
||||
k8s.io/apimachinery v0.0.0-20240720202316-95b78024e3fe
|
||||
k8s.io/client-go v0.0.0-20240723200359-dcfcc90795cc
|
||||
k8s.io/client-go v0.0.0-20240724010704-ac9204c6195b
|
||||
k8s.io/component-base v0.0.0-20240722183709-6cc953a9d440
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/kms v0.0.0-20240707024556-6e3528fa4c33
|
||||
|
@ -123,3 +123,5 @@ require (
|
|||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace k8s.io/api => k8s.io/api v0.0.0-20240724010313-a789efa287e8
|
||||
|
|
8
go.sum
8
go.sum
|
@ -370,12 +370,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.0.0-20240723194852-871340c2e998 h1:XvMrEqepRsNkn8Bl60PB5TO4ZEOgr70bYrpAedjvTV8=
|
||||
k8s.io/api v0.0.0-20240723194852-871340c2e998/go.mod h1:ytlEzqC2wOTwYET71W7+J+k7O2V7vrDuzmNLBSpgT+k=
|
||||
k8s.io/api v0.0.0-20240724010313-a789efa287e8 h1:TISAHWnfAdn420WpN+fEHG6snbLbfaCAp3kHDoAkxIc=
|
||||
k8s.io/api v0.0.0-20240724010313-a789efa287e8/go.mod h1:ytlEzqC2wOTwYET71W7+J+k7O2V7vrDuzmNLBSpgT+k=
|
||||
k8s.io/apimachinery v0.0.0-20240720202316-95b78024e3fe h1:V9MwpYUwbKlfLKVrhpVuKWiat/LBIhm1pGB9/xdHm5Q=
|
||||
k8s.io/apimachinery v0.0.0-20240720202316-95b78024e3fe/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
k8s.io/client-go v0.0.0-20240723200359-dcfcc90795cc h1:qe0SREEjfE5w3ANvrSURWv00J/ISlqa9Sa3FCBYKRlg=
|
||||
k8s.io/client-go v0.0.0-20240723200359-dcfcc90795cc/go.mod h1:XfEsPNNFOR0wNkr3BtkPUN668l7Sx1W4ECSUolQ0mA4=
|
||||
k8s.io/client-go v0.0.0-20240724010704-ac9204c6195b h1:NTLYx38CAu+VstHvPLosqB6uSQUtSM+3Mqz2D/C5JpE=
|
||||
k8s.io/client-go v0.0.0-20240724010704-ac9204c6195b/go.mod h1:Y6CzOT21oLI4O66cjiV5oSSUgOL7gG/VCG9n8XI8OxU=
|
||||
k8s.io/component-base v0.0.0-20240722183709-6cc953a9d440 h1:14X+5sRQRsul6tLxIKTP0/DotvWlMd9DFCgMqHP1hZY=
|
||||
k8s.io/component-base v0.0.0-20240722183709-6cc953a9d440/go.mod h1:dj2Pl05aLcVMZi2NXcwv+M/WdUVPEkisFPjDze7rbSk=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
|
|
|
@ -267,7 +267,10 @@ func (e *EnvSet) filterAndBuildOpts(base *cel.Env, compatVer *version.Version, h
|
|||
|
||||
if len(declTypes) > 0 {
|
||||
provider := apiservercel.NewDeclTypeProvider(declTypes...)
|
||||
providerOpts, err := provider.EnvOptions(base.TypeProvider())
|
||||
if compatVer.AtLeast(version.MajorMinor(1, 31)) {
|
||||
provider.SetRecognizeKeywordAsFieldName(true)
|
||||
}
|
||||
providerOpts, err := provider.EnvOptions(base.CELTypeProvider())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -43,6 +43,15 @@ func TestBaseEnvironment(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
// The escaping happens while construct declType hence we use escaped format here directly.
|
||||
gadgetsType := apiservercel.NewObjectType("Gadget",
|
||||
map[string]*apiservercel.DeclField{
|
||||
"__namespace__": {
|
||||
Name: "__namespace__",
|
||||
Type: apiservercel.StringType,
|
||||
},
|
||||
})
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
typeVersionCombinations []envTypeAndVersion
|
||||
|
@ -251,6 +260,42 @@ func TestBaseEnvironment(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "recognizeKeywordAsFieldName disabled",
|
||||
typeVersionCombinations: []envTypeAndVersion{
|
||||
{version.MajorMinor(1, 30), NewExpressions},
|
||||
// always enabled for StoredExpressions
|
||||
},
|
||||
invalidExpressions: []string{"gadget.namespace == 'buzz'"},
|
||||
activation: map[string]any{"gadget": map[string]any{"namespace": "buzz"}},
|
||||
opts: []VersionedOptions{
|
||||
{
|
||||
IntroducedVersion: version.MajorMinor(1, 28),
|
||||
DeclTypes: []*apiservercel.DeclType{gadgetsType},
|
||||
EnvOptions: []cel.EnvOption{
|
||||
cel.Variable("gadget", cel.ObjectType("Gadget")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "recognizeKeywordAsFieldName enabled",
|
||||
typeVersionCombinations: []envTypeAndVersion{
|
||||
{version.MajorMinor(1, 31), NewExpressions},
|
||||
{version.MajorMinor(1, 30), StoredExpressions},
|
||||
},
|
||||
validExpressions: []string{"gadget.namespace == 'buzz'"},
|
||||
activation: map[string]any{"gadget": map[string]any{"namespace": "buzz"}},
|
||||
opts: []VersionedOptions{
|
||||
{
|
||||
IntroducedVersion: version.MajorMinor(1, 28),
|
||||
DeclTypes: []*apiservercel.DeclType{gadgetsType},
|
||||
EnvOptions: []cel.EnvOption{
|
||||
cel.Variable("gadget", cel.ObjectType("Gadget")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
|
|
@ -27,7 +27,6 @@ import (
|
|||
"github.com/google/cel-go/common/types/traits"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
|
@ -349,9 +348,14 @@ func NewDeclTypeProvider(rootTypes ...*DeclType) *DeclTypeProvider {
|
|||
// DeclTypeProvider extends the CEL ref.TypeProvider interface and provides an Open API Schema-based
|
||||
// type-system.
|
||||
type DeclTypeProvider struct {
|
||||
registeredTypes map[string]*DeclType
|
||||
typeProvider ref.TypeProvider
|
||||
typeAdapter ref.TypeAdapter
|
||||
registeredTypes map[string]*DeclType
|
||||
typeProvider types.Provider
|
||||
typeAdapter types.Adapter
|
||||
recognizeKeywordAsFieldName bool
|
||||
}
|
||||
|
||||
func (rt *DeclTypeProvider) SetRecognizeKeywordAsFieldName(recognize bool) {
|
||||
rt.recognizeKeywordAsFieldName = recognize
|
||||
}
|
||||
|
||||
func (rt *DeclTypeProvider) EnumValue(enumName string) ref.Val {
|
||||
|
@ -366,7 +370,7 @@ func (rt *DeclTypeProvider) FindIdent(identName string) (ref.Val, bool) {
|
|||
// as well as a custom ref.TypeProvider.
|
||||
//
|
||||
// If the DeclTypeProvider value is nil, an empty []cel.EnvOption set is returned.
|
||||
func (rt *DeclTypeProvider) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
func (rt *DeclTypeProvider) EnvOptions(tp types.Provider) ([]cel.EnvOption, error) {
|
||||
if rt == nil {
|
||||
return []cel.EnvOption{}, nil
|
||||
}
|
||||
|
@ -382,54 +386,52 @@ func (rt *DeclTypeProvider) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, er
|
|||
|
||||
// WithTypeProvider returns a new DeclTypeProvider that sets the given TypeProvider
|
||||
// If the original DeclTypeProvider is nil, the returned DeclTypeProvider is still nil.
|
||||
func (rt *DeclTypeProvider) WithTypeProvider(tp ref.TypeProvider) (*DeclTypeProvider, error) {
|
||||
func (rt *DeclTypeProvider) WithTypeProvider(tp types.Provider) (*DeclTypeProvider, error) {
|
||||
if rt == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var ta ref.TypeAdapter = types.DefaultTypeAdapter
|
||||
tpa, ok := tp.(ref.TypeAdapter)
|
||||
var ta types.Adapter = types.DefaultTypeAdapter
|
||||
tpa, ok := tp.(types.Adapter)
|
||||
if ok {
|
||||
ta = tpa
|
||||
}
|
||||
rtWithTypes := &DeclTypeProvider{
|
||||
typeProvider: tp,
|
||||
typeAdapter: ta,
|
||||
registeredTypes: rt.registeredTypes,
|
||||
typeProvider: tp,
|
||||
typeAdapter: ta,
|
||||
registeredTypes: rt.registeredTypes,
|
||||
recognizeKeywordAsFieldName: rt.recognizeKeywordAsFieldName,
|
||||
}
|
||||
for name, declType := range rt.registeredTypes {
|
||||
tpType, found := tp.FindType(name)
|
||||
expT, err := declType.ExprType()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to get cel type: %s", err)
|
||||
}
|
||||
if found && !proto.Equal(tpType, expT) {
|
||||
tpType, found := tp.FindStructType(name)
|
||||
// cast celType to types.type
|
||||
|
||||
expT := declType.CelType()
|
||||
if found && !expT.IsExactType(tpType) {
|
||||
return nil, fmt.Errorf(
|
||||
"type %s definition differs between CEL environment and type provider", name)
|
||||
}
|
||||
|
||||
}
|
||||
return rtWithTypes, nil
|
||||
}
|
||||
|
||||
// FindType attempts to resolve the typeName provided from the rule's rule-schema, or if not
|
||||
// FindStructType attempts to resolve the typeName provided from the rule's rule-schema, or if not
|
||||
// from the embedded ref.TypeProvider.
|
||||
//
|
||||
// FindType overrides the default type-finding behavior of the embedded TypeProvider.
|
||||
// FindStructType overrides the default type-finding behavior of the embedded TypeProvider.
|
||||
//
|
||||
// Note, when the type name is based on the Open API Schema, the name will reflect the object path
|
||||
// where the type definition appears.
|
||||
func (rt *DeclTypeProvider) FindType(typeName string) (*exprpb.Type, bool) {
|
||||
func (rt *DeclTypeProvider) FindStructType(typeName string) (*types.Type, bool) {
|
||||
if rt == nil {
|
||||
return nil, false
|
||||
}
|
||||
declType, found := rt.findDeclType(typeName)
|
||||
if found {
|
||||
expT, err := declType.ExprType()
|
||||
if err != nil {
|
||||
return expT, false
|
||||
}
|
||||
expT := declType.CelType()
|
||||
return expT, found
|
||||
}
|
||||
return rt.typeProvider.FindType(typeName)
|
||||
return rt.typeProvider.FindStructType(typeName)
|
||||
}
|
||||
|
||||
// FindDeclType returns the CPT type description which can be mapped to a CEL type.
|
||||
|
@ -440,37 +442,41 @@ func (rt *DeclTypeProvider) FindDeclType(typeName string) (*DeclType, bool) {
|
|||
return rt.findDeclType(typeName)
|
||||
}
|
||||
|
||||
// FindFieldType returns a field type given a type name and field name, if found.
|
||||
// FindStructFieldNames returns the field names associated with the type, if the type
|
||||
// is found.
|
||||
func (rt *DeclTypeProvider) FindStructFieldNames(typeName string) ([]string, bool) {
|
||||
return []string{}, false
|
||||
}
|
||||
|
||||
// FindStructFieldType returns a field type given a type name and field name, if found.
|
||||
//
|
||||
// Note, the type name for an Open API Schema type is likely to be its qualified object path.
|
||||
// If, in the future an object instance rather than a type name were provided, the field
|
||||
// resolution might more accurately reflect the expected type model. However, in this case
|
||||
// concessions were made to align with the existing CEL interfaces.
|
||||
func (rt *DeclTypeProvider) FindFieldType(typeName, fieldName string) (*ref.FieldType, bool) {
|
||||
func (rt *DeclTypeProvider) FindStructFieldType(typeName, fieldName string) (*types.FieldType, bool) {
|
||||
st, found := rt.findDeclType(typeName)
|
||||
if !found {
|
||||
return rt.typeProvider.FindFieldType(typeName, fieldName)
|
||||
return rt.typeProvider.FindStructFieldType(typeName, fieldName)
|
||||
}
|
||||
|
||||
f, found := st.Fields[fieldName]
|
||||
if rt.recognizeKeywordAsFieldName && !found && celReservedSymbols.Has(fieldName) {
|
||||
f, found = st.Fields["__"+fieldName+"__"]
|
||||
}
|
||||
|
||||
if found {
|
||||
ft := f.Type
|
||||
expT, err := ft.ExprType()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &ref.FieldType{
|
||||
expT := ft.CelType()
|
||||
return &types.FieldType{
|
||||
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{
|
||||
expT := et.CelType()
|
||||
return &types.FieldType{
|
||||
Type: expT,
|
||||
}, true
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue