skip to content

Rego Language

You will learn to write and test Rego: how a rule resolves over the input and data documents, why deny-by-default is idiomatic, and how opa test proves it. Interviewers ask you to sketch a deny rule.

on this pageshow

explore

questions

page 1 of 2

In Rego, what does a deny rule written as a partial set of messages produce when no input violates it?

level: juniorimportance: must knowfreq 68%

answer

  1. not a boolean
  2. one element per violation
  3. the rule is always defined
  4. clean input yields the empty set
  5. conftest looks the name up

basics

~10 s

It produces the empty set. Each successful evaluation of the rule body adds one message; when nothing matches, there are zero messages, and a runner such as conftest reports that as a pass.

solid answer

~40 s

`deny` written as a partial set is not a boolean. Every time the rule body succeeds for some binding of its variables, the message it builds is added to a set, so one run over a CI job definition can return several messages at once — one per job whose build-log retention is below the required floor. When no body evaluation succeeds, the rule still has a value: the empty set. A partial rule is always defined, so you never get an undefined error for a clean file. conftest evaluates the rules it finds by name in a namespace — `deny`, `violation` and `warn`, optionally with a trailing suffix such as `deny_retention` — and exits non-zero if any `deny` or `violation` message came back. Empty set, no messages, exit zero, gate green.

code

rego · 12 lines
rego
package ci.retention

min_days := 90

deny contains msg if {
	some job_name, job in input.jobs
	job.artifacts.retention_days < min_days
	msg := sprintf(
		"job %q keeps build logs %d days; policy requires %d",
		[job_name, job.artifacts.retention_days, min_days],
	)
}

go deeper

for a junior

Be ready to state that deny is a set of strings, that each match adds one message, and that a clean input leaves the set empty rather than undefined or false.

for a middle

Explain the mechanics: partial versus complete rules, why every variable binding is tried, and how conftest turns a non-empty deny set into a non-zero exit code.

for a senior

Show that you know an empty set is ambiguous in production — it means either compliance or that nothing evaluated — and say how you would tell the two apart.

for a principal

Own the convention itself: the ecosystem's default shape optimises for cheap contribution and readable messages, and you should be able to say what that costs in failure direction.

