This document explains how you can add your own logic as a CEL API and make it available to the gRPC Federation.
We call this mechanism the CEL Plugin.
This document is based on _examples/15_cel_plugin. If you would like to see a working sample, please refer to that document.
First, define in ProtocolBuffers the types, functions you want to provide.
syntax = "proto3";
package example.regexp;
import "grpc/federation/federation.proto";
option go_package = "example/plugin;pluginpb";
message Regexp {
uint64 ptr = 1; // store raw pointer value.
}
option (grpc.federation.file).plugin.export = {
name: "regexp"
types: [
{
name: "Regexp"
methods: [
{
name: "matchString"
args { type { kind: STRING } }
return { kind: BOOL }
}
]
}
]
functions: [
{
name: "compile"
args { type { kind: STRING } }
return { message: "Regexp" }
}
]
};
(grpc.federation.file).plugin.export
option is used to define the API. In this example, the plugin is named regexp
.
The regexp
plugin belongs to the example.regexp
package. Also, this provides the Regexp
message type and makes matchString
available as a method on the Regexp
message, and compile
function is also added.
In summary, it contains the following definitions.
example.regexp.Regexp
messageexample.regexp.Regexp.matchString(string) bool
methodexample.regexp.compile(string) example.regexp.Regexp
function
Put this definition in plugin/plugin.proto
.
Next, write the code to use the defined API.
The following definition is a sample of adding a gRPC method that compiles the expr
specified by the example.regexp.compile
function and returns whether the contents of target
matches the result.
syntax = "proto3";
package org.federation;
import "grpc/federation/federation.proto";
import "plugin/plugin.proto"; // your CEL API definition
option go_package = "example/federation;federation";
service FederationService {
option (grpc.federation.service) = {};
rpc IsMatch(IsMatchRequest) returns (IsMatchResponse) {};
}
message IsMatchRequest {
string expr = 1;
string target = 2;
}
message IsMatchResponse {
option (grpc.federation.message) = {
def {
name: "matched"
// Use the defined CEL API.
by: "example.regexp.compile($.expr).matchString($.target)"
}
};
bool result = 1 [(grpc.federation.field).by = "matched"];
}
Run the gRPC Federation code generator to generate Go language code. At this time, CEL API's schema is verified by the gRPC Federation Compiler, and an error is output if it is used incorrectly.
By the code generation, the library code is generated to write the plugin. In this example, the output is in plugin/plugin_grpc_federation.pb.go
.
Using that library, write a plugin as follows.
package main
import (
pluginpb "example/plugin"
"regexp"
"unsafe"
)
var _ pluginpb.RegexpPlugin = new(plugin)
type plugin struct{}
// For example.regexp.compile function.
func (_ *plugin) Example_Regexp_Compile(ctx context.Context, expr string) (*pluginpb.Regexp, error) {
re, err := regexp.Compile(expr)
if err != nil {
return nil, err
}
return &pluginpb.Regexp{
Ptr: uint32(uintptr(unsafe.Pointer(re))),
}, nil
}
// For example.regexp.Regexp.matchString method.
func (_ *plugin) Example_Regexp_Regexp_MatchString(ctx context.Context, re *pluginpb.Regexp, s string) (bool, error) {
return (*regexp.Regexp)(unsafe.Pointer(uintptr(re.Ptr))).MatchString(s), nil
}
func main() {
pluginpb.RegisterRegexpPlugin(new(plugin))
}
pluginpb.RegisterRegexpPlugin
function for registering developed plugins. Also, pluginpb.RegexpPlugin
is interface type for plugin.
$ GOOS=wasip1 GOARCH=wasm go build -o regexp.wasm ./cmd/plugin
$ sha256sum regexp.wasm
820f86011519c42da0fe9876bc2ca7fbee5df746acf104d9e2b9bba802ddd2b9 regexp.wasm
Initialize the gRPC server with the path to the wasm file and the sha256 value of the file.
federationServer, err := federation.NewFederationService(federation.FederationServiceConfig{
CELPlugin: &federation.FederationServiceCELPluginConfig{
Regexp: federation.FederationServiceCELPluginWasmConfig{
Path: "regexp.wasm",
Sha256: "820f86011519c42da0fe9876bc2ca7fbee5df746acf104d9e2b9bba802ddd2b9",
},
},
})
Host and wasm plugin using stdio to exchange data.
The exchanged data types are CELPluginRequest
and CELPluginResponse
defined on plugin.proto. Actually, the data is converted to json and then exchanged.