概要
CELのキーコンセプトでは
- Control PlaneでCEL式をParse & Checkし、生成されたASTを保存
- Data Planeで保存したASTを読み取り、インプット値を評価する
と説明されていました。
主に管理ツール等で前者のControl Planeを実装し、オペレーターに自由に評価式を入力してもらいます。
Control PlaneではCheckも行われるので評価式がおかしければその時点でvalidationしてくれます。
そしてData Planeでは起動時にASTを読み込んで入力値を評価できるようにします。
今回は具体的に外部に保存する手順を説明します。
環境
- go v1.22.1
- cel-go v0.20.1
方法
Control Plane、Data Planeそれぞれで説明します。
Control Plane
ref: https://codelabs.developers.google.com/codelabs/cel-go#1
cel.AstToCheckedExprを使ってASTをprotocol buffersに変換します。
そしてprotobufをシリアライズして外部に保存します。
package main import ( "log" "os" "github.com/google/cel-go/cel" "google.golang.org/protobuf/proto" ) func main() { env, err := cel.NewEnv( cel.Variable("name", cel.StringType), ) if err != nil { log.Fatalf("Failed to create CEL environment: %v", err) } ast, iss := env.Compile(`"Hello, " + name + "!"`) if iss.Err() != nil { log.Fatalf("Failed to compile expression: %v", iss.Err()) } expr, err := cel.AstToCheckedExpr(ast) if err != nil { log.Fatalf("Failed to convert an Ast to an protobuf: %v", err) } // Serialize the AST to Protocol Buffers binary format astBytes, err := proto.Marshal(expr) if err != nil { log.Fatalf("Failed to serialize AST: %v", err) } // Save the serialized AST to a file if err := os.WriteFile("ast.pb", astBytes, 0644); err != nil { log.Fatalf("Failed to write AST to file: %v", err) } }
今回は簡単のためローカルファイルとして保存しますが、実際の運用ではGCSやデータベースに保存するのが良いでしょう。
動作確認
$ go run main.go $ ls ast.pb go.mod go.sum main.go
ast.pb
が生成されました。
Data Plane
ローカルにあるast.pb
を読み込みデシリアライズし、cel.CheckedExprToAstで*cel.Astにします。
package main import ( "log" "os" "github.com/google/cel-go/cel" exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" "google.golang.org/protobuf/proto" ) func main() { env, err := cel.NewEnv( cel.Variable("name", cel.StringType), ) if err != nil { log.Fatalf("Failed to create CEL environment: %v", err) } // Read the serialized AST from the file astBytes, err := os.ReadFile("./gen/ast.pb") if err != nil { log.Fatalf("Failed to read AST from file: %v", err) } // Deserialize the AST from Protocol Buffers binary format var astPb exprpb.CheckedExpr if err := proto.Unmarshal(astBytes, &astPb); err != nil { log.Fatalf("Failed to deserialize AST: %v", err) } // Recover the AST structure ast := cel.CheckedExprToAst(&astPb) // Create a Program from the AST prg, err := env.Program(ast, cel.EvalOptions(cel.OptTrackState, cel.OptExhaustiveEval)) if err != nil { log.Fatalf("Failed to create program: %v", err) } // Evaluate the Program with a given variable out, _, err := prg.Eval(map[string]interface{}{ "name": "World", }) if err != nil { log.Fatalf("Evaluation failed: %v", err) } log.Printf("Result: %v\n", out) }
動作確認
$ go run main.go 2024/03/31 15:04:48 Result: Hello, World!
期待通り評価式に入力値が適用されたアウトプットが表示されました。
その他
サンプルコード
今回のサンプルコードはこちらです。
まとめ
CELのキーコンセプトに合わせた運用方法を紹介しました。