## Two rule shapes in Rego Rego rules come in two shapes, and the difference decides how a policy behaves when nothing happens. A **complete rule** produces a single value: `allow := true`, `max_age := 90`. If its body does not succeed, the rule is *undefined* — it has no value at all, and a query for it returns nothing. A **partial rule** produces a collection built up from every successful evaluation of its body. `deny` is conventionally written as a partial **set** of strings — one string per thing that is wrong. ```rego package ci.retention min_days := 90 deny contains msg if { some job_name, job in input.jobs job.artifacts.retention_days < min_days msg := sprintf("job %q keeps build logs %d days; policy requires %d", [job_name, job.artifacts.retention_days, min_days]) } ``` Read that as a loop rather than as an `if` statement. The engine tries every binding of `job_name`/`job` over `input.jobs`. For each binding where the comparison holds, `msg` is computed and added to the set. A pipeline file with three under-retained jobs yields three messages in one run — the rule does not stop at the first failure, which is why a developer sees the whole list instead of fixing violations one at a time. ## The value when nothing matches This is the point of the question. If no binding satisfies the body, `deny` is **not** undefined and **not** `false`. It is the **empty set**. A partial rule is always defined; the only question is how many elements it has. That matters because the whole convention rests on it: *no messages means nothing to report*, which the surrounding runner interprets as a pass. There is no separate "the policy approved this" signal — approval is the absence of complaint. ## How conftest turns that into an exit code conftest does not ask the policy a general question. It parses the input document (YAML, JSON, HCL, Dockerfile and others), then looks up rules **by name** inside a namespace, which defaults to `main` and can be selected with `--namespace` or widened with `--all-namespaces`. The names it recognises are `deny`, `violation` and `warn`, each of which may carry a trailing suffix — `deny_retention`, `warn_missing_owner` — so several independently named rules can coexist in one package. - messages from `deny` and `violation` are reported as **failures** and make the process exit non-zero; - messages from `warn` are reported as **warnings** and do not fail the run by default. So the contract a policy author is writing against is: *put a string in the `deny` set and the build fails; put nothing there and it passes.* ## Building the message Because the element of the set is whatever you put there, the message is the entire output the developer gets. `sprintf` with the offending value interpolated (`sprintf("...%d...", [days])`) is the normal way to build it. A message of `"policy violation"` technically satisfies the shape and is useless in practice. A `violation` rule can produce a structured object rather than a plain string, which is what tooling reaches for when something downstream needs to parse the result rather than print it. ## The obvious trap Since the empty set is what a passing file produces, an empty set is *also* what you get when your rule never ran — the package was renamed, the namespace was not selected, the file was not in the policy directory. Both cases look identical from the outside: zero failures, exit code zero, green check. Nothing inside a deny-set policy distinguishes "I evaluated this and it is fine" from "I evaluated nothing". That asymmetry is the reason the alternative shape — a complete `allow` rule with `default allow := false` — exists, and the reason mature policy repositories keep a deliberately non-compliant fixture that the gate is asserted to reject. ## What to say in an interview Name the shape (partial set), say that each successful body evaluation contributes one element, say the value on a clean input is the empty set rather than undefined or false, and connect it to the runner: conftest looks up `deny`/`warn` by name and turns a non-empty `deny` set into a non-zero exit code.

  • Does the rule stop at the first violation it finds?
    No. The body is evaluated for every binding of its variables, and each success contributes one element to the set. A file with five offending jobs produces five messages in a single run, so the developer sees the whole list instead of fixing one violation per pipeline attempt.
  • What is the difference between a deny message and a warn message to conftest?
    conftest reports `deny` and `violation` messages as failures and exits non-zero when any are present; `warn` messages are printed as warnings and do not fail the run by default. The rule body and the message you build are otherwise written exactly the same way.
  • If deny is a set, can the same message appear twice in one run?
    No — a set deduplicates. If two different jobs produce byte-identical message strings, only one element survives and the report shows a single line. Interpolating the offending job name or field path into the message keeps distinct violations distinct.

It is a complaints box, not a verdict. The box is emptied at the end of the run; an empty box is taken to mean everyone was happy, even if nobody was ever asked.

saying these in an interview costs you the question

  • Says a deny rule returns true or false
  • Claims deny is undefined when nothing matches
  • Thinks evaluation stops at the first violation
  • Reads a zero-failure report as proof the rule ran
  • Writes a constant message with no offending value in it

context

open as a page

In Rego, where does a rule get a fact that the artifact under evaluation does not contain?

level: juniorimportance: must knowfreq 78%

basics

~20 s

A Rego rule cannot invent a fact. It must be loaded into the engine before the query, supplied by the caller inside the query itself, or fetched during evaluation with http.send. Nothing else reaches a rule.

open as a page

Why does a Rego rule written as deny[msg] { ... } fail to parse under OPA v1?

level: juniorimportance: must knowfreq 74%

basics

~20 s

OPA v1 makes if and contains mandatory, so a partial set rule must read deny contains msg if { ... }. The bare v0 head is a parse error, not a deprecation. import rego.v1 opts a single file into v1 syntax on an older engine.

open as a page

In Rego, what is the difference between the input document and the data document?

level: juniorimportance: must knowfreq 80%

basics

~20 s

input is the document the caller sends with a single query, the thing being judged. data is the tree the engine already holds: JSON loaded alongside the policy, plus the virtual documents that rules themselves define.

open as a page

In Rego, what is the difference between an expression that is undefined and one that is false?

level: juniorimportance: must knowfreq 74%

basics

~20 s

False is a value; undefined means Rego found no value at all. Both stop a rule body, but false is an answer, while undefined leaves the rule with no result to emit and no error to report.

open as a page

In Rego, what is the difference between =, := and == in a rule body?

level: juniorimportance: must knowfreq 74%

basics

