Perform Rego queries from multiple languages with rego-cpp
A Rego language interpreter in C++ with wrappers for C, Rust, and Python.
Currently v1.0.0ยทAll releases
What is Rego?
Rego is a language developed by Open Policy Agent (OPA) for use in defining policies in cloud systems.
We think Rego is a great language, and you can learn more about it here. However, it has some limitations. Primarily, the interpreter is only accessible via the command line or a web server. Further, the only option for using the language in-process is via an interface in Go.
The rego-cpp project provides the ability to integrate Rego natively into a wider range of languages. We currrently support C, C++, dotnet, Python, and Rust, and are largely compatible with v1.8.0 of the language. You can learn more about our implementation here.
You can compile rego-cpp directly into your C++ program, which gives you full access to our API. We recommend using CMake FetchContent to add rego-cpp to your project.
FetchContent_Declare(
regocpp
GIT_REPOSITORY https://github.com/microsoft/rego-cpp.git
GIT_TAG main
)
FetchContent_MakeAvailable(regocpp)
# ...
target_link_libraries(myproject PRIVATE regocpp::rego)
CMakeLists.txt
#include <rego/rego.hh>
int main()
{
rego::Interpreter rego;
rego.add_module("objects", R"(package objects
rect := {`width`: 2, "height": 4}
cube := {"width": 3, `height`: 4, "depth": 5}
a := 42
b := false
c := null
d := {"a": a, "x": [b, c]}
index := 1
shapes := [rect, cube]
names := ["prod", `smoke1`, "dev"]
sites := [{"name": "prod"}, {"name": names[index]}, {"name": "dev"}]
e := {
a: "foo",
"three": c,
names[2]: b,
"four": d,
}
f := e["dev"])");
rego.add_data_json(R"({
"one": {
"bar": "Foo",
"baz": 5,
"be": true,
"bop": 23.4
},
"two": {
"bar": "Bar",
"baz": 12.3,
"be": false,
"bop": 42
}
})");
rego.add_data_json(R"({
"three": {
"bar": "Baz",
"baz": 15,
"be": true,
"bop": 4.23
}
})");
rego.set_input_json(R"({
"a": 10,
"b": "20",
"c": 30.0,
"d": true
})");
std::cout << rego.query("[data.one, input.b, data.objects.sites[1]] = x") << std::endl;
}
example.cc
You can also compile rego-cpp directly into your C program. The C API provides most of the functionality of the full C++ API while providing a stable interface which is amenable to FFI. We recommend using CMake FetchContent to add rego-cpp to your project.
FetchContent_Declare(
regocpp
GIT_REPOSITORY https://github.com/microsoft/rego-cpp.git
GIT_TAG main
)
FetchContent_MakeAvailable(regocpp)
# ...
target_link_libraries(myproject PRIVATE regocpp::rego)
CMakeLists.txt
#include <rego/rego_c.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
regoEnum err;
int rc = EXIT_SUCCESS;
regoOutput* output = NULL;
regoInterpreter* rego = regoNew();
err = regoAddModuleFile(rego, "examples/objects.rego");
if (err != REGO_OK)
{
goto error;
}
err = regoAddDataJSONFile(rego, "examples/data0.json");
if (err != REGO_OK)
{
goto error;
}
err = regoAddDataJSONFile(rego, "examples/data1.json");
if (err != REGO_OK)
{
goto error;
}
err = regoSetInputJSONFile(rego, "examples/input0.json");
if (err != REGO_OK)
{
goto error;
}
output = regoQuery(rego, "[data.one, input.b, data.objects.sites[1]] = x");
if(output == NULL)
{
goto error;
}
printf("%s\n", regoOutputString(output));
goto exit;
error:
printf("%s\n", regoGetError(rego));
rc = EXIT_FAILURE;
exit:
if (output != NULL)
{
regoFreeOutput(output);
}
if (rego != NULL)
{
regoFree(rego);
}
return rc;
}
example.c
We provide a Nuget package called Rego which exposes the C API to dotnet languages.
Learn more about the dotnet API
$ dotnet add Rego
using System.Collections;
using Rego;
//////////////////////
///// Query only /////
//////////////////////
Console.WriteLine("Query Only");
// You can run simple queries without any input or data.
Interpreter rego = new();
var output = rego.Query("x=5;y=x + (2 - 4 * 0.25) * -3 + 7.4;2 * 5");
Console.WriteLine(output);
// {"expressions":[true, true, 10], "bindings":{"x":5, "y":9.4}}
// You can access bound results using the Binding method
Console.WriteLine("x = {0}", output.Binding("x"));
// You can also access expressions by index
Console.WriteLine(output.Expressions()[2]);
Console.WriteLine();
////////////////////////
//// Input and Data ////
////////////////////////
Console.WriteLine("Input and Data");
// If you provide an object, it will be converted to JSON before
// being added to the state.
var data0 = new Dictionary{
{"one", new Dictionary{
{"bar", "Foo"},
{"baz", 5},
{"be", true},
{"bop", 23.4}}},
{"two", new Dictionary{
{"bar", "Bar"},
{"baz", 12.3},
{"be", false},
{"bop", 42}}}};
rego.AddData(data0);
// You can also provide JSON directly.
rego.AddDataJson("""
{
"three": {
"bar": "Baz",
"baz": 15,
"be": true,
"bop": 4.23
}
}
""");
var objectsSource = """
package objects
rect := {"width": 2, "height": 4}
cube := {"width": 3, "height": 4, "depth": 5}
a := 42
b := false
c := null
d := {"a": a, "x": [b, c]}
index := 1
shapes := [rect, cube]
names := ["prod", "smoke1", "dev"]
sites := [{"name": "prod"}, {"name": names[index]}, {"name": "dev"}]
e := {
a: "foo",
"three": c,
names[2]: b,
"four": d,
}
f := e["dev"]
""";
rego.AddModule("objects.rego", objectsSource);
// inputs can be either JSON or Rego, and provided
// as objects (which will be converted to JSON) or as
// text.
rego.SetInputTerm("""
{
"a": 10,
"b": "20",
"c": 30.0,
"d": true
}
""");
Console.WriteLine(rego.Query("[data.one, input.b, data.objects.sites[1]] = x"));
// {"bindings":{"x":[{"bar":"Foo", "baz":5, "be":true, "bop":23.4}, "20", {"name":"smoke1"}]}}
///////////////////
///// Bundles ////
///////////////////
Console.WriteLine();
Console.WriteLine("Bundles");
// If you want to run the same set of queries against a policy with different
// inputs, you can create a bundle and use that to save the cost of compilation.
Interpreter rego_build = new();
rego_build.AddDataJson("""
{"a": 7,
"b": 13}
""");
rego_build.AddModule("example.rego", """
package example
foo := data.a * input.x + data.b * input.y
bar := data.b * input.x + data.a * input.y
""");
// We can specify both a default query, and specific entry points into the policy
// that should be made available to use later.
var bundle = rego_build.Build("x=data.example.foo + data.example.bar", ["example/foo", "example/bar"]);
// we can now save the bundle to the disk
rego_build.SaveBundle("bundle", bundle);
// and load it again
Interpreter rego_run = new();
rego_run.LoadBundle("bundle");
// the most efficient way to provide input to a policy is by constructing it
// manually, without the need for parsing JSON or Rego.
var input = Input.Create(new Dictionary
{
{"x", 104 },
{"y", 119 }
});
rego_run.SetInput(input);
// We can query the bundle, which will use the entrypoint of the default query
// provided at build
Console.WriteLine("query: {0}",rego_run.QueryBundle(bundle));
// Query: {"expressions":[true], "bindings":{"x":4460}}
// or we can query specific entrypoints
Console.WriteLine("example/foo: {0}", rego_run.QueryBundle(bundle, "example/foo"));
// example/foo: {"expressions":[2275]}
Program.cs
We provide a Python wheel called regopy which exposes the C API in a pythonic way.
Learn more about the Python API
$ pip install regopy
from regopy import Interpreter
rego = Interpreter()
with open("examples/objects.rego") as f:
rego.add_module("objects", f.read())
rego.add_data({
"one": {
"bar": "Foo",
"baz": 5,
"be": True,
"bop": 23.4
},
"two": {
"bar": "Bar",
"baz": 12.3,
"be": False,
"bop": 42
}
})
rego.add_data({
"three": {
"bar": "Baz",
"baz": 15,
"be": True,
"bop": 4.23
}
})
rego.set_input({
"a": 10,
"b": "20",
"c": 30.0,
"d": True
})
print(rego.query("[data.one, input.b, data.objects.sites[1]] = x"))
example.py
We provide a Rust crate called regorust which exposes the C API in idiomatic Rust. This example shows a CLI interpreter implemented in Rust.
[dependencies]
regorust = "0.4.5"
Cargo.toml
use clap::Parser;
use regorust::Interpreter;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short, long)]
data: Vec<std::path::PathBuf>,
#[arg(short, long)]
input: Option<std::path::PathBuf>,
query: String,
}
fn main() {
let args = Args::parse();
let rego = Interpreter::new();
if let Some(input) = args.input {
rego.set_input_json_file(input.as_path())
.expect("Failed to read input file");
}
for data in args.data {
if data.extension().unwrap() == "rego" {
rego.add_module_file(data.as_path())
.expect("Failed to load module file");
} else {
rego.add_data_json_file(data.as_path())
.expect("Failed to load data file");
}
}
println!(
"{}",
rego.query(args.query.as_str())
.expect("Failed to evaluate query")
);
}
main.rs