The package and individual rules in a module can be annotated with a rich set of metadata.
# METADATA
# title: My rule
# description: A rule that determines if x is allowed.
# authors:
# - John Doe <john@example.com>
# entrypoint: true
allow {
...
}
Annotations are grouped within a metadata block, and must be specified as YAML within a comment block that must start with # METADATA
.
Also, every line in the comment block containing the annotation must start at Column 1 in the module/file, or otherwise, they will be ignored.
Annotations
Name | Type | Description |
---|---|---|
scope | string; one of package , rule , document , subpackages | The scope on which the schemas annotation is applied. Read more here. |
title | string | A human-readable name for the annotation target. Read more here. |
description | string | A description of the annotation target. Read more here. |
related_resources | list of URLs | A list of URLs pointing to related resources/documentation. Read more here. |
authors | list of strings | A list of authors for the annotation target. Read more here. |
organizations | list of strings | A list of organizations related to the annotation target. Read more here. |
schemas | list of object | A list of associations between value paths and schema definitions. Read more here. |
entrypoint | boolean | Whether or not the annotation target is to be used as a policy entrypoint. Read more here. |
custom | mapping of arbitrary data | A custom mapping of named parameters holding arbitrary data. Read more here. |
Scope
Annotations can be defined at the rule or package level. The scope
annotation in
a metadata block determines how that metadata block will be applied. If the
scope
field is omitted, it defaults to the scope for the statement that
immediately follows the annotation. The scope
values that are currently
supported are:
rule
- applies to the individual rule statement (within the same file). Default, when metadata block precedes rule.document
- applies to all of the rules with the same name in the same package (across multiple files)package
- applies to all of the rules in the package (across multiple files). Default, when metadata block precedes package.subpackages
- applies to all of the rules in the package and all subpackages (recursively, across multiple files)
Since the document
scope annotation applies to all rules with the same name in the same package
and the package
and subpackages
scope annotations apply to all packages with a matching path, metadata blocks with
these scopes are applied over all files with applicable package- and rule paths.
As there is no ordering across files in the same package, the document
, package
, and subpackages
scope annotations
can only be specified once per path.
The document
scope annotation can be applied to any rule in the set (i.e., ordering does not matter.)
Example
# METADATA
# scope: document
# description: A set of rules that determines if x is allowed.
# METADATA
# title: Allow Ones
allow {
x == 1
}
# METADATA
# title: Allow Twos
allow {
x == 2
}
Title
The title
annotation is a string value giving a human-readable name to the annotation target.
Example
# METADATA
# title: Allow Ones
allow {
x == 1
}
# METADATA
# title: Allow Twos
allow {
x == 2
}
Description
The description
annotation is a string value describing the annotation target, such as its purpose.
Example
# METADATA
# description: |
# The 'allow' rule...
# Is about allowing things.
# Not denying them.
allow {
...
}
Related Resources
The related_resources
annotation is a list of related-resource entries, where each links to some related external resource; such as RFCs and other reading material.
A related-resource entry can either be an object or a short-form string holding a single URL.
Object Related-resource Format
When a related-resource entry is presented as an object, it has two fields:
ref
: a URL pointing to the resource (required).description
: a text describing the resource.
String Related-resource Format
When a related-resource entry is presented as a string, it needs to be a valid URL.
Examples
# METADATA
# related_resources:
# - ref: https://example.com
# ...
# - ref: https://example.com/foo
# description: A text describing this resource
allow {
...
}
# METADATA
# related_resources:
# - https://example.com/foo
# ...
# - https://example.com/bar
allow {
...
}
Authors
The authors
annotation is a list of author entries, where each entry denotes an author.
An author entry can either be an object or a short-form string.
Object Author Format
When an author entry is presented as an object, it has two fields:
name
: the name of the authoremail
: the email of the author
At least one of the above fields are required for a valid author
entry.
String Author Format
When an author entry is presented as a string, it has the format { name } [ "<" email ">"]
;
where the name of the author is a sequence of whitespace-separated words.
Optionally, the last word may represent an email, if enclosed with <>
.
Examples
# METADATA
# authors:
# - name: John Doe
# ...
# - name: Jane Doe
# email: jane@example.com
allow {
...
}
# METADATA
# authors:
# - John Doe
# ...
# - Jane Doe <jane@example.com>
allow {
...
}
Organizations
The organizations
annotation is a list of string values representing the organizations associated with the annotation target.
Example
# METADATA
# organizations:
# - Acme Corp.
# ...
# - Tyrell Corp.
allow {
...
}
Schemas
The schemas
annotation is a list of key value pairs, associating schemas to data values.
In-depth information on this topic can be found here.
Schema Reference Format
Schema files can be referenced by path, where each path starts with the schema
namespace, and trailing components specify
the path of the schema file (sans file-ending) relative to the root directory specified by the --schema
flag on applicable commands.
If the --schema
flag is not present, referenced schemas are ignored during type checking.
# METADATA
# schemas:
# - input: schema.input
# - data.acl: schema["acl-schema"]
allow {
access := data.acl["alice"]
access[_] == input.operation
}
Inlined Schema Format
Schema definitions can be inlined by specifying the schema structure as a YAML or JSON map.
Inlined schemas are always used to inform type checking for the eval
, check
, and test
commands;
in contrast to by-reference schema annotations, which require the --schema
flag to be present in order to be evaluated.
# METADATA
# schemas:
# - input.x: {type: number}
allow {
input.x == 42
}
Entrypoint
The entrypoint
annotation is a boolean used to mark rules and packages that should be used as entrypoints for a policy.
This value is false by default, and can only be used at rule
or package
scope.
The build
and eval
CLI commands will automatically pick up annotated entrypoints; you do not have to specify them with
--entrypoint
.
Custom
The custom
annotation is a mapping of user-defined data, mapping string keys to arbitrarily typed values.
Example
# METADATA
# custom:
# my_int: 42
# my_string: Some text
# my_bool: true
# my_list:
# - a
# - b
# my_map:
# a: 1
# b: 2
allow {
...
}
Accessing annotations
Rego
In the example below, you can see how to access an annotation from within a policy.
Given the input:
{
"number": 11,
"subject": {
"name": "John doe",
"role": "customer"
}
}
The following policy
package example
# METADATA
# title: Deny invalid numbers
# description: Numbers may not be higher than 5
# custom:
# severity: MEDIUM
output := decision {
input.number > 5
annotation := rego.metadata.rule()
decision := {
"severity": annotation.custom.severity,
"message": annotation.description,
}
}
will output
{
"output": {
"message": "Numbers may not be higher than 5",
"severity": "MEDIUM"
}
}
If you’d like more examples and information on this, you can see more here under the Rego policy reference.
Inspect command
Annotations can be listed through the inspect
command by using the -a
flag:
opa inspect -a
Go API
The ast.AnnotationSet
is a collection of all ast.Annotations
declared in a set of modules.
An ast.AnnotationSet
can be created from a slice of compiled modules:
var modules []*ast.Module
...
as, err := ast.BuildAnnotationSet(modules)
if err != nil {
// Handle error.
}
or can be retrieved from an ast.Compiler
instance:
var modules []*ast.Module
...
compiler := ast.NewCompiler()
compiler.Compile(modules)
as := compiler.GetAnnotationSet()
The ast.AnnotationSet
can be flattened into a slice of ast.AnnotationsRef
, which is a complete, sorted list of all
annotations, grouped by the path and location of their targeted package or -rule.
flattened := as.Flatten()
for _, entry := range flattened {
fmt.Printf("%v at %v has annotations %v\n",
entry.Path,
entry.Location,
entry.Annotations)
}
// Output:
// data.foo at foo.rego:5 has annotations {"scope":"subpackages","organizations":["Acme Corp."]}
// data.foo.bar at mod:3 has annotations {"scope":"package","description":"A couple of useful rules"}
// data.foo.bar.p at mod:7 has annotations {"scope":"rule","title":"My Rule P"}
//
// For modules:
// # METADATA
// # scope: subpackages
// # organizations:
// # - Acme Corp.
// package foo
// ---
// # METADATA
// # description: A couple of useful rules
// package foo.bar
//
// # METADATA
// # title: My Rule P
// p := 7
Given an ast.Rule
, the ast.AnnotationSet
can return the chain of annotations declared for that rule, and its path ancestry.
The returned slice is ordered starting with the annotations for the rule, going outward to the farthest node with declared annotations
in the rule’s path ancestry.
var rule *ast.Rule
...
chain := ast.Chain(rule)
for _, link := range chain {
fmt.Printf("link at %v has annotations %v\n",
link.Path,
link.Annotations)
}
// Output:
// data.foo.bar.p at mod:7 has annotations {"scope":"rule","title":"My Rule P"}
// data.foo.bar at mod:3 has annotations {"scope":"package","description":"A couple of useful rules"}
// data.foo at foo.rego:5 has annotations {"scope":"subpackages","organizations":["Acme Corp."]}
//
// For modules:
// # METADATA
// # scope: subpackages
// # organizations:
// # - Acme Corp.
// package foo
// ---
// # METADATA
// # description: A couple of useful rules
// package foo.bar
//
// # METADATA
// # title: My Rule P
// p := 7
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.