~20 s

:= declares a local variable and assigns a value to it. == compares two values that are already bound and yields true or false. = unifies: it binds whichever side is still unbound, and compares when both sides are bound.

open as a page

How does opa test decide which Rego rules are tests and whether they passed?

level: juniorimportance: must knowfreq 70%

basics

~20 s

opa test evaluates every rule whose name begins with test_, in any package it loaded. A test passes if that rule evaluates to a defined value that is not false, and fails if it is undefined or false.

open as a page

Why does a Rego policy shaped as a deny set fail open while default allow := false fails closed?

level: middleimportance: must knowfreq 57%

basics

~20 s

A deny set treats absence of messages as approval, so anything that stops the rule from matching produces a pass. A complete allow rule with default false has an explicit value of false whenever its body does not succeed, so absence of evidence becomes a denial.

open as a page

In Rego, when does object.get beat a direct lookup of a field that may be missing?

level: middleimportance: must knowfreq 68%

basics

~20 s

Use object.get whenever the field is optional and the rule must still reach a verdict. A direct reference to a missing key is undefined, so the whole rule body stops and yields nothing; object.get substitutes your default and evaluation continues.

open as a page

In Rego, what do complete, partial set and partial object rules each define?

level: middleimportance: must knowfreq 68%

basics

~20 s

A complete rule defines at most one value and is undefined when no body succeeds. A partial set rule, written with contains, collects elements; a partial object rule, written with a bracketed key, collects pairs. Partial rules are always defined.

open as a page

Why does a Rego deny rule stop denying, without erroring, when the field it reads is missing?

level: middleimportance: must knowfreq 60%

basics

~20 s

A missing key makes the reference undefined rather than false, and undefined propagates through the whole body. The rule then contributes no message, the deny set stays empty, and the gate reports a clean pass with nothing to alert on.

open as a page

In Rego, what does the wildcard _ do in a reference like input.spec.containers[_].name?

level: middleimportance: must knowfreq 60%

basics

~20 s

The underscore is an anonymous variable, so the reference iterates: OPA tries every element of the array and the expression succeeds if at least one element makes it hold. Each underscore is a separate variable whose value you cannot refer to later.

open as a page

In a Rego test, what does the with keyword replace and for how long?

level: middleimportance: must knowfreq 58%

basics

~20 s

with substitutes a document only while the single expression it is attached to is evaluated. with input as {...} swaps the whole input document, and with data.waivers as {...} swaps that one path under data. Nothing persists afterwards.

open as a page

Your Rego deny rule stopped firing after you moved its check into a helper — why?

level: seniorimportance: must knowfreq 53%

basics

~20 s

The helper is undefined for that input, and an undefined call makes the whole rule body undefined. A partial deny rule with an undefined body adds no message, so the image passes. Undefined is not false.

open as a page

A Rego advisory rule warns 'TLS policy is not approved' with no resource name — how do you find which listeners matched?

level: seniorimportance: must knowfreq 50%

basics

~20 s

Reproduce it locally with opa eval on the same template, add a print of the resource id and the value the rule reads, and read it back with --explain=notes. Then fix the message to carry that context.

open as a page

A Rego rule checking namespaces against thousands of quota records is slow — how do you find and fix it?

level: seniorimportance: must knowfreq 52%

basics

~20 s

Profile with opa eval --profile: the top row is the comparison inside the scan, its redo count near the record count. Fix the data shape — key the inventory by namespace name so the lookup is one reference.

open as a page

In Rego, when do you write a function with arguments instead of a helper rule?

level: juniorimportance: should knowfreq 58%

basics

~20 s

Use a function when one check must run over several different values: it takes arguments and evaluates per call site. Use a helper rule when the value depends only on the input, so OPA computes it once.

open as a page

In Rego, what does the walk built-in produce as it traverses a nested document?

level: juniorimportance: should knowfreq 55%

basics

~20 s

walk emits one [path, value] pair for every node in the document, starting with the root itself. The path is an array of object keys and array indexes; the value is the whole subtree sitting at that path.

open as a page

In Rego, what does print() do in a rule body and where does its output go?

level: juniorimportance: should knowfreq 58%

basics

~20 s

print() is Rego's debugging built-in. It writes its arguments to the stderr of the tool evaluating the policy and always succeeds, so adding it never changes a decision. The JSON result on stdout never contains it.

open as a page

What does opa test --coverage mark as covered in a Rego policy, and what does it not prove?

level: juniorimportance: should knowfreq 46%

basics

~20 s

opa test --coverage marks every expression the tests actually evaluated, reported per file with a percentage. That proves the policy text ran; it does not prove the rule decided correctly, and it says nothing about which inputs were tried.

open as a page

What do the timeout, raise_error and force_cache options on Rego's http.send do?

level: middleimportance: should knowfreq 55%

basics

~20 s

In Rego's http.send, timeout bounds how long evaluation waits. raise_error decides whether a failed call aborts evaluation or returns an error field. force_cache reuses a response across queries for force_cache_duration_seconds, ignoring the server's cache headers.

open as a page

How do you unit-test a Rego rule that calls http.send or reads from data?

level: middleimportance: should knowfreq 48%

basics

~20 s

Use the with keyword to replace evaluation context: with input as {...} supplies a fake document, with data.x as {...} replaces a data subtree, and with http.send as {...} mocks the built-in, so opa test runs offline and deterministically.

open as a page

In Rego, what happens when one function name has two definitions that disagree?

level: middleimportance: should knowfreq 41%

basics

~20 s

Rego allows a name to be defined twice, but a function must not produce two different values for the same arguments. When both definitions hold and disagree, OPA fails the query with a conflict error.

open as a page

In Rego, what does `not input.metadata.annotations.exempt` evaluate to if the object has no annotations?

level: middleimportance: should knowfreq 46%

basics

~20 s

It succeeds. A negated expression in Rego is satisfied whenever the inner expression is undefined or false, so a negated reference to a path that does not exist always passes — absent and explicitly-not-set look identical to the rule.

open as a page

In Rego, how do you require that every container in a list has CPU and memory limits?

level: middleimportance: should knowfreq 48%

basics

~20 s

Use the every keyword: every c in the container list, the body asserting the limits exist. The alternative is to find a counterexample with iteration and negate it. Plain iteration cannot express this, because an unbound variable means at least one element.

open as a page

What does opa build --optimize do to a policy, and what must you give it?

level: middleimportance: should knowfreq 32%

basics

~20 s

It runs partial evaluation at build time, specialising the policy for named entrypoints against the data packaged in the bundle. You must supply at least one entrypoint; without one there is nothing for the optimiser to specialise towards.

open as a page

What does OPA's partial evaluation return when you mark part of the input unknown?

level: middleimportance: should knowfreq 48%

basics

~20 s

It returns a residual policy rather than a decision. OPA evaluates every expression it can already decide, then hands back the conditions that still depend on the unknown values, plus any support rules those conditions reference.

open as a page

Your Rego deny rule skips its message for waived repositories — what does that silence cost you?

level: seniorimportance: should knowfreq 39%

basics

~20 s

A waived repository then produces output identical to a compliant one: no messages, exit zero. You lose the ability to tell an exception from a pass, to count how many waivers are live, and to notice when the rule stopped firing at all.

open as a page

OPA allowed an unencrypted volume though a Rego rule forbids it — how do you check the rule was loaded?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Separate evaluated-and-found-nothing from never-loaded. Ask the running engine which modules it holds and whether its bundle activated, then replay the exact input against that same bundle. A bundle that fails to compile is rejected whole, leaving older policy serving.

open as a page

In Rego, how would you warn on every removed apiVersion anywhere in a repo's manifest bundle?

level: seniorimportance: should knowfreq 47%

basics

~20 s

Walk the whole bundle instead of enumerating paths: for every node that is an object whose apiVersion is in your removed set, emit a warning that includes the walk path. The path is what makes the advisory list actionable.

open as a page

showing 1–30 of 43