mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 21:47:07 +00:00
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>
This commit is contained in:
parent
fa4b8f32ac
commit
a48a92c72e
@ -9012,6 +9012,189 @@ func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
|
|||||||
requireStructuralSchema: true,
|
requireStructuralSchema: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "invalid x-kubernetes-validations for escaping (keywords are invalid field names before v1.30)",
|
||||||
|
input: apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "object",
|
||||||
|
XValidations: apiextensions.ValidationRules{
|
||||||
|
{
|
||||||
|
Rule: "self.__if__ > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.__namespace__ > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.if > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.namespace > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.as > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.break > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.const > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.continue > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.else > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.for > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.function > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.import > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.let > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.loop > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.package > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.return > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.var > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.void > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.while > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.self > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.int > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.true > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.false > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.null > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.in > 0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||||
|
"if": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"namespace": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"as": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"break": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"const": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"continue": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"for": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"function": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"let": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"loop": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"package": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"return": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"var": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"void": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"while": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"self": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"int": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"true": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"false": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"null": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"in": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
opts: validationOptions{
|
||||||
|
requireStructuralSchema: true,
|
||||||
|
celEnvironmentSet: environment.MustBaseEnvSet(version.MajorMinor(1, 30), true),
|
||||||
|
},
|
||||||
|
expectedErrors: []validationMatch{
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[2].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[3].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[4].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[5].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[6].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[7].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[8].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[9].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[10].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[11].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[12].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[13].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[14].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[15].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[16].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[17].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[18].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[21].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[22].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[23].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[24].rule"),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "valid x-kubernetes-validations for escaping",
|
name: "valid x-kubernetes-validations for escaping",
|
||||||
input: apiextensions.CustomResourceValidation{
|
input: apiextensions.CustomResourceValidation{
|
||||||
@ -9024,12 +9207,76 @@ func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Rule: "self.__namespace__ > 0",
|
Rule: "self.__namespace__ > 0",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.if > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.namespace > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.as > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.break > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.const > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.continue > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.else > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.for > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.function > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.import > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.let > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.loop > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.package > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.return > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.var > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.void > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.while > 0",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Rule: "self.self > 0",
|
Rule: "self.self > 0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Rule: "self.int > 0",
|
Rule: "self.int > 0",
|
||||||
},
|
},
|
||||||
|
// reserved keywords `true`, `false`, `null` and `in` are not supported
|
||||||
|
{
|
||||||
|
Rule: "self.true > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.false > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.null > 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rule: "self.in > 0",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||||
"if": {
|
"if": {
|
||||||
@ -9038,49 +9285,97 @@ func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
|
|||||||
"namespace": {
|
"namespace": {
|
||||||
Type: "integer",
|
Type: "integer",
|
||||||
},
|
},
|
||||||
|
"as": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"break": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"const": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"continue": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"for": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"function": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"let": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"loop": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"package": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"return": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"var": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"void": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"while": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
"self": {
|
"self": {
|
||||||
Type: "integer",
|
Type: "integer",
|
||||||
},
|
},
|
||||||
"int": {
|
"int": {
|
||||||
Type: "integer",
|
Type: "integer",
|
||||||
},
|
},
|
||||||
|
"true": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"false": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"null": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
"in": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
opts: validationOptions{
|
opts: validationOptions{
|
||||||
requireStructuralSchema: true,
|
requireStructuralSchema: true,
|
||||||
|
celEnvironmentSet: environment.MustBaseEnvSet(version.MajorMinor(1, 31), true),
|
||||||
|
},
|
||||||
|
expectedErrors: []validationMatch{
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[21].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[22].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[23].rule"),
|
||||||
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[24].rule"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid x-kubernetes-validations for escaping",
|
name: "invalid x-kubernetes-validations for unknown property",
|
||||||
input: apiextensions.CustomResourceValidation{
|
input: apiextensions.CustomResourceValidation{
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
Type: "object",
|
Type: "object",
|
||||||
XValidations: apiextensions.ValidationRules{
|
XValidations: apiextensions.ValidationRules{
|
||||||
{
|
|
||||||
Rule: "self.if > 0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Rule: "self.namespace > 0",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Rule: "self.unknownProp > 0",
|
Rule: "self.unknownProp > 0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"if": {
|
|
||||||
Type: "integer",
|
|
||||||
},
|
|
||||||
"namespace": {
|
|
||||||
Type: "integer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErrors: []validationMatch{
|
expectedErrors: []validationMatch{
|
||||||
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[0].rule"),
|
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[0].rule"),
|
||||||
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[1].rule"),
|
|
||||||
invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[2].rule"),
|
|
||||||
},
|
},
|
||||||
opts: validationOptions{
|
opts: validationOptions{
|
||||||
requireStructuralSchema: true,
|
requireStructuralSchema: true,
|
||||||
|
@ -22,40 +22,38 @@ import (
|
|||||||
"github.com/google/cel-go/cel"
|
"github.com/google/cel-go/cel"
|
||||||
"github.com/google/cel-go/common/types"
|
"github.com/google/cel-go/common/types"
|
||||||
|
|
||||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
|
||||||
|
|
||||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTypes_RuleTypesFieldMapping(t *testing.T) {
|
func TestTypes_RuleTypesFieldMapping(t *testing.T) {
|
||||||
stdEnv, _ := cel.NewEnv()
|
stdEnv, _ := cel.NewEnv()
|
||||||
rt := apiservercel.NewDeclTypeProvider(SchemaDeclType(testSchema(), true).MaybeAssignTypeName("CustomObject"))
|
rt := apiservercel.NewDeclTypeProvider(SchemaDeclType(testSchema(), true).MaybeAssignTypeName("CustomObject"))
|
||||||
nestedFieldType, found := rt.FindFieldType("CustomObject", "nested")
|
nestedFieldType, found := rt.FindStructFieldType("CustomObject", "nested")
|
||||||
if !found {
|
if !found {
|
||||||
t.Fatal("got field not found for 'CustomObject.nested', wanted found")
|
t.Fatal("got field not found for 'CustomObject.nested', wanted found")
|
||||||
}
|
}
|
||||||
if nestedFieldType.Type.GetMessageType() != "CustomObject.nested" {
|
if nestedFieldType.Type.DeclaredTypeName() != "CustomObject.nested" {
|
||||||
t.Errorf("got field type %v, wanted mock_template.nested", nestedFieldType.Type)
|
t.Errorf("got field type %v, wanted mock_template.nested", nestedFieldType.Type)
|
||||||
}
|
}
|
||||||
subnameFieldType, found := rt.FindFieldType("CustomObject.nested", "subname")
|
subnameFieldType, found := rt.FindStructFieldType("CustomObject.nested", "subname")
|
||||||
if !found {
|
if !found {
|
||||||
t.Fatal("got field not found for 'CustomObject.nested.subname', wanted found")
|
t.Fatal("got field not found for 'CustomObject.nested.subname', wanted found")
|
||||||
}
|
}
|
||||||
if subnameFieldType.Type.GetPrimitive() != exprpb.Type_STRING {
|
if subnameFieldType.Type.TypeName() != "string" {
|
||||||
t.Errorf("got field type %v, wanted string", subnameFieldType.Type)
|
t.Errorf("got field type %v, wanted string", subnameFieldType.Type)
|
||||||
}
|
}
|
||||||
flagsFieldType, found := rt.FindFieldType("CustomObject.nested", "flags")
|
flagsFieldType, found := rt.FindStructFieldType("CustomObject.nested", "flags")
|
||||||
if !found {
|
if !found {
|
||||||
t.Fatal("got field not found for 'CustomObject.nested.flags', wanted found")
|
t.Fatal("got field not found for 'CustomObject.nested.flags', wanted found")
|
||||||
}
|
}
|
||||||
if flagsFieldType.Type.GetMapType() == nil {
|
if flagsFieldType.Type.Kind() != types.MapKind {
|
||||||
t.Errorf("got field type %v, wanted map", flagsFieldType.Type)
|
t.Errorf("got field type %v, wanted map", flagsFieldType.Type)
|
||||||
}
|
}
|
||||||
flagFieldType, found := rt.FindFieldType("CustomObject.nested.flags", "my_flag")
|
flagFieldType, found := rt.FindStructFieldType("CustomObject.nested.flags", "my_flag")
|
||||||
if !found {
|
if !found {
|
||||||
t.Fatal("got field not found for 'CustomObject.nested.flags.my_flag', wanted found")
|
t.Fatal("got field not found for 'CustomObject.nested.flags.my_flag', wanted found")
|
||||||
}
|
}
|
||||||
if flagFieldType.Type.GetPrimitive() != exprpb.Type_BOOL {
|
if flagFieldType.Type.Kind() != types.BoolKind {
|
||||||
t.Errorf("got field type %v, wanted bool", flagFieldType.Type)
|
t.Errorf("got field type %v, wanted bool", flagFieldType.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +88,7 @@ func TestTypes_RuleTypesFieldMapping(t *testing.T) {
|
|||||||
// t.Error("got CEL rule value of nil, wanted non-nil")
|
// t.Error("got CEL rule value of nil, wanted non-nil")
|
||||||
//}
|
//}
|
||||||
|
|
||||||
opts, err := rt.EnvOptions(stdEnv.TypeProvider())
|
opts, err := rt.EnvOptions(stdEnv.CELTypeProvider())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -98,7 +96,7 @@ func TestTypes_RuleTypesFieldMapping(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
helloVal := ruleEnv.TypeAdapter().NativeToValue("hello")
|
helloVal := ruleEnv.CELTypeAdapter().NativeToValue("hello")
|
||||||
if helloVal.Equal(types.String("hello")) != types.True {
|
if helloVal.Equal(types.String("hello")) != types.True {
|
||||||
t.Errorf("got %v, wanted types.String('hello')", helloVal)
|
t.Errorf("got %v, wanted types.String('hello')", helloVal)
|
||||||
}
|
}
|
||||||
|
@ -899,6 +899,48 @@ func TestValidationExpressions(t *testing.T) {
|
|||||||
"has(self.embedded.metadata.namespace)": "undefined field 'namespace'",
|
"has(self.embedded.metadata.namespace)": "undefined field 'namespace'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{name: "embedded object with usage of reserved keywords",
|
||||||
|
obj: map[string]interface{}{
|
||||||
|
"embedded": map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Pod",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "foo",
|
||||||
|
"generateName": "pickItForMe",
|
||||||
|
"namespace": "reserved_keyword_namespace",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"if": "reserved_keyword_if",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema: objectTypePtr(map[string]schema.Structural{
|
||||||
|
"embedded": {
|
||||||
|
Generic: schema.Generic{Type: "object"},
|
||||||
|
Extensions: schema.Extensions{
|
||||||
|
XEmbeddedResource: true,
|
||||||
|
},
|
||||||
|
Properties: map[string]schema.Structural{
|
||||||
|
"kind": stringType,
|
||||||
|
"apiVersion": stringType,
|
||||||
|
"metadata": objectType(map[string]schema.Structural{
|
||||||
|
"name": stringType,
|
||||||
|
"generateName": stringType,
|
||||||
|
"namespace": stringType,
|
||||||
|
}),
|
||||||
|
"spec": objectType(map[string]schema.Structural{
|
||||||
|
"if": stringType,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
valid: []string{
|
||||||
|
"has(self.embedded.metadata.namespace)",
|
||||||
|
"self.embedded.metadata.namespace == 'reserved_keyword_namespace'",
|
||||||
|
"has(self.embedded.spec.if)",
|
||||||
|
"self.embedded.spec.if == 'reserved_keyword_if'",
|
||||||
|
},
|
||||||
|
},
|
||||||
{name: "embedded object with preserve unknown",
|
{name: "embedded object with preserve unknown",
|
||||||
obj: map[string]interface{}{
|
obj: map[string]interface{}{
|
||||||
"embedded": map[string]interface{}{
|
"embedded": map[string]interface{}{
|
||||||
|
@ -267,7 +267,10 @@ func (e *EnvSet) filterAndBuildOpts(base *cel.Env, compatVer *version.Version, h
|
|||||||
|
|
||||||
if len(declTypes) > 0 {
|
if len(declTypes) > 0 {
|
||||||
provider := apiservercel.NewDeclTypeProvider(declTypes...)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
typeVersionCombinations []envTypeAndVersion
|
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 {
|
for _, tc := range cases {
|
||||||
|
@ -27,7 +27,6 @@ import (
|
|||||||
"github.com/google/cel-go/common/types/traits"
|
"github.com/google/cel-go/common/types/traits"
|
||||||
|
|
||||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"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
|
// DeclTypeProvider extends the CEL ref.TypeProvider interface and provides an Open API Schema-based
|
||||||
// type-system.
|
// type-system.
|
||||||
type DeclTypeProvider struct {
|
type DeclTypeProvider struct {
|
||||||
registeredTypes map[string]*DeclType
|
registeredTypes map[string]*DeclType
|
||||||
typeProvider ref.TypeProvider
|
typeProvider types.Provider
|
||||||
typeAdapter ref.TypeAdapter
|
typeAdapter types.Adapter
|
||||||
|
recognizeKeywordAsFieldName bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *DeclTypeProvider) SetRecognizeKeywordAsFieldName(recognize bool) {
|
||||||
|
rt.recognizeKeywordAsFieldName = recognize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *DeclTypeProvider) EnumValue(enumName string) ref.Val {
|
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.
|
// as well as a custom ref.TypeProvider.
|
||||||
//
|
//
|
||||||
// If the DeclTypeProvider value is nil, an empty []cel.EnvOption set is returned.
|
// 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 {
|
if rt == nil {
|
||||||
return []cel.EnvOption{}, 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
|
// WithTypeProvider returns a new DeclTypeProvider that sets the given TypeProvider
|
||||||
// If the original DeclTypeProvider is nil, the returned DeclTypeProvider is still nil.
|
// 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 {
|
if rt == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var ta ref.TypeAdapter = types.DefaultTypeAdapter
|
var ta types.Adapter = types.DefaultTypeAdapter
|
||||||
tpa, ok := tp.(ref.TypeAdapter)
|
tpa, ok := tp.(types.Adapter)
|
||||||
if ok {
|
if ok {
|
||||||
ta = tpa
|
ta = tpa
|
||||||
}
|
}
|
||||||
rtWithTypes := &DeclTypeProvider{
|
rtWithTypes := &DeclTypeProvider{
|
||||||
typeProvider: tp,
|
typeProvider: tp,
|
||||||
typeAdapter: ta,
|
typeAdapter: ta,
|
||||||
registeredTypes: rt.registeredTypes,
|
registeredTypes: rt.registeredTypes,
|
||||||
|
recognizeKeywordAsFieldName: rt.recognizeKeywordAsFieldName,
|
||||||
}
|
}
|
||||||
for name, declType := range rt.registeredTypes {
|
for name, declType := range rt.registeredTypes {
|
||||||
tpType, found := tp.FindType(name)
|
tpType, found := tp.FindStructType(name)
|
||||||
expT, err := declType.ExprType()
|
// cast celType to types.type
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("fail to get cel type: %s", err)
|
expT := declType.CelType()
|
||||||
}
|
if found && !expT.IsExactType(tpType) {
|
||||||
if found && !proto.Equal(tpType, expT) {
|
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"type %s definition differs between CEL environment and type provider", name)
|
"type %s definition differs between CEL environment and type provider", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return rtWithTypes, nil
|
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.
|
// 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
|
// Note, when the type name is based on the Open API Schema, the name will reflect the object path
|
||||||
// where the type definition appears.
|
// 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 {
|
if rt == nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
declType, found := rt.findDeclType(typeName)
|
declType, found := rt.findDeclType(typeName)
|
||||||
if found {
|
if found {
|
||||||
expT, err := declType.ExprType()
|
expT := declType.CelType()
|
||||||
if err != nil {
|
|
||||||
return expT, false
|
|
||||||
}
|
|
||||||
return expT, found
|
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.
|
// 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)
|
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.
|
// 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
|
// 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
|
// resolution might more accurately reflect the expected type model. However, in this case
|
||||||
// concessions were made to align with the existing CEL interfaces.
|
// 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)
|
st, found := rt.findDeclType(typeName)
|
||||||
if !found {
|
if !found {
|
||||||
return rt.typeProvider.FindFieldType(typeName, fieldName)
|
return rt.typeProvider.FindStructFieldType(typeName, fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, found := st.Fields[fieldName]
|
f, found := st.Fields[fieldName]
|
||||||
|
if rt.recognizeKeywordAsFieldName && !found && celReservedSymbols.Has(fieldName) {
|
||||||
|
f, found = st.Fields["__"+fieldName+"__"]
|
||||||
|
}
|
||||||
|
|
||||||
if found {
|
if found {
|
||||||
ft := f.Type
|
ft := f.Type
|
||||||
expT, err := ft.ExprType()
|
expT := ft.CelType()
|
||||||
if err != nil {
|
return &types.FieldType{
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return &ref.FieldType{
|
|
||||||
Type: expT,
|
Type: expT,
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
// This could be a dynamic map.
|
// This could be a dynamic map.
|
||||||
if st.IsMap() {
|
if st.IsMap() {
|
||||||
et := st.ElemType
|
et := st.ElemType
|
||||||
expT, err := et.ExprType()
|
expT := et.CelType()
|
||||||
if err != nil {
|
return &types.FieldType{
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return &ref.FieldType{
|
|
||||||
Type: expT,
|
Type: expT,
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
|
@ -382,7 +382,7 @@ func simpleCompileCEL(schema *spec.Schema, expression string) (cel.Program, erro
|
|||||||
}
|
}
|
||||||
declType := celopenapi.SchemaDeclType(schema, true).MaybeAssignTypeName("selfType")
|
declType := celopenapi.SchemaDeclType(schema, true).MaybeAssignTypeName("selfType")
|
||||||
rt := commoncel.NewDeclTypeProvider(declType)
|
rt := commoncel.NewDeclTypeProvider(declType)
|
||||||
opts, err := rt.EnvOptions(env.TypeProvider())
|
opts, err := rt.EnvOptions(env.CELTypeProvider())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user