mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat(bindings)!: add RVM/Program support across FFI and language bindings (#565)
- FFI: add RVM/Program APIs, execution state accessors, HostAwait handling, and buffer/result helpers in rvm.rs, common.rs, engine.rs. - Compiler: emit HostAwait for __builtin_host_await in function_calls.rs. - RVM tests: add HostAwait regression cases and extend harness for suspend/resume responses in host_await.yaml and mod.rs. - C/C++: add RVM tests/examples and wrapper updates in rvm_tests.c, rvm_tests.cpp, regorus.hpp, plus CMake wiring. - C#: add Program/Rvm bindings, SafeHandle/PInvoke, tests, and example usage in Regorus, RvmProgramTests.cs, Program.cs, and README updates. - Go: add Program/Rvm bindings, tests, and examples in rvm.go, rvm_test.go, main.go. - Java: add Program/Rvm bindings, JNI glue, and examples in lib.rs, regorus, Test.java. - Python: add Program/Rvm bindings and examples in lib.rs, test.py. - WASM: add Program/Rvm bindings and examples in lib.rs, test.js. - Tooling: wire binding tests in xtask and ignore generated Java artifacts in .gitignore. Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
0316ccd90c
commit
3f7a5496dc
@@ -1,6 +1,6 @@
|
||||
package regorus
|
||||
|
||||
// #cgo LDFLAGS: -L ../../../ffi/target/release -lregorus_ffi
|
||||
// #cgo LDFLAGS: -L ../../../ffi/target/release -L ../../../ffi/target/debug -lregorus_ffi
|
||||
// #include "../../../ffi/regorus.h"
|
||||
import "C"
|
||||
import (
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
package regorus
|
||||
|
||||
// #cgo LDFLAGS: -L ../../../ffi/target/release -L ../../../ffi/target/debug -lregorus_ffi
|
||||
// #include "../../../ffi/regorus.h"
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type PolicyModule struct {
|
||||
Id string
|
||||
Content string
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
p *C.RegorusProgram
|
||||
}
|
||||
|
||||
type Rvm struct {
|
||||
vm *C.RegorusRvm
|
||||
}
|
||||
|
||||
type Buffer struct {
|
||||
b *C.RegorusBuffer
|
||||
}
|
||||
|
||||
func (b *Buffer) Close() {
|
||||
if b != nil && b.b != nil {
|
||||
C.regorus_buffer_drop(b.b)
|
||||
b.b = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) Bytes() []byte {
|
||||
if b == nil || b.b == nil || b.b.data == nil || b.b.len == 0 {
|
||||
return nil
|
||||
}
|
||||
return C.GoBytes(unsafe.Pointer(b.b.data), C.int(b.b.len))
|
||||
}
|
||||
|
||||
func (p *Program) Close() {
|
||||
if p != nil && p.p != nil {
|
||||
C.regorus_program_drop(p.p)
|
||||
p.p = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) SerializeBinary() ([]byte, error) {
|
||||
result := C.regorus_program_serialize_binary(p.p)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return nil, fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
buffer := &Buffer{b: (*C.RegorusBuffer)(result.pointer_value)}
|
||||
defer buffer.Close()
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *Program) GenerateListing() (string, error) {
|
||||
result := C.regorus_program_generate_listing(p.p)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (p *Program) GenerateTabularListing() (string, error) {
|
||||
result := C.regorus_program_generate_tabular_listing(p.p)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func DeserializeProgram(data []byte) (*Program, bool, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, false, fmt.Errorf("empty program data")
|
||||
}
|
||||
var isPartial C.bool
|
||||
result := C.regorus_program_deserialize_binary((*C.uchar)(unsafe.Pointer(&data[0])), C.ulong(len(data)), (*C.bool)(unsafe.Pointer(&isPartial)))
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return nil, false, fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return &Program{p: (*C.RegorusProgram)(result.pointer_value)}, bool(isPartial), nil
|
||||
}
|
||||
|
||||
func CompileProgramFromModules(data string, modules []PolicyModule, entryPoints []string) (*Program, error) {
|
||||
dataC := C.CString(data)
|
||||
defer C.free(unsafe.Pointer(dataC))
|
||||
|
||||
cModules := make([]C.RegorusPolicyModule, len(modules))
|
||||
moduleIdPtrs := make([]*C.char, len(modules))
|
||||
moduleContentPtrs := make([]*C.char, len(modules))
|
||||
for i, module := range modules {
|
||||
idC := C.CString(module.Id)
|
||||
contentC := C.CString(module.Content)
|
||||
moduleIdPtrs[i] = idC
|
||||
moduleContentPtrs[i] = contentC
|
||||
cModules[i].id = idC
|
||||
cModules[i].content = contentC
|
||||
}
|
||||
defer func() {
|
||||
for i := range moduleIdPtrs {
|
||||
if moduleIdPtrs[i] != nil {
|
||||
C.free(unsafe.Pointer(moduleIdPtrs[i]))
|
||||
}
|
||||
if moduleContentPtrs[i] != nil {
|
||||
C.free(unsafe.Pointer(moduleContentPtrs[i]))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
entryPtrs := make([]*C.char, len(entryPoints))
|
||||
for i, entry := range entryPoints {
|
||||
entryPtrs[i] = C.CString(entry)
|
||||
}
|
||||
defer func() {
|
||||
for _, ptr := range entryPtrs {
|
||||
C.free(unsafe.Pointer(ptr))
|
||||
}
|
||||
}()
|
||||
|
||||
var modulesPtr *C.RegorusPolicyModule
|
||||
if len(cModules) > 0 {
|
||||
modulesPtr = (*C.RegorusPolicyModule)(unsafe.Pointer(&cModules[0]))
|
||||
}
|
||||
var entryPtr **C.char
|
||||
if len(entryPtrs) > 0 {
|
||||
entryPtr = (**C.char)(unsafe.Pointer(&entryPtrs[0]))
|
||||
}
|
||||
|
||||
result := C.regorus_program_compile_from_modules(
|
||||
dataC,
|
||||
modulesPtr,
|
||||
C.ulong(len(cModules)),
|
||||
entryPtr,
|
||||
C.ulong(len(entryPtrs)),
|
||||
)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return nil, fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return &Program{p: (*C.RegorusProgram)(result.pointer_value)}, nil
|
||||
}
|
||||
|
||||
func CompileProgramFromEngine(engine *Engine, entryPoints []string) (*Program, error) {
|
||||
entryPtrs := make([]*C.char, len(entryPoints))
|
||||
for i, entry := range entryPoints {
|
||||
entryPtrs[i] = C.CString(entry)
|
||||
}
|
||||
defer func() {
|
||||
for _, ptr := range entryPtrs {
|
||||
C.free(unsafe.Pointer(ptr))
|
||||
}
|
||||
}()
|
||||
|
||||
var entryPtr **C.char
|
||||
if len(entryPtrs) > 0 {
|
||||
entryPtr = (**C.char)(unsafe.Pointer(&entryPtrs[0]))
|
||||
}
|
||||
|
||||
result := C.regorus_engine_compile_program_with_entrypoints(
|
||||
engine.e,
|
||||
entryPtr,
|
||||
C.ulong(len(entryPtrs)),
|
||||
)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return nil, fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return &Program{p: (*C.RegorusProgram)(result.pointer_value)}, nil
|
||||
}
|
||||
|
||||
func NewRvm() (*Rvm, error) {
|
||||
vm := C.regorus_rvm_new()
|
||||
if vm == nil {
|
||||
return nil, fmt.Errorf("failed to create RVM")
|
||||
}
|
||||
return &Rvm{vm: vm}, nil
|
||||
}
|
||||
|
||||
func (r *Rvm) Close() {
|
||||
if r != nil && r.vm != nil {
|
||||
C.regorus_rvm_drop(r.vm)
|
||||
r.vm = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Rvm) LoadProgram(program *Program) error {
|
||||
result := C.regorus_rvm_load_program(r.vm, program.p)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rvm) SetDataJson(data string) error {
|
||||
dataC := C.CString(data)
|
||||
defer C.free(unsafe.Pointer(dataC))
|
||||
result := C.regorus_rvm_set_data(r.vm, dataC)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rvm) SetInputJson(input string) error {
|
||||
inputC := C.CString(input)
|
||||
defer C.free(unsafe.Pointer(inputC))
|
||||
result := C.regorus_rvm_set_input(r.vm, inputC)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rvm) SetExecutionMode(mode byte) error {
|
||||
result := C.regorus_rvm_set_execution_mode(r.vm, C.uchar(mode))
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rvm) Execute() (string, error) {
|
||||
result := C.regorus_rvm_execute(r.vm)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (r *Rvm) ExecuteEntryPoint(name string) (string, error) {
|
||||
nameC := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(nameC))
|
||||
result := C.regorus_rvm_execute_entry_point_by_name(r.vm, nameC)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (r *Rvm) ExecuteEntryPointIndex(index uint64) (string, error) {
|
||||
result := C.regorus_rvm_execute_entry_point_by_index(r.vm, C.ulong(index))
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (r *Rvm) Resume(resumeValue string, hasValue bool) (string, error) {
|
||||
var valueC *C.char
|
||||
if hasValue {
|
||||
valueC = C.CString(resumeValue)
|
||||
defer C.free(unsafe.Pointer(valueC))
|
||||
}
|
||||
result := C.regorus_rvm_resume(r.vm, valueC, C.bool(hasValue))
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (r *Rvm) GetExecutionState() (string, error) {
|
||||
result := C.regorus_rvm_get_execution_state(r.vm)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package regorus
|
||||
|
||||
import "testing"
|
||||
|
||||
const rvmPolicy = `
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.account.active == true
|
||||
details := __builtin_host_await(input.account.id, "account")
|
||||
details.tier == "gold"
|
||||
}
|
||||
`
|
||||
|
||||
const rvmInput = `{"account":{"id":"acct-1","active":true}}`
|
||||
|
||||
const rvmRegularPolicy = `
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
input.active == true
|
||||
}
|
||||
`
|
||||
|
||||
const rvmRegularInput = `{"user":"alice","active":true}`
|
||||
|
||||
func TestRvmProgramCompileAndExecute(t *testing.T) {
|
||||
modules := []PolicyModule{{Id: "demo.rego", Content: rvmRegularPolicy}}
|
||||
entryPoints := []string{"data.demo.allow"}
|
||||
program, err := CompileProgramFromModules("{}", modules, entryPoints)
|
||||
if err != nil {
|
||||
t.Fatalf("compile program: %v", err)
|
||||
}
|
||||
defer program.Close()
|
||||
|
||||
listing, err := program.GenerateListing()
|
||||
if err != nil || listing == "" {
|
||||
t.Fatalf("listing failed: %v", err)
|
||||
}
|
||||
|
||||
binary, err := program.SerializeBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("serialize program: %v", err)
|
||||
}
|
||||
|
||||
rehydrated, isPartial, err := DeserializeProgram(binary)
|
||||
if err != nil {
|
||||
t.Fatalf("deserialize program: %v", err)
|
||||
}
|
||||
if isPartial {
|
||||
t.Fatalf("deserialized program marked partial")
|
||||
}
|
||||
defer rehydrated.Close()
|
||||
|
||||
vm, err := NewRvm()
|
||||
if err != nil {
|
||||
t.Fatalf("new vm: %v", err)
|
||||
}
|
||||
defer vm.Close()
|
||||
|
||||
if err := vm.LoadProgram(rehydrated); err != nil {
|
||||
t.Fatalf("load program: %v", err)
|
||||
}
|
||||
if err := vm.SetInputJson(rvmRegularInput); err != nil {
|
||||
t.Fatalf("set input: %v", err)
|
||||
}
|
||||
|
||||
result, err := vm.Execute()
|
||||
if err != nil {
|
||||
t.Fatalf("execute: %v", err)
|
||||
}
|
||||
if result != "true" {
|
||||
t.Fatalf("expected allow=true, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRvmHostAwaitSuspendResume(t *testing.T) {
|
||||
modules := []PolicyModule{{Id: "host_await.rego", Content: rvmPolicy}}
|
||||
entryPoints := []string{"data.demo.allow"}
|
||||
program, err := CompileProgramFromModules("{}", modules, entryPoints)
|
||||
if err != nil {
|
||||
t.Fatalf("compile program: %v", err)
|
||||
}
|
||||
defer program.Close()
|
||||
|
||||
vm, err := NewRvm()
|
||||
if err != nil {
|
||||
t.Fatalf("new vm: %v", err)
|
||||
}
|
||||
defer vm.Close()
|
||||
|
||||
if err := vm.SetExecutionMode(1); err != nil {
|
||||
t.Fatalf("set execution mode: %v", err)
|
||||
}
|
||||
if err := vm.LoadProgram(program); err != nil {
|
||||
t.Fatalf("load program: %v", err)
|
||||
}
|
||||
if err := vm.SetInputJson(rvmInput); err != nil {
|
||||
t.Fatalf("set input: %v", err)
|
||||
}
|
||||
|
||||
if _, err := vm.Execute(); err != nil {
|
||||
t.Fatalf("execute in suspendable mode failed: %v", err)
|
||||
}
|
||||
|
||||
state, err := vm.GetExecutionState()
|
||||
if err != nil {
|
||||
t.Fatalf("get execution state: %v", err)
|
||||
}
|
||||
if state == "" {
|
||||
t.Fatalf("expected non-empty execution state")
|
||||
}
|
||||
|
||||
result, err := vm.Resume(`{"tier":"gold"}`, true)
|
||||
if err != nil {
|
||||
t.Fatalf("resume: %v", err)
|
||||
}
|
||||
if result != "true" {
|
||||
t.Fatalf("expected allow=true, got %s", result)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user