Fugue Rego Toolkit
fregot(Fugue Rego Toolkit) is a set of tools for working with the Rego policy language, which is part of the Open Policy Agent (OPA) policy engine.
fregotallows you to easily evaluate expressions, debug code, test policies, and more.
(Check out the text-based demo on asciinema so you can copy/paste commands.)
fregotcan be seen an alternative REPL to OPA's built-in interpreter. The goals are a little different -- whereas the OPA agent provides general-purpose components and functionality that are particularly useful with Kubernetes,
fregotwas developed internally at Fugue as a lightweight set of tools to enhance the Rego development experience. It aims to provide:
You can use
fregotto validate just about any kind of JSON against Rego policy. For an example of using
fregotto test a Terraform plan prior to deployment, see the Example Use Case.
We are also actively working on improving the static analyzer to prevent many kinds of bugs.
fregot works on all major platforms, and pre-built binaries are available for macOS and Linux.
Download the latest binary for macOS or Linux from Releases.
Place the binary somewhere in your
$PATH.
Installation through source is done using standard Haskell tooling -- Cabal and stack both work well.
cdinto it.
stack install.
$HOME/.local/binis in your
$PATH.
cdinto it.
cabal install.
$HOME/.cabal/binis in your
$PATH.
fregot v0.11.1Usage: fregot COMMAND
Available options: -h,--help Show this help text --format FORMAT Format for error messages and diagnostics
Available commands: repl Run fregot repl test Run tests in .rego files bundle Bundle .rego files eval Evaluate a rego expression
fregotunderstands a number of subcommands. See details and examples below.
fregot repl [PATHS] [--input PATH] [--watch]: Start a REPL. Optionally, use the
--input [PATH]flag to specify input and the
--watchflag to enable watching files. See working with the REPL for details and examples.
fregot test [PATHS]: Run tests.
fregotwill recursively look for Rego files in the given paths and run any rule starting with
test_.
Usage: fregot test PATHS Run tests in .rego filesAvailable options: PATHS Rego files or directories to test
Tip: You can run this command yourself from the root of this repo!
This command runs all rules starting with
test_in
ami_id.rego:
fregot test examples/ami_id/ami_id.rego
You'll see a count of passed, failed, and errored tests:
passed: 2, failed: 0, errored: 0
Take a look at
examples/ami_id/ami_id.regoto see test examples.
Tip: You can also test Rego files from within the REPL using the
:testcommand. See
:testfor details.
fregot bundle [PATHS]: Compile a number of Rego files into a single bundle that can be loaded faster than individual files. Experimental. Note that we currently use a different bundle format from OPA.
Usage: fregot bundle (-o|--output BUNDLE) PATHS Bundle .rego filesAvailable options: -o,--output BUNDLE Path of output file PATHS Rego files or directories to bundle
fregot eval [--input PATH] EXPRESSION [PATHS]: Evaluate a Rego expression in a policy file using a JSON file as input.
Usage: fregot eval [-i|--input PATH] EXPRESSION [PATHS] Evaluate a rego expressionAvailable options: -i,--input PATH Input filepath EXPRESSION Rego expression to evaluate PATHS Rego files or directories to load
Tip: You can run this command yourself from the root of this repo!
This command evaluates the
data.fregot.examples.ami_id.allowexpression from
ami_id.regousing the input file
repl_input.json:
fregot eval \ --input examples/ami_id/repl_input.json \ 'data.fregot.examples.ami_id.allow' \ examples/ami_id/ami_id.rego
You'll see the value of the expression in the output:
[true]
Note that the expression argument should be formatted as
data.package.ruleaccording to the package and rule name in the Rego file.
F u g u e R E G O T o o l k i t fregot v0.11.1 repl - use :help for usage info repl% :help Enter an expression to evaluate it. Enter a rule to add it to the current package.Other commands: :break Set or remove a breakpoint :help show this info :input set the input document :open open a different package, e.g.
:open foo
:quit exit the repl :load load a rego file, e.g.:load foo.rego
:reload reload modified rego files :continue continue running the debugged program :step step (into) the next rule in the debugged program :next step (over) the next rule in the debugged program :rewind go back to the previous debug suspension :test run tests in the current package :type print the type of a term :where print your location :watch evaluate input after file changesShortcuts are supported for commands, e.g.,
:l
for:load
.
The REPL is currently the most important part of
fregot. After loading the files passed on the command line, you end up on an interactive prompt.
There are three ways to interact with the REPL:
Entering a rule adds the rule to the currently open package, e.g.:
repl% numbers = {4, 8, 15, 16, 23, 42} Rule numbers added
Entering a query evaluates that query, e.g.:
repl% numbers[n]; n % 2 == 0; n = 4 | n = 4 = 16 | n = 16 = 8 | n = 8 = 42 | n = 42
There are number of special commands that start with
:. Entering
:helpshows you the full list of commands.
repl% :quit
See REPL Usage for details and examples for each command.
The REPL has the concept of an open package, indicated by the prompt. Initially this is
repl, but you can change this using
:open. For example, we can add a rule to the package
fooand change back to
repl:
repl% :open foo Warning: package foo contains no rules foo% a = 1 Rule a added foo% :open repl repl% data.foo.a = 1
A typical workflow is to have an editor open as well as a
fregot repl. You can then load the file using
:load, which automatically opens the package:
repl% :load policy.rego Loading policy.rego... Loaded package policy policy%
Once you make changes to the file, just reload it using
:reload, which reloads all modified Rego files. If the file includes any rules starting with
test_, you can assess your changes using
:test, like so:
policy% :reload Reloaded policy.rego policy% :test passed: 1, failed: 0, errored: 0
Debugging generally follows these steps:
:break
You can start debugging by setting a breakpoint with
:breakand then evaluating something.
To set a breakpoint, use the
:breakcommand. The command below sets a breakpoint on the rule
denyin the currently loaded package,
fregot.examples.demo:
fregot.examples.demo% :break deny Set breakpoint at fregot.examples.demo.deny
You can use the
:breakcommand with either names, or a position in a file (line number). For example:
:break foo # `foo` in the current package :break repl.foo # `foo` in the package repl :break data.repl.foo # Same as above :break foo.rego:9 # Line 9 of `foo.rego`
Once at least one breakpoint is set, you can use
:breakwithout arguments to display the list. You'll see output like this:
fregot.examples.demo.test_allow examples/demo/demo.rego:9 fregot.examples.demo.deny
You can also use
:breakon an existing breakpoint to remove it again:
fregot.examples.demo% :break deny Removed breakpoint at fregot.examples.demo.deny
Next, evaluate an expression that activates the breakpoint. If the
replpackage is already loaded and the breakpoint is set on
foo, we can just evaluate
foo:
%repl foo
Once the breakpoint is activated, you end up in a debugging context. From here, you can do a number of things:
:continueto continue to the next breakpoint.
:stepand
:nextto step into and over the next query, respectively.
:rewindto go back to the last step.
:whereto see your current location.
:quitto exit debugging mode. Use
:quitagain to exit the REPL.
By default,
fregotturns off optimizations when debugging. This allows you to more naturally follow what the code is doing. However, this may get in your way when trying to debug complex queries that take too long to execute without optimizations.
To explicitly turn on optimizations (even while debugging), use
fregot repl -O.
:break [location]sets a breakpoint. For example, the command below sets a breakpoint at the
allowrule in the package
fregot.examples.ami_id:
repl% :break fregot.examples.ami_id.allow Set breakpoint at fregot.examples.ami_id.allow
To enter debugging mode, make sure you've loaded the package with
:load, and activate the breakpoint by entering the rule name:
fregot.examples.ami_id% allow
The REPL displays the code at the breakpoint, along with the line number:
fregot.examples.ami_id% allow 23| count(unapproved_amis) == 0 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
The REPL prompt then includes the word
debug, allowing you to enter other debugging commands:
fregot.examples.ami_id(debug)%
Entering
:breakby itself displays a list of breakpoints:
repl% :break
You'll see output like this:
fregot.examples.ami_id.allow examples/ami_id/ami_id.rego:9
See Step 1: Set breakpoint for more info.
Once you've loaded a file, activated a breakpoint, and entered debugging mode, you can evaluate queries in the current context -- including printing and evaluating local variables.
For example, if you look at break_example.rego, you'll see that
function_ahas the local variable
a:
function_a { a = "Welcome to function a!" true }
If you load the Rego file and try to evaluate
awithout setting a breakpoint first, you'll get an error message that the variable is not in scope:
fregot.examples.break_example% afregot (compile error): "a" (line 1, column 1): unknown variable:
1| a ^
Undefined variable: a
However, if you set a breakpoint at
function_a, activate it, and
:stepinto it, you can see the value of
a:
fregot.examples.break_example% :break function_a Set breakpoint at fregot.examples.break_example.function_afregot.examples.break_example% function_a
4| a = "Welcome to function a!" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fregot.examples.break_example(debug)% :step
5| true ^^^^
fregot.examples.break_example(debug)% a = "Welcome to function a!"
Shortcut
:h
:helpdisplays help text for the REPL. See REPL for more information.
While in the REPL, you can directly change the input document by using the
:inputcommand to specify the path of your input document. Input can be JSON or YAML.
For example, if you want to load the input in
example.json, you would do this:
repl% :input example.json
The command returns no output unless there is an error.
After you've set the input, you can enter
inputwithout colon or argument to print the input document to the screen:
repl% input = {"user": "alice"}
Two things to note:
The home directory shortcut
~is not currently supported, so use the absolute path instead; e.g.,
/Users/alice/input.json
If you change the input document, make sure to update it by issuing the
:reloadcommand. (Or, if you've enabled
--watchmode, the REPL will reload the changes automatically!)
You can also set the input when you start the REPL with the
--input PATHflag:
fregot repl examples/ami_id/ami_id.rego --input input.json
Shortcut
:o
By default, you start in the
replpackage when you run
fregot repl.
:openallows you to switch between packages in files you've loaded. For example, you can switch to
fregot.examples.ami_idlike so:
repl% :open fregot.examples.ami_id
The REPL prompt changes to the name of the package you just loaded:
fregot.examples.ami_id%
To change to another package (or return to the
replpackage), you can run
:open [PACKAGE].
For more information, see The open package.
Shortcut
:q
:quitexits debugging mode if you're in debugging mode, and exits the REPL if you're not:
repl(debug)% :quitrepl% :quit
Shortcut
:l
:load [PATH]loads a Rego file and automatically opens the package. For example:
repl% :load examples/ami_id/ami_id.rego
You'll see output like this:
Loaded package fregot.examples.ami_id
Once the file is loaded, you can debug it with other commands. You can also enter rules or expressions to evaluate them. For example, this command returns the value of the rule
allowin the loaded Rego file:
fregot.examples.ami_id% allow
You'll see output like this:
= true
Tip: You can skip the
:loadstep by specifying the Rego file paths when you start the REPL:
fregot repl my_policy_1.rego my_policy_2.rego
Directories are searched recursively. This command opens all the Rego files in the
examplesfolder:
fregot repl examples
Note: If you change the Rego file after you've loaded it, you'll need to
:reloadit. However, if you've enabled
--watchmode, the REPL automatically reloads your changes.
Shortcut
:r
:reloadchecks for modified Rego files and reloads them. If you make changes to a loaded file, you can
:reloadit to update it in the REPL.
fregot.examples.ami_id% :reload
You'll see output like this:
Reloaded input.json Reloaded examples/ami_id/ami_id.rego
--watch.
Note that reloading when debugging is not possible as it would modify the code currently running.
:continuecontinues to the next breakpoint:
fregot.examples.ami_id(debug)% :continue
The REPL displays the code at the next breakpoint, along with the line number.
In the following example, we set a breakpoint at
test_stepand at
function_a(see break_example.rego), activate the
test_stepbreakpoint, then use the
:continuecommand:
fregot.examples.break_example% :break test_step Set breakpoint at fregot.examples.break_example.test_step fregot.examples.break_example% :break function_a Set breakpoint at fregot.examples.break_example.function_a fregot.examples.break_example% test_step 14| function_a ^^^^^^^^^^ fregot.examples.break_example(debug)% :continue 4| a = "Welcome to function a!" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If there are no more breakpoints, the program finishes running and you'll see the validation results and
(debug) finished:
(debug) = true (debug) finished
Shortcut
:s
:stepsteps into the next rule in the debugged program:
fregot.examples.break_example(debug)% :step
The REPL displays the next query, along with the line number.
In the following example, we activate the breakpoint
test_step, then use the
:stepcommand once to step into
function_a, and a second time to step into the next query in
function_a:
fregot.examples.break_example% test_step 14| function_a ^^^^^^^^^^ fregot.examples.break_example(debug)% :step 4| a = "Welcome to function a!" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ fregot.examples.break_example(debug)% :step 5| true ^^^^
If there are no more queries, you'll see the results of the validation and the output
(debug) finished:
(debug) = true (debug) finished
Shortcut
:n
:nextsteps over the next rule in the debugged program:
fregot.examples.break_example% :next
The REPL skips to the next complete rule and displays the line number.
In the following example, we activate the breakpoint
test_step, then use the
:nextcommand to jump to the next rule,
function_b:
fregot.examples.break_example% test_step 14| function_a ^^^^^^^^^^ fregot.examples.break_example(debug)% :next 15| function_b ^^^^^^^^^^
:rewindgoes back to the previous debug suspension -- it "rewinds" back to the most recent
:stepor
:nextcommand.
In the following example, we
:stepinto
function_a,
:stepinto the next query, and then
:rewindback to
function_a(the previous query):
fregot.examples.break_example(debug)% :step 4| a = "Welcome to function a!" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ fregot.examples.break_example(debug)% :step 5| true ^^^^ fregot.examples.break_example(debug)% :rewind 4| a = "Welcome to function a!" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The history that you can rewind is currently limited to 10 steps.
Shortcut
:t
:testruns tests in the current package. This is similar to the
fregot testcommand, but is scoped to a package rather than recursive directories.
For a more detailed example, check out ami_id.rego and compare it to testamiid.rego.
:testonly runs the two tests in
ami_id.rego:
fregot.examples.ami_id% :test passed: 2, failed: 0, errored: 0
On the other hand,
fregot testsearches recursively through the given directory and includes the 2 tests in
ami_id.regoand the 8 tests from
test_ami_id.regofor a total of 10 tests:
fregot test examples/ami_id/ passed: 10, failed: 0, errored: 0
:typeprints the type of a term in the loaded package:
fregot.examples.ami_id% :type allow allow : booleanfregot.examples.ami_id% :type approved_amis approved_amis : set{string}
:whereprints your location in debugging mode using a stack trace:
fregot.examples.ami_id(debug)% :where 23| count(unapproved_amis) == 0 ^^^^^^^^^^^^^^^^^^^^^^^^^^^Stack trace: rule fregot.examples.ami_id.allow at allow:1:1
This comes in handy when you are stepping into and over rules and want to double-check your location in the code.
To enable
--watchmode, you must launch the REPL with
fregot repl --watch. This also allows you to use the
:watch [expression]command.
When you launch the REPL with
fregot repl --watch, the REPL monitors loaded package and input files for changes and live-reloads them. You can also use the
:watch data.package.rulecommand to monitor an expression, and
fregotwill automatically print an updated evaluation when loaded files are changed.
Here's an example. Start the REPL with the
--watchflag:
fregot repl --watch
Load the Rego and input files:
repl% :load examples/ami_id/ami_id.rego Loading examples/ami_id/ami_id.rego... Loaded package fregot.examples.ami_id fregot.examples.ami_id% :input examples/ami_id/repl_input.json
Now you can make changes to the Rego and/or input files and
fregotautomatically reloads them:
fregot.examples.ami_id% Reloaded examples/ami_id/ami_id.rego fregot.examples.ami_id% Reloaded examples/ami_id/repl_input.json
This allows you to evaluate expressions as you like, and they'll automatically be up-to-date.
Use
:watch data.package.ruleto monitor a particular expression:
fregot.examples.ami_id% :watch data.fregot.examples.ami_id.allow
You can then make changes to the Rego and/or input file, and
fregotre-evaluates the expression and prints the evaluation:
fregot.examples.ami_id% Reloaded ami_id.rego = falsefregot.examples.ami_id% Reloaded repl_input.json = true
You can use fregot to determine whether a Terraform plan complies with a Rego policy. Incorporate
fregotinto your CI/CD pipeline to prevent noncompliant infrastructure from being deployed.
See examples/amiid/amiid.rego for details.
Want to learn more? Check out these resources: