diff --git a/WORKSPACE b/WORKSPACE index b52b8319a..f566d7d09 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -101,8 +101,8 @@ go_repository( go_repository( name = "dev_cel_expr", importpath = "cel.dev/expr", - sum = "h1:o+Wj235dy4gFYlYin3JsMpp3EEfMrPm/6tdoyjT98S0=", - version = "v0.21.2", + sum = "h1:xoFEsNh972Yzey8N9TCPx2nDvMN7TMhQEzxLuj/iRrI=", + version = "v0.22.1", ) # local_repository( diff --git a/common/env/env.go b/common/env/env.go index 07294c696..8e57c42e3 100644 --- a/common/env/env.go +++ b/common/env/env.go @@ -836,7 +836,7 @@ func (td *TypeDesc) AsCELType(tp types.Provider) (*types.Type, error) { } } -// SerializeTypeDesc converts *types.Type to a serialized format TypeDesc +// SerializeTypeDesc converts a CEL native *types.Type to a serializable TypeDesc. func SerializeTypeDesc(t *types.Type) *TypeDesc { typeName := t.TypeName() if t.Kind() == types.TypeParamKind { diff --git a/ext/extension_option_factory_test.go b/ext/extension_option_factory_test.go index f721bb6bf..573155baa 100644 --- a/ext/extension_option_factory_test.go +++ b/ext/extension_option_factory_test.go @@ -61,6 +61,9 @@ func TestExtensionOptionFactoryValidBindingsExtension(t *testing.T) { t.Fatalf("ExtensionOptionFactory(%s) returned invalid extension", e.Name) } cfg, err := en.ToConfig("test config") + if err != nil { + t.Fatalf("ToConfig(%s) returned error: %v", e.Name, err) + } if len(cfg.Extensions) != 1 || cfg.Extensions[0].Name != "cel.lib.ext.cel.bindings" || cfg.Extensions[0].Version != "latest" { t.Fatalf("ExtensionOptionFactory(%s) returned invalid extension", e.Name) } diff --git a/go.mod b/go.mod index 8bf321c5a..4108e1724 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21.1 toolchain go1.23.0 require ( - cel.dev/expr v0.21.2 + cel.dev/expr v0.22.1 github.com/antlr4-go/antlr/v4 v4.13.0 github.com/stoewer/go-strcase v1.2.0 google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 diff --git a/go.sum b/go.sum index 69a046f2b..062b316c3 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.21.2 h1:o+Wj235dy4gFYlYin3JsMpp3EEfMrPm/6tdoyjT98S0= -cel.dev/expr v0.21.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.22.1 h1:xoFEsNh972Yzey8N9TCPx2nDvMN7TMhQEzxLuj/iRrI= +cel.dev/expr v0.22.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= diff --git a/policy/BUILD.bazel b/policy/BUILD.bazel index 875f523c3..f058f1ab9 100644 --- a/policy/BUILD.bazel +++ b/policy/BUILD.bazel @@ -15,7 +15,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") package( - default_visibility = ["//policy:__subpackages__"], + default_visibility = [ + "//policy:__subpackages__", + "//tools:__subpackages__" + ], licenses = ["notice"], ) @@ -67,3 +70,8 @@ go_test( "@in_gopkg_yaml_v3//:go_default_library", ], ) + +filegroup( + name = "k8s_policy_testdata", + srcs = glob(["testdata/k8s/*"]), +) \ No newline at end of file diff --git a/policy/testdata/k8s/config.yaml b/policy/testdata/k8s/config.yaml index 15a32b535..5a2cb3290 100644 --- a/policy/testdata/k8s/config.yaml +++ b/policy/testdata/k8s/config.yaml @@ -14,6 +14,10 @@ name: k8s extensions: + - name: "optional" + version: "latest" + - name: "bindings" + version: "latest" - name: "strings" version: 2 variables: diff --git a/tools/compiler/BUILD.bazel b/tools/compiler/BUILD.bazel new file mode 100644 index 000000000..0c3e4080b --- /dev/null +++ b/tools/compiler/BUILD.bazel @@ -0,0 +1,75 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# https://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. + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +package( + default_visibility = ["//visibility:public"], + licenses = ["notice"], +) + +go_library( + name = "go_default_library", + srcs = [ + "compiler.go", + ], + importpath = "github.com/google/cel-go/tools/compiler", + deps = [ + "//cel:go_default_library", + "//common:go_default_library", + "//common/env:go_default_library", + "//common/types:go_default_library", + "//ext:go_default_library", + "//policy:go_default_library", + "@dev_cel_expr//:expr", + "@dev_cel_expr//conformance:go_default_library", + "@in_gopkg_yaml_v3//:go_default_library", + "@io_bazel_rules_go//go/runfiles", + "@org_golang_google_genproto_googleapis_api//expr/v1alpha1:go_default_library", + "@org_golang_google_protobuf//encoding/prototext:go_default_library", + "@org_golang_google_protobuf//proto:go_default_library", + "@org_golang_google_protobuf//reflect/protoreflect:go_default_library", + "@org_golang_google_protobuf//types/descriptorpb:go_default_library", + ], +) + +filegroup( + name = "compiler_testdata", + srcs = glob(["testdata/**"]), +) + +go_test( + name = "go_default_test", + size = "small", + srcs = [ + "compiler_test.go", + ], + data = [ + ":compiler_testdata", + "//policy:k8s_policy_testdata", + ], + embed = [":go_default_library"], + deps = [ + "//cel:go_default_library", + "//common/decls:go_default_library", + "//common/env:go_default_library", + "//common/types:go_default_library", + "//ext:go_default_library", + "//policy:go_default_library", + "@dev_cel_expr//:expr", + "@dev_cel_expr//conformance:go_default_library", + "@in_gopkg_yaml_v3//:go_default_library", + "@org_golang_google_protobuf//types/known/structpb:go_default_library", + ], +) diff --git a/tools/compiler/compiler.go b/tools/compiler/compiler.go new file mode 100644 index 000000000..fd590ecf3 --- /dev/null +++ b/tools/compiler/compiler.go @@ -0,0 +1,534 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// https://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 compiler exposes a standard way to set up a compiler which can be used for CEL +// expressions and policies. +package compiler + +import ( + "fmt" + "os" + "path/filepath" + + "gopkg.in/yaml.v3" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common" + "github.com/google/cel-go/common/env" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/ext" + "github.com/google/cel-go/policy" + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + + celpb "cel.dev/expr" + configpb "cel.dev/expr/conformance" + exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" + descpb "google.golang.org/protobuf/types/descriptorpb" +) + +// FileFormat represents the format of the file being loaded. +type FileFormat int + +const ( + // Unspecified is used when the file format is not determined. + Unspecified FileFormat = iota + 1 + // BinaryProto is used for a binary proto file. + BinaryProto + // TextProto is used for a text proto file. + TextProto + // TextYAML is used for a YAML file. + TextYAML + // CELString is used for a CEL string expression defined in a file with .cel extension. + CELString + // CELPolicy is used for a CEL policy file with .celpolicy extension. + CELPolicy +) + +// PolicyMetadataEnvOption represents a function which accepts a policy metadata map and returns an +// environment option used to extend the CEL environment. +// +// The policy metadata map is generally produced as a byproduct of parsing the policy and it can +// be optionally customised by providing a custom policy parser. +type PolicyMetadataEnvOption func(map[string]any) cel.EnvOption + +// Compiler interface is used to set up a compiler with the following capabilities: +// - create a CEL environment +// - create a policy parser +// - fetch policy compiler options +// - fetch policy environment options +// +// Note: This compiler is not the same as the CEL expression compiler, rather it provides an +// abstraction layer which can create the different components needed for parsing and compiling CEL +// expressions and policies. +type Compiler interface { + // CreateEnv creates a singleton CEL environment with the configured environment options. + CreateEnv() (*cel.Env, error) + // CreatePolicyParser creates a policy parser using the optionally configured parser options. + CreatePolicyParser() (*policy.Parser, error) + // PolicyCompilerOptions returns the policy compiler options. + PolicyCompilerOptions() []policy.CompilerOption + // PolicyMetadataEnvOptions returns the policy metadata environment options. + PolicyMetadataEnvOptions() []PolicyMetadataEnvOption +} + +type compiler struct { + envOptions []cel.EnvOption + policyParserOptions []policy.ParserOption + policyCompilerOptions []policy.CompilerOption + policyMetadataEnvOptions []PolicyMetadataEnvOption + env *cel.Env +} + +// NewCompiler creates a new compiler with a set of functional options. +func NewCompiler(opts ...any) (Compiler, error) { + c := &compiler{ + envOptions: []cel.EnvOption{}, + policyParserOptions: []policy.ParserOption{}, + policyCompilerOptions: []policy.CompilerOption{}, + policyMetadataEnvOptions: []PolicyMetadataEnvOption{}, + } + for _, opt := range opts { + switch opt := opt.(type) { + case cel.EnvOption: + c.envOptions = append(c.envOptions, opt) + case policy.ParserOption: + c.policyParserOptions = append(c.policyParserOptions, opt) + case policy.CompilerOption: + c.policyCompilerOptions = append(c.policyCompilerOptions, opt) + case PolicyMetadataEnvOption: + c.policyMetadataEnvOptions = append(c.policyMetadataEnvOptions, opt) + default: + return nil, fmt.Errorf("unsupported compiler option: %v", opt) + } + } + return c, nil +} + +// CreateEnv creates a singleton CEL environment with the configured environment options. +func (c *compiler) CreateEnv() (*cel.Env, error) { + if c.env != nil { + return c.env, nil + } + env, err := cel.NewCustomEnv(c.envOptions...) + if err != nil { + return nil, err + } + c.env = env + return c.env, nil +} + +// CreatePolicyParser creates a policy parser using the optionally configured parser options. +func (c *compiler) CreatePolicyParser() (*policy.Parser, error) { + return policy.NewParser(c.policyParserOptions...) +} + +// PolicyCompilerOptions returns the policy compiler options configured in the compiler. +func (c *compiler) PolicyCompilerOptions() []policy.CompilerOption { + return c.policyCompilerOptions +} + +// PolicyMetadataEnvOptions returns the policy metadata environment options configured in the compiler. +func (c *compiler) PolicyMetadataEnvOptions() []PolicyMetadataEnvOption { + return c.policyMetadataEnvOptions +} + +func loadFile(path string) ([]byte, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read file %q: %v", path, err) + } + return data, err +} + +func loadProtoFile(path string, format FileFormat, out protoreflect.ProtoMessage) error { + unmarshaller := proto.Unmarshal + if format == TextProto { + unmarshaller = prototext.Unmarshal + } + data, err := loadFile(path) + if err != nil { + return err + } + return unmarshaller(data, out) +} + +func inferFileFormat(path string) FileFormat { + extension := filepath.Ext(path) + switch extension { + case ".textproto": + return TextProto + case ".yaml": + return TextYAML + case ".binarypb", ".fds": + return BinaryProto + case ".cel": + return CELString + case ".celpolicy": + return CELPolicy + default: + return Unspecified + } +} + +// EnvironmentFile returns an EnvOption which loads a serialized CEL environment from a file. +// The file must be in one of the following formats: +// - Textproto +// - YAML +// - Binarypb +func EnvironmentFile(path string) cel.EnvOption { + return func(e *cel.Env) (*cel.Env, error) { + format := inferFileFormat(path) + if format != TextProto && format != TextYAML && format != BinaryProto { + return nil, fmt.Errorf("file extension must be one of .textproto, .yaml, .binarypb: found %v", format) + } + var envConfig *env.Config + var fileDescriptorSet *descpb.FileDescriptorSet + switch format { + case TextProto, BinaryProto: + pbEnv := &configpb.Environment{} + var err error + if err = loadProtoFile(path, format, pbEnv); err != nil { + return nil, err + } + envConfig, fileDescriptorSet, err = envProtoToConfig(pbEnv) + if err != nil { + return nil, err + } + case TextYAML: + envConfig = &env.Config{} + data, err := loadFile(path) + if err != nil { + return nil, err + } + err = yaml.Unmarshal(data, envConfig) + if err != nil { + return nil, fmt.Errorf("yaml.Unmarshal failed to map CEL environment: %w", err) + } + default: + return nil, fmt.Errorf("unsupported format: %v, file: %s", format, path) + } + var envOpts []cel.EnvOption + if fileDescriptorSet != nil { + envOpts = append(envOpts, cel.TypeDescs(fileDescriptorSet)) + } + envOpts = append(envOpts, cel.FromConfig(envConfig, ext.ExtensionOptionFactory)) + var err error + e, err = e.Extend(envOpts...) + if err != nil { + return nil, fmt.Errorf("e.Extend() with env options %v failed: %w", envOpts, err) + } + return e, nil + } +} + +func envProtoToConfig(pbEnv *configpb.Environment) (*env.Config, *descpb.FileDescriptorSet, error) { + if pbEnv == nil { + return nil, nil, fmt.Errorf("proto environment is not set") + } + envConfig := env.NewConfig(pbEnv.GetName()) + envConfig.Description = pbEnv.GetDescription() + envConfig.SetContainer(pbEnv.GetContainer()) + for _, imp := range pbEnv.GetImports() { + envConfig.AddImports(env.NewImport(imp.GetName())) + } + stdLib, err := envToStdLib(pbEnv) + if err != nil { + return nil, nil, err + } + envConfig.SetStdLib(stdLib) + extensions := make([]*env.Extension, 0, len(pbEnv.GetExtensions())) + for _, extension := range pbEnv.GetExtensions() { + extensions = append(extensions, &env.Extension{Name: extension.GetName(), Version: extension.GetVersion()}) + } + envConfig.AddExtensions(extensions...) + if contextVariable := pbEnv.GetContextVariable(); contextVariable != nil { + envConfig.SetContextVariable(env.NewContextVariable(contextVariable.GetTypeName())) + } + functions, variables, err := protoDeclToFunctionsAndVariables(pbEnv.GetDeclarations()) + if err != nil { + return nil, nil, err + } + envConfig.AddFunctions(functions...) + envConfig.AddVariables(variables...) + validators, err := envToValidators(pbEnv) + if err != nil { + return nil, nil, err + } + envConfig.AddValidators(validators...) + features, err := envToFeatures(pbEnv) + if err != nil { + return nil, nil, err + } + envConfig.AddFeatures(features...) + fileDescriptorSet := pbEnv.GetMessageTypeExtension() + return envConfig, fileDescriptorSet, nil +} + +func envToFeatures(pbEnv *configpb.Environment) ([]*env.Feature, error) { + features := make([]*env.Feature, 0, len(pbEnv.GetFeatures())+1) + for _, feature := range pbEnv.GetFeatures() { + features = append(features, env.NewFeature(feature.GetName(), feature.GetEnabled())) + } + if pbEnv.GetEnableMacroCallTracking() { + features = append(features, env.NewFeature("cel.feature.macro_call_tracking", true)) + } + return features, nil +} + +func envToValidators(pbEnv *configpb.Environment) ([]*env.Validator, error) { + validators := make([]*env.Validator, 0, len(pbEnv.GetValidators())) + for _, pbValidator := range pbEnv.GetValidators() { + validator := env.NewValidator(pbValidator.GetName()) + config := map[string]any{} + for k, v := range pbValidator.GetConfig() { + val := types.DefaultTypeAdapter.NativeToValue(v) + config[k] = val + } + validator.SetConfig(config) + validators = append(validators, validator) + } + return validators, nil +} + +func protoDeclToFunctionsAndVariables(declarations []*celpb.Decl) ([]*env.Function, []*env.Variable, error) { + functions := make([]*env.Function, 0, len(declarations)) + variables := make([]*env.Variable, 0, len(declarations)) + for _, decl := range declarations { + switch decl.GetDeclKind().(type) { + case *celpb.Decl_Function: + fn, err := protoDeclToFunction(decl) + if err != nil { + return nil, nil, fmt.Errorf("protoDeclToFunction(%s) failed to create function: %w", decl.GetName(), err) + } + functions = append(functions, fn) + case *celpb.Decl_Ident: + t, err := types.ProtoAsType(decl.GetIdent().GetType()) + if err != nil { + return nil, nil, fmt.Errorf("types.ProtoAsType(%s) failed to create type: %w", decl.GetIdent().GetType(), err) + } + variables = append(variables, env.NewVariable(decl.GetName(), env.SerializeTypeDesc(t))) + } + } + return functions, variables, nil +} + +func envToStdLib(pbEnv *configpb.Environment) (*env.LibrarySubset, error) { + stdLib := env.NewLibrarySubset() + pbEnvStdLib := pbEnv.GetStdlib() + if pbEnvStdLib == nil { + if pbEnv.GetDisableStandardCelDeclarations() { + stdLib.SetDisabled(true) + return stdLib, nil + } + return nil, nil + } + if !stdLib.Disabled { + stdLib.SetDisabled(pbEnvStdLib.GetDisabled()) + } + stdLib.SetDisableMacros(pbEnvStdLib.GetDisableMacros()) + stdLib.AddIncludedMacros(pbEnvStdLib.GetIncludeMacros()...) + stdLib.AddExcludedMacros(pbEnvStdLib.GetExcludeMacros()...) + if pbEnvStdLib.GetIncludeFunctions() != nil { + for _, includeFn := range pbEnvStdLib.GetIncludeFunctions() { + if includeFn.GetFunction() != nil { + fn, err := protoDeclToFunction(includeFn) + if err != nil { + return nil, err + } + stdLib.AddIncludedFunctions(fn) + } else { + return nil, fmt.Errorf("IncludeFunctions must specify a function decl") + } + } + } + if pbEnvStdLib.GetExcludeFunctions() != nil { + for _, excludeFn := range pbEnvStdLib.GetExcludeFunctions() { + if excludeFn.GetFunction() != nil { + fn, err := protoDeclToFunction(excludeFn) + if err != nil { + return nil, err + } + stdLib.AddExcludedFunctions(fn) + } else { + return nil, fmt.Errorf("ExcludeFunctions must specify a function decl") + } + } + } + return stdLib, nil +} + +func protoDeclToFunction(decl *celpb.Decl) (*env.Function, error) { + declFn := decl.GetFunction() + if declFn == nil { + return nil, nil + } + overloads := make([]*env.Overload, 0, len(declFn.GetOverloads())) + for _, o := range declFn.GetOverloads() { + args := make([]*env.TypeDesc, 0, len(o.GetParams())) + for _, p := range o.GetParams() { + t, err := types.ProtoAsType(p) + if err != nil { + return nil, err + } + args = append(args, env.SerializeTypeDesc(t)) + } + res, err := types.ProtoAsType(o.GetResultType()) + if err != nil { + return nil, err + } + ret := env.SerializeTypeDesc(res) + if o.IsInstanceFunction { + overloads = append(overloads, env.NewMemberOverload(o.GetOverloadId(), args[0], args[1:], ret)) + } else { + overloads = append(overloads, env.NewOverload(o.GetOverloadId(), args, ret)) + } + } + return env.NewFunction(decl.GetName(), overloads...), nil +} + +// TypeDescriptorSetFile returns an EnvOption which loads type descriptors from a file. +// The file must be in binary format. +func TypeDescriptorSetFile(path string) cel.EnvOption { + return func(e *cel.Env) (*cel.Env, error) { + format := inferFileFormat(path) + if format != BinaryProto { + return nil, fmt.Errorf("type descriptor must be in binary format") + } + fds := &descpb.FileDescriptorSet{} + if err := loadProtoFile(path, BinaryProto, fds); err != nil { + return nil, err + } + var err error + e, err = e.Extend(cel.TypeDescs(fds)) + if err != nil { + return nil, fmt.Errorf("e.Extend() with type descriptor set %v failed: %w", fds, err) + } + return e, nil + } +} + +// InputExpression is an interface for an expression which can be compiled into a CEL AST and return +// an optional policy metadata map. +type InputExpression interface { + // CreateAST creates a CEL AST from the input expression using the provided compiler. + CreateAST(Compiler) (*cel.Ast, map[string]any, error) +} + +// CompiledExpression is an InputExpression which loads a CheckedExpr from a file. +type CompiledExpression struct { + Path string +} + +// CreateAST creates a CEL AST from a checked expression file. +// The file must be in one of the following formats: +// - Binarypb +// - Textproto +func (c *CompiledExpression) CreateAST(_ Compiler) (*cel.Ast, map[string]any, error) { + var expr exprpb.CheckedExpr + format := inferFileFormat(c.Path) + if format != BinaryProto && format != TextProto { + return nil, nil, fmt.Errorf("file extension must be .binarypb or .textproto: found %v", format) + } + if err := loadProtoFile(c.Path, format, &expr); err != nil { + return nil, nil, err + } + return cel.CheckedExprToAst(&expr), nil, nil +} + +// FileExpression is an InputExpression which loads a CEL expression or policy from a file. +type FileExpression struct { + Path string +} + +// CreateAST creates a CEL AST from a file using the provided compiler: +// - All policy metadata options as executed using the policy metadata map to extend the +// environment. +// - All policy compiler options are passed on to compile the parsed policy. +// +// The file must be in one of the following formats: +// - .cel: CEL string expression +// - .celpolicy: CEL policy +func (f *FileExpression) CreateAST(compiler Compiler) (*cel.Ast, map[string]any, error) { + e, err := compiler.CreateEnv() + if err != nil { + return nil, nil, err + } + data, err := loadFile(f.Path) + if err != nil { + return nil, nil, err + } + format := inferFileFormat(f.Path) + switch format { + case CELString: + src := common.NewStringSource(string(data), f.Path) + ast, iss := e.CompileSource(src) + if iss.Err() != nil { + return nil, nil, fmt.Errorf("e.CompileSource(%q) failed: %w", src.Content(), iss.Err()) + } + return ast, nil, nil + case CELPolicy, TextYAML: + src := policy.ByteSource(data, f.Path) + parser, err := compiler.CreatePolicyParser() + if err != nil { + return nil, nil, err + } + p, iss := parser.Parse(src) + if iss.Err() != nil { + return nil, nil, fmt.Errorf("parser.Parse(%q) failed: %w", src.Content(), iss.Err()) + } + policyMetadata := clonePolicyMetadata(p) + for _, opt := range compiler.PolicyMetadataEnvOptions() { + if e, err = e.Extend(opt(policyMetadata)); err != nil { + return nil, nil, fmt.Errorf("e.Extend() with metadata option failed: %w", err) + } + } + ast, iss := policy.Compile(e, p, compiler.PolicyCompilerOptions()...) + if iss.Err() != nil { + return nil, nil, fmt.Errorf("policy.Compile(%q) failed: %w", src.Content(), iss.Err()) + } + return ast, policyMetadata, nil + default: + return nil, nil, fmt.Errorf("unsupported file format: %v", format) + } +} + +func clonePolicyMetadata(p *policy.Policy) map[string]any { + metadataKeys := p.MetadataKeys() + metadata := make(map[string]any, len(metadataKeys)) + for _, key := range metadataKeys { + value, _ := p.Metadata(key) + metadata[key] = value + } + return metadata +} + +// RawExpression is an InputExpression which loads a CEL expression from a string. +type RawExpression struct { + Value string +} + +// CreateAST creates a CEL AST from a raw CEL expression using the provided compiler. +func (r *RawExpression) CreateAST(compiler Compiler) (*cel.Ast, map[string]any, error) { + e, err := compiler.CreateEnv() + if err != nil { + return nil, nil, err + } + ast, iss := e.Compile(r.Value) + if iss.Err() != nil { + return nil, nil, fmt.Errorf("e.Compile(%q) failed: %w", r.Value, iss.Err()) + } + return ast, nil, nil +} diff --git a/tools/compiler/compiler_test.go b/tools/compiler/compiler_test.go new file mode 100644 index 000000000..d0c9ad0be --- /dev/null +++ b/tools/compiler/compiler_test.go @@ -0,0 +1,492 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// https://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 compiler + +import ( + "reflect" + "testing" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/decls" + "github.com/google/cel-go/common/env" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/ext" + "github.com/google/cel-go/policy" + "gopkg.in/yaml.v3" + + celpb "cel.dev/expr" + configpb "cel.dev/expr/conformance" + structpb "google.golang.org/protobuf/types/known/structpb" +) + +func TestEnvironmentFileCompareTextprotoAndYAML(t *testing.T) { + t.Run("compare textproto and yaml environment files", func(t *testing.T) { + pbEnv := testEnvProto() + protoConfig, err := configFromEnvProto(t, pbEnv) + if err != nil { + t.Fatalf("configFromEnvProto(%v) failed: %v", pbEnv, err) + } + config, err := parseEnv(t, "yaml_config", "testdata/config.yaml") + if err != nil { + t.Fatalf("parseEnv(%s) failed: %v", "testdata/config.yaml", err) + } + if protoConfig.Container != config.Container { + t.Fatalf("Container got %q, wanted %q", protoConfig.Container, config.Container) + } + if !reflect.DeepEqual(protoConfig.Imports, config.Imports) { + t.Fatalf("Imports got %v, wanted %v", protoConfig.Imports, config.Imports) + } + if !reflect.DeepEqual(protoConfig.StdLib, config.StdLib) { + t.Fatalf("StdLib got %v, wanted %v", protoConfig.StdLib, config.StdLib) + } + if len(protoConfig.Extensions) != len(config.Extensions) { + t.Fatalf("Extensions count got %d, wanted %d", len(protoConfig.Extensions), len(config.Extensions)) + } + for _, protoConfigExt := range protoConfig.Extensions { + found := false + for _, configExt := range config.Extensions { + if reflect.DeepEqual(protoConfigExt, configExt) { + found = true + break + } + } + if !found { + t.Fatalf("Extensions got %v, wanted %v", protoConfig.Extensions, config.Extensions) + } + } + if !reflect.DeepEqual(protoConfig.ContextVariable, config.ContextVariable) { + t.Fatalf("ContextVariable got %v, wanted %v", protoConfig.ContextVariable, config.ContextVariable) + } + if len(protoConfig.Variables) != len(config.Variables) { + t.Fatalf("Variables count got %d, wanted %d", len(protoConfig.Variables), len(config.Variables)) + } else { + for i, v := range protoConfig.Variables { + for j, p := range v.TypeDesc.Params { + if p.TypeName == "google.protobuf.Any" && + config.Variables[i].TypeDesc.Params[j].TypeName == "dyn" { + p.TypeName = "dyn" + } + } + if !reflect.DeepEqual(v, config.Variables[i]) { + t.Fatalf("Variables[%d] not equal, got %v, wanted %v", i, v, config.Variables[i]) + } + } + } + if len(protoConfig.Functions) != len(config.Functions) { + t.Fatalf("Functions count got %d, wanted %d", len(protoConfig.Functions), len(config.Functions)) + } else { + for i, f := range protoConfig.Functions { + if !reflect.DeepEqual(f, config.Functions[i]) { + t.Fatalf("Functions[%d] not equal, got %v, wanted %v", i, f, config.Functions[i]) + } + } + } + if len(protoConfig.Features) != len(config.Features) { + t.Fatalf("Features count got %d, wanted %d", len(protoConfig.Features), len(config.Features)) + } else { + for i, f := range protoConfig.Features { + if !reflect.DeepEqual(f, config.Features[i]) { + t.Fatalf("Features[%d] not equal, got %v, wanted %v", i, f, config.Features[i]) + } + } + } + if len(protoConfig.Validators) != len(config.Validators) { + t.Fatalf("Validators count got %d, wanted %d", len(protoConfig.Validators), len(config.Validators)) + } else { + for i, v := range protoConfig.Validators { + if !reflect.DeepEqual(v, config.Validators[i]) { + t.Fatalf("Validators[%d] not equal, got %v, wanted %v", i, v, config.Validators[i]) + } + } + } + }) +} + +func testEnvProto() *configpb.Environment { + return &configpb.Environment{ + Name: "test-environment", + Description: "Test environment", + Container: "google.expr", + Imports: []*configpb.Environment_Import{ + {Name: "google.expr.proto3.test.TestAllTypes"}, + }, + Stdlib: &configpb.LibrarySubset{ + IncludeMacros: []string{"has", "exists"}, + IncludeFunctions: []*celpb.Decl{ + { + Name: "_==_", + DeclKind: &celpb.Decl_Function{ + Function: &celpb.Decl_FunctionDecl{ + Overloads: []*celpb.Decl_FunctionDecl_Overload{ + { + OverloadId: "equals", + Params: []*celpb.Type{ + { + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_STRING, + }, + }, + { + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_STRING, + }, + }, + }, + ResultType: &celpb.Type{ + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_BOOL, + }, + }, + }, + }, + }, + }, + }, + { + Name: "_||_", + DeclKind: &celpb.Decl_Function{ + Function: &celpb.Decl_FunctionDecl{ + Overloads: []*celpb.Decl_FunctionDecl_Overload{ + { + OverloadId: "logical_or", + Params: []*celpb.Type{ + { + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_BOOL, + }, + }, + { + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_BOOL, + }, + }, + }, + ResultType: &celpb.Type{ + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_BOOL, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Extensions: []*configpb.Extension{ + { + Name: "optional", + Version: "latest", + }, + { + Name: "lists", + Version: "latest", + }, + { + Name: "sets", + Version: "latest", + }, + }, + Declarations: []*celpb.Decl{ + { + Name: "destination.ip", + DeclKind: &celpb.Decl_Ident{ + Ident: &celpb.Decl_IdentDecl{ + Type: &celpb.Type{ + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_STRING, + }, + }, + }, + }, + }, + { + Name: "origin.ip", + DeclKind: &celpb.Decl_Ident{ + Ident: &celpb.Decl_IdentDecl{ + Type: &celpb.Type{ + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_STRING, + }, + }, + }, + }, + }, + { + Name: "spec.restricted_destinations", + DeclKind: &celpb.Decl_Ident{ + Ident: &celpb.Decl_IdentDecl{ + Type: &celpb.Type{ + TypeKind: &celpb.Type_ListType_{ + ListType: &celpb.Type_ListType{ + ElemType: &celpb.Type{ + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_STRING, + }, + }, + }, + }, + }, + }, + }, + }, + { + Name: "spec.origin", + DeclKind: &celpb.Decl_Ident{ + Ident: &celpb.Decl_IdentDecl{ + Type: &celpb.Type{ + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_STRING, + }, + }, + }, + }, + }, + { + Name: "request", + DeclKind: &celpb.Decl_Ident{ + Ident: &celpb.Decl_IdentDecl{ + Type: &celpb.Type{ + TypeKind: &celpb.Type_MapType_{ + MapType: &celpb.Type_MapType{ + KeyType: &celpb.Type{ + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_STRING, + }, + }, + ValueType: &celpb.Type{ + TypeKind: &celpb.Type_WellKnown{ + WellKnown: celpb.Type_ANY, + }, + }, + }, + }, + }, + }, + }, + }, + { + Name: "resource", + DeclKind: &celpb.Decl_Ident{ + Ident: &celpb.Decl_IdentDecl{ + Type: &celpb.Type{ + TypeKind: &celpb.Type_MapType_{ + MapType: &celpb.Type_MapType{ + KeyType: &celpb.Type{ + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_STRING, + }, + }, + ValueType: &celpb.Type{ + TypeKind: &celpb.Type_WellKnown{ + WellKnown: celpb.Type_ANY, + }, + }, + }, + }, + }, + }, + }, + }, + { + Name: "locationCode", + DeclKind: &celpb.Decl_Function{ + Function: &celpb.Decl_FunctionDecl{ + Overloads: []*celpb.Decl_FunctionDecl_Overload{ + { + OverloadId: "locationCode_string", + Params: []*celpb.Type{ + { + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_STRING, + }, + }, + }, + ResultType: &celpb.Type{ + TypeKind: &celpb.Type_Primitive{ + Primitive: celpb.Type_STRING, + }, + }, + }, + }, + }, + }, + }, + }, + Validators: []*configpb.Validator{ + {Name: "cel.validator.duration"}, + { + Name: "cel.validator.nesting_comprehension_limit", + Config: map[string]*structpb.Value{ + "limits": structpb.NewNumberValue(2), + }, + }, + }, + Features: []*configpb.Feature{ + { + Name: "cel.feature.macro_call_tracking", + Enabled: true, + }, + }, + } +} + +func configFromEnvProto(t *testing.T, pbEnv *configpb.Environment) (*env.Config, error) { + t.Helper() + envConfig, fileDescriptorSet, err := envProtoToConfig(pbEnv) + if err != nil { + return nil, err + } + var envOpts []cel.EnvOption + if fileDescriptorSet != nil { + envOpts = append(envOpts, cel.TypeDescs(fileDescriptorSet)) + } + envOpts = append(envOpts, cel.FromConfig(envConfig, ext.ExtensionOptionFactory)) + return envOptionToConfig(t, envConfig.Name, envOpts...) +} + +func parseEnv(t *testing.T, name, path string) (*env.Config, error) { + t.Helper() + opts := EnvironmentFile(path) + return envOptionToConfig(t, name, opts) +} + +func envOptionToConfig(t *testing.T, name string, opts ...cel.EnvOption) (*env.Config, error) { + t.Helper() + e, err := cel.NewCustomEnv(opts...) + if err != nil { + return nil, err + } + conf, err := e.ToConfig(name) + if err != nil { + return nil, err + } + return conf, nil +} + +func TestFileExpressionCustomPolicyParser(t *testing.T) { + t.Run("test file expression custom policy parser", func(t *testing.T) { + envOpt := EnvironmentFile("../../policy/testdata/k8s/config.yaml") + parserOpt := policy.ParserOption(func(p *policy.Parser) (*policy.Parser, error) { + p.TagVisitor = policy.K8sTestTagHandler() + return p, nil + }) + compilerOpts := []any{envOpt, parserOpt} + compiler, err := NewCompiler(compilerOpts...) + if err != nil { + t.Fatalf("NewCompiler() failed: %v", err) + } + policyFile := &FileExpression{ + Path: "../../policy/testdata/k8s/policy.yaml", + } + k8sAst, _, err := policyFile.CreateAST(compiler) + if err != nil { + t.Fatalf("CreateAST() failed: %v", err) + } + if k8sAst == nil { + t.Fatalf("CreateAST() returned nil ast") + } + }) +} + +func TestFileExpressionPolicyMetadataOptions(t *testing.T) { + t.Run("test file expression policy metadata options", func(t *testing.T) { + envOpt := EnvironmentFile("testdata/custom_policy_config.yaml") + parserOpt := policy.ParserOption(func(p *policy.Parser) (*policy.Parser, error) { + p.TagVisitor = customTagHandler{TagVisitor: policy.DefaultTagVisitor()} + return p, nil + }) + policyMetadataOpt := PolicyMetadataEnvOption(ParsePolicyVariables) + compilerOpts := []any{envOpt, parserOpt, policyMetadataOpt} + compiler, err := NewCompiler(compilerOpts...) + if err != nil { + t.Fatalf("NewCompiler() failed: %v", err) + } + policyFile := &FileExpression{ + Path: "testdata/custom_policy.celpolicy", + } + ast, _, err := policyFile.CreateAST(compiler) + if err != nil { + t.Fatalf("CreateAST() failed: %v", err) + } + if ast == nil { + t.Fatalf("CreateAST() returned nil ast") + } + }) +} + +func ParsePolicyVariables(metadata map[string]any) cel.EnvOption { + variables := []*decls.VariableDecl{} + for n, t := range metadata { + variables = append(variables, decls.NewVariable(n, parseCustomPolicyVariableType(t.(string)))) + } + return cel.VariableDecls(variables...) +} + +func parseCustomPolicyVariableType(t string) *types.Type { + switch t { + case "int": + return types.IntType + case "string": + return types.StringType + default: + return types.UnknownType + } +} + +type variableType struct { + VariableName string `yaml:"variable_name"` + VariableType string `yaml:"variable_type"` +} + +type customTagHandler struct { + policy.TagVisitor +} + +func (customTagHandler) PolicyTag(ctx policy.ParserContext, id int64, tagName string, node *yaml.Node, p *policy.Policy) { + switch tagName { + case "variable_types": + varList := []*variableType{} + if err := node.Decode(&varList); err != nil { + ctx.ReportErrorAtID(id, "invalid yaml variable_types node: %v, error: %w", node, err) + return + } + for _, v := range varList { + p.SetMetadata(v.VariableName, v.VariableType) + } + default: + ctx.ReportErrorAtID(id, "unsupported policy tag: %s", tagName) + } +} + +func TestRawExpressionCreateAst(t *testing.T) { + t.Run("test raw expression create ast", func(t *testing.T) { + envOpt := EnvironmentFile("testdata/config.yaml") + compiler, err := NewCompiler(envOpt) + if err != nil { + t.Fatalf("NewCompiler() failed: %v", err) + } + rawExpr := &RawExpression{ + Value: "locationCode(destination.ip)==locationCode(origin.ip)", + } + ast, _, err := rawExpr.CreateAST(compiler) + if err != nil { + t.Fatalf("CreateAST() failed: %v", err) + } + if ast == nil { + t.Fatalf("CreateAST() returned nil ast") + } + }) +} diff --git a/tools/compiler/testdata/config.yaml b/tools/compiler/testdata/config.yaml new file mode 100644 index 000000000..a63a859ad --- /dev/null +++ b/tools/compiler/testdata/config.yaml @@ -0,0 +1,84 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# https://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. + +name: "test-environment" +description: "Test environment" +container: "google.expr" +imports: +- name: "google.expr.proto3.test.TestAllTypes" +stdlib: + include_macros: + - has + - exists + include_functions: + - name: "_==_" + overloads: + - id: equals + args: + - type_name: "string" + - type_name: "string" + return: + type_name: "bool" + - name: "_||_" + overloads: + - id: logical_or + args: + - type_name: "bool" + - type_name: "bool" + return: + type_name: "bool" +extensions: +- name: "optional" + version: "latest" +- name: "lists" + version: "latest" +- name: "sets" + version: "latest" +variables: +- name: "destination.ip" + type_name: "string" +- name: "origin.ip" + type_name: "string" +- name: "spec.restricted_destinations" + type_name: "list" + params: + - type_name: "string" +- name: "spec.origin" + type_name: "string" +- name: "request" + type_name: "map" + params: + - type_name: "string" + - type_name: "dyn" +- name: "resource" + type_name: "map" + params: + - type_name: "string" + - type_name: "dyn" +functions: +- name: "locationCode" + overloads: + - id: "locationCode_string" + args: + - type_name: "string" + return: + type_name: "string" +validators: +- name: cel.validator.duration +- name: cel.validator.nesting_comprehension_limit + config: + limit: 2 +features: +- name: cel.feature.macro_call_tracking + enabled: true diff --git a/tools/compiler/testdata/custom_policy.celpolicy b/tools/compiler/testdata/custom_policy.celpolicy new file mode 100644 index 000000000..663fcf0a7 --- /dev/null +++ b/tools/compiler/testdata/custom_policy.celpolicy @@ -0,0 +1,25 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# https://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. + +name: "custom_policy" +variable_types: + - variable_name: "variable1" + variable_type: "int" + - variable_name: "variable2" + variable_type: "string" +rule: + match: + - condition: | + variable1 == 1 || variable2 == "known" + output: "true" diff --git a/tools/compiler/testdata/custom_policy_config.yaml b/tools/compiler/testdata/custom_policy_config.yaml new file mode 100644 index 000000000..460fab4b4 --- /dev/null +++ b/tools/compiler/testdata/custom_policy_config.yaml @@ -0,0 +1,18 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# https://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. + +name: "custom_policy_config" +extensions: +- name: "optional" + version: "latest" diff --git a/tools/go.mod b/tools/go.mod new file mode 100644 index 000000000..392efac8f --- /dev/null +++ b/tools/go.mod @@ -0,0 +1,22 @@ +module github.com/google/cel-go/tools + +go 1.23.0 + +require ( + cel.dev/expr v0.22.1 + github.com/google/cel-go v0.22.0 + github.com/google/cel-go/policy v0.0.0-20250311174852-f5ea07b389a1 + google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf + google.golang.org/protobuf v1.36.5 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect +) + +replace github.com/google/cel-go => ../. diff --git a/tools/go.sum b/tools/go.sum new file mode 100644 index 000000000..b34becfc8 --- /dev/null +++ b/tools/go.sum @@ -0,0 +1,37 @@ +cel.dev/expr v0.22.1 h1:xoFEsNh972Yzey8N9TCPx2nDvMN7TMhQEzxLuj/iRrI= +cel.dev/expr v0.22.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/cel-go/policy v0.0.0-20250311174852-f5ea07b389a1 h1:jT/04RYwo++S9tvHggXWuAqvnc2Pi0BTHYsZYVOoMOs= +github.com/google/cel-go/policy v0.0.0-20250311174852-f5ea07b389a1/go.mod h1:dgvqy3CzFx17CBMkL0s1hd0r1+rEQOo85tDpr0g6Dp4= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf h1:BdIVRm+fyDUn8lrZLPSlBCfM/YKDwUBYgDoLv9+DYo0= +google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf h1:dHDlF3CWxQkefK9IJx+O8ldY0gLygvrlYRBNbPqDWuY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/cel.dev/expr/.bazelversion b/vendor/cel.dev/expr/.bazelversion index 26bc914a3..13c50892b 100644 --- a/vendor/cel.dev/expr/.bazelversion +++ b/vendor/cel.dev/expr/.bazelversion @@ -1,2 +1,2 @@ -7.0.1 +7.3.2 # Keep this pinned version in parity with cel-go diff --git a/vendor/cel.dev/expr/MODULE.bazel b/vendor/cel.dev/expr/MODULE.bazel index 9794266f5..c0a631316 100644 --- a/vendor/cel.dev/expr/MODULE.bazel +++ b/vendor/cel.dev/expr/MODULE.bazel @@ -13,12 +13,24 @@ bazel_dep( ) bazel_dep( name = "googleapis", - version = "0.0.0-20240819-fe8ba054a", + version = "0.0.0-20241220-5e258e33.bcr.1", repo_name = "com_google_googleapis", ) +bazel_dep( + name = "googleapis-cc", + version = "1.0.0", +) +bazel_dep( + name = "googleapis-java", + version = "1.0.0", +) +bazel_dep( + name = "googleapis-go", + version = "1.0.0", +) bazel_dep( name = "protobuf", - version = "26.0", + version = "27.0", repo_name = "com_google_protobuf", ) bazel_dep( @@ -27,7 +39,7 @@ bazel_dep( ) bazel_dep( name = "rules_go", - version = "0.49.0", + version = "0.50.1", repo_name = "io_bazel_rules_go", ) bazel_dep( @@ -50,14 +62,6 @@ python.toolchain( python_version = "3.11", ) -switched_rules = use_extension("@com_google_googleapis//:extensions.bzl", "switched_rules") -switched_rules.use_languages( - cc = True, - go = True, - java = True, -) -use_repo(switched_rules, "com_google_googleapis_imports") - go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk") go_sdk.download(version = "1.21.1") diff --git a/vendor/cel.dev/expr/cloudbuild.yaml b/vendor/cel.dev/expr/cloudbuild.yaml index c40881f12..e3e533a04 100644 --- a/vendor/cel.dev/expr/cloudbuild.yaml +++ b/vendor/cel.dev/expr/cloudbuild.yaml @@ -1,5 +1,5 @@ steps: -- name: 'gcr.io/cloud-builders/bazel:7.0.1' +- name: 'gcr.io/cloud-builders/bazel:7.3.2' entrypoint: bazel args: ['build', '...'] id: bazel-build diff --git a/vendor/modules.txt b/vendor/modules.txt index 620a2e524..dfdf1bd13 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# cel.dev/expr v0.21.2 +# cel.dev/expr v0.22.1 ## explicit; go 1.21.1 cel.dev/expr # github.com/antlr4-go/antlr/v4 v4.13.0