In Rego, when do you write a function with arguments instead of a helper rule?
answer
- arguments or no arguments
- one value per input versus per call
- only one is addressable as data
- three containers, one check
basics
~20 sUse 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.
solid answer
~40 sA helper rule such as `approved_bases := {...}` or `image_labels := input.image.config.Labels` is a named value in the package's document — it takes no arguments, is computed once for a given input, and can be read at `data.<package>.<name>`. A function such as `is_approved_base(img)` takes arguments and only exists at its call sites; it is not addressable as a data path. So the test is simple: if I need the same logic applied to three different values — the app image, an init-container image, a sidecar image — I write a function and call it three times. If the answer is fixed for this input, a helper rule is clearer and avoids re-deriving it. Both can be undefined, and both are how you keep a `deny` rule body down to a couple of readable lines.
go deeper
Be ready to state the mechanical difference out loud: a function takes arguments and is called, a rule does not and is a named value in the package. Then give one example of each from a policy you have written.
An interviewer expects you to explain why a function is not addressable as a data path, and to pick the right construct for a check that must run over every container in a spec rather than over one field.
Show the refactoring judgment: which logic becomes a named helper so the decision rule stays readable, and where a parameter set beats hard-coded comparisons when a policy's allowed values change without a logic change.
Own the convention across a policy estate. Decide what shape helpers take so rules stay reviewable by the teams they block, and so a check can be reused rather than re-implemented slightly differently in each package.
## Two ways to name a piece of logic Rego gives you two constructs for factoring a policy: **rules** and **functions**. They look similar on the page and behave very differently. ### Rules A rule assigns a name inside the package's virtual document: ``` approved_bases := {"registry.internal/base/distroless", "registry.internal/base/ubi9"} has_base_label if { input.image.config.Labels["org.opencontainers.image.base.name"] } ``` `approved_bases` is a complete rule holding a set. `has_base_label` is a complete rule whose value is `true` when its body holds and which is *undefined* when it does not. Both are addressable: query `data.image.base.approved_bases` and you get the set back. Both are derived from `input` and `data` only — a rule takes no parameters, so for one input document it has one value, and OPA evaluates it once and reuses the result. ### Functions A function takes arguments: ``` is_approved_base(img) if { img.config.Labels["org.opencontainers.image.base.name"] in approved_bases } ``` The call site supplies the value: ``` deny contains msg if { some c in input.spec.containers not is_approved_base(c.image_config) msg := sprintf("container %q is not built from an approved base", [c.name]) } ``` A function is **not part of the document tree**: there is no value at `data.image.base.is_approved_base`, because there is no answer until you say *for which image*. It exists only where it is called. ### Choosing between them The question to ask is "does this value depend on anything other than the input document?" - **One fixed answer per input** — the list of approved bases, the image config of the single pod being admitted, a normalised copy of a field. Write a **helper rule**. It reads better at the call site, and it is directly inspectable when you are debugging a decision, because you can evaluate the rule on its own. - **The same check over several values** — every container in a pod spec, every layer in an image history, every resource in a Terraform plan. Write a **function** and call it once per value. Copying the rule three times with three different field paths is the smell this construct exists to remove. ### The lookup-table pattern A very common Rego idiom is a helper *rule* holding a set, used as a membership test rather than a function: ``` approved_bases := {"registry.internal/base/distroless", "registry.internal/base/ubi9"} # at the call site not base in approved_bases ``` This is preferred over a function that runs a chain of comparisons, because the data — the policy's *parameters* — is separated from the logic, and a reviewer can see the allowed values in one line. ### Things that are true of both - **Either can be undefined.** A helper rule whose body does not hold produces no value; a function whose body does not hold produces no value for that call. That undefined-ness travels to whatever used it, which is the single most common surprise when a policy is refactored into helpers. - **Either can have several definitions.** Rego lets you define the same name more than once. For a set rule (`deny contains msg`) the definitions union together; for a complete rule or a function the definitions must agree on a value, or evaluation fails. - **Neither is a procedure.** There is no order of execution to reason about, no early return, no mutation. A helper is a *definition*, not a step. ### A small style note Keep the rule that produces the decision — the `deny` set or the `allow` boolean — short enough to read in one breath, and push field walking, normalisation and comparison into named helpers. The names then double as documentation: `not is_approved_base(c.image_config)` says what the policy means, where three lines of label indexing say only what it does.
- Can you query a Rego function directly the way you query a rule?No. A rule's value sits in the package document, so `data.image.base.approved_bases` returns it. A function needs arguments, so it has no value until a call supplies them and it does not appear in the document. When you want to inspect a function's behaviour you evaluate an expression that calls it with a concrete value.
- Why is a set held in a helper rule often better than a function full of comparisons?It separates the policy's parameters from its logic. The allowed values sit in one place a reviewer can scan and an owner can edit, and membership is a single expression at the call site. A function chaining `or`-style comparisons hides the same data inside control flow and grows a line every time the list changes.
A helper rule is a value you compute once and leave on the shelf; a function is a tool you hand a different part to each time you pick it up.
saying these in an interview costs you the question
- Says a function is just a rule with a shorter name
- Expects to read a function's value at a data path
- Copies one rule three times instead of parameterising it
- Treats a helper as a procedure that runs in order