/opactl

A simple tool for converting Rego (OPA) rule into command.

Primary LanguageGoApache License 2.0Apache-2.0

opactl

opactl executes your own Rego (OPA) policy as CLI command.

This is how it works. You define a rule in OPA policy, for example rule1. Then, opactl detects your rule and turns it into subcommand such as opactl rule1. It also supports completion with tab key.

Options are supported for various usage. Also, you can preset configuration file, then opactl reads it.

Prerequisite

  • CLI opa is used in opactl. You can install opa following Running OPA.

Execute a rule as subcommand

When you define a rule filter as follows,

package opactl

# pick up only lines which includes specific mod
filter = { line |
  # load each line of stdin
  line := input.stdin[_]
  # split into words
  texts := split(texts, " ")
  # check the first word equals to parameter `mod`
  texts[0] == input.mod
}

you can run a subcommand opactl filter like this.

# Run subcommand filter with using stdin (-i) and parameter (mod=...)
ls -l | opactl -i examples filter filter -p mod="-rwxr-xr-x"
[
  "-rwxr-xr-x  1 hiroyukosaki  staff  8055840 May 12 01:04 opactl"
]

In another case, JSON can be read. (examples/filter.rego)

echo '{"orange": {"sweetness": "high"}, "cherry": {"sweetness":"middle"}}' | opactl -i examples filter json_filter -p sweetness=high
[
  "orange"
]

Last, opactl is good for use with other commands (Script examples)

Installation

Build from source

git clone https://github.com/onelittlenightmusic/opactl
cd opactl
go build
sudo cp opactl /usr/local/bin/

Enable shell completion

# bash
source <(opactl completion bash)
# zsh
opactl completion zsh > /usr/local/share/zsh/site-functions/_opactl

opactl autocompletes the subcommands.

opactl <tab>
hierarchy visibility ..(as many rules as you define)

Give it a try!

After the installation, you can learn how to use opactl with examples in the repository (example rules are located here).

opactl examples help

Options

Flags:
  -a, --all                           Show all commands
  -B, --base string                   OPA base path which will be evaluated (default "data.opactl")
  -b, --bundle strings                bundles
      --config string                 config file (default is <current directory>/.opactl)
  -d, --directory strings             directories
  -h, --help                          help for opactl
  -p, --parameter strings             Parameter (-p key=value[,key2=value2] [-p others])
  -P, --parameter-array stringArray   Array parameter (key=value1,value2 [key=value3])
  -q, --query string                  Input your own query script (example: { rtn | rtn := 1 })
  -r, --raw-output                    If the result of rule is a string,
                                      it will be written directly to stdout without quotes
  -i, --stdin                         Accept stdin as input.stdin. 
                                      Multiple lines are stored as array.
                                      JSON will be parsed and stored in input.json_stdin as well.
  -v, --verbose                       Toggle verbose mode on/off (display print() output)

Usage example)

opactl -a
# all rules should be listed.
[
  "filter",
  "hierarchy",
  "visibility"
]

Configuration

You can create an .opactl configuration file. When you run opactl command in the same directory, opactl loads the configuration and set options.

Each field in .opactl is connected to one option. For example, parameter field is read as --parameter option.

directory:
- examples
base: data.opactl
parameter:
- item=1

Define your rule

# object
get_test_object = {
  "test": "test"
}

get_first_line = rtn {
  rtn := input.stdin[0]
} else = {}
# To define default return value is strongly recommended.

# set (Kind of list. Elements are unique. No order.)
select_unique_lines[rtn] {
  rtn := input.stdin[_]
}

# array (Kind of list. Elements are not necessary unique. The order is preserved.)
lines = [rtn|
  rtn := input.stdin[_]
]

Advanced techniques

1. Shorthand of subcommand

If you have cmd1 subcommand, you can create shorthand c1 by adding the last one line.

package opactl

cmd1 = ...

c1 = cmd1

We can execute shorthand like this.

opactl c1 (= opactl cmd1)

2. Hierarchy of subcommand/subsubcommand and so on

  • Subcommand

    You can create subcommand opactl cmd1 by creating a rule cmd1.

  • Subsubcommand

    when you need subsubcommand opactl cmd2 cmd21 under the subcommand opactl cmd2, you can create a package package opactl.cmd2 and create a rule cmd21 under this package.

  • Subsubsubcommand

    We can create subsubsubcommand such as opactl cmd2 cmd22 cmd221 in the same way by creating package opactl.cmd2.cmd22.

Let's compare these hierarchical patterns.

package opactl
├── rule cmd1  (this works as `opactl cmd1`)
├── package opactl.cmd2
│   ├── rule cmd21  (this works as `opactl cmd2 cmd21`)
│   └── package opactl.cmd2.cmd22  
│       └── rule cmd221 (this works as `opactl cmd2 cmd22 cmd221`)

3. Supecial command for relative path

. and .. are supported to get all data in current path and parent path respectively.

opactl .  (= show base path data.opactl)
opactl lines .. (=show base path data.opactl, which is parent of lines)

4. Description for completion

You can define a description for each subcommand.

When you trigger subcommand completion, each option can have a description. For example, subcommand visibility has a description Example of how to use invisible field....

$ opactl <tab>
completion           -- Generate completion script
filter               -- Filter ls output with mode label (parameter mod="-rwxr-xr-x")
help                 -- Help about any command
visibility           -- Example of how to use invisible field __ and comments

Descriptions can be written as __comment under a package, or __<fieldname> next to a field. (Example)

package opactl.visibility

# A comment for subcommand "visibility" 
__comment = "Example of how to use invisible field __ and comments"

# A comment for subsubcommand "artist"
__artist = "Get data from __config field"
artist = __config.artist

# A comment for subsubcommand "song_id"
__song_id = "Get id of song"
song_id = 1

# A comment for subsubcommand "song"
__song = "Get a song specified with song_id"
song = __config.songs[song_id]

Example) Outputs of completion