Write a policy in Rego
Write release policies in Rego: the three policy styles, the result contract, tests, and how Prodgator runs your code.
Availability
Writing policies in Rego
A policy can be written in three ways:
- Form: form rules only.
- Mixed: form rules plus custom rules written in Rego. Each custom rule is its own module in and defines .
- Rego: one or more modules you write yourself, in , defining .
Prodgator compiles every policy with Open Policy Agent (OPA) when you save it. A policy that does not compile is not saved.
The result contract
In a Rego policy, is a list of objects:
| Field | Type | Meaning |
|---|---|---|
| string | A short name for the rule. | |
| string | , , or . | |
| boolean | Whether a rejects the deployment. | |
| string | Shown on the deployment and in the GitHub comment (first 300 characters). | |
| object | Optional details, stored with the evaluation. |
A result with any other , or a value that is not a list, counts as .
package prodgator.policy
branch_status := "pass" if input.release.branch == "main"
else := "fail"
results := [{
"rule": "main_only",
"status": branch_status,
"blocking": true,
"reason": sprintf("deploying from %v", [input.release.branch]),
}]Example: block self-approval
This policy fails a release when the only approval comes from the person who started the deployment. It compares with (the same object as ) by .
package prodgator.policy
other_approvals := [a |
some a in input.approvals
a.decision == "approved"
a.prodgatorUserId != input.actor.prodgatorUserId
]
self_status := "pass" if count(other_approvals) > 0
else := "fail"
results := [{
"rule": "no_self_approval",
"status": self_status,
"blocking": true,
"reason": "needs an approval from someone other than the person who started the deployment",
}]Example: a custom rule that reads attestations
In a mixed policy, a custom rule lives in its own module in and defines . This one passes when the Prodgator report action sent a trusted attestation named for the commit. Attestations come from ; see Release input document for every field. Send a custom attestation shows the CI side.
package prodgator.custom.change_ticket
approved := [a |
some a in input.attestations
a.kind == "custom"
a.name == "change-ticket"
a.trusted
a.status == "pass"
]
result := {
"status": "pass",
"blocking": true,
"reason": "change ticket approved",
} if count(approved) > 0
result := {
"status": "fail",
"blocking": true,
"reason": "no approved change ticket attestation for this commit",
} if count(approved) == 0Tests
A Rego policy can include test modules, with file names ending in . Prodgator runs them with on every save and refuses the save if a test fails.
package prodgator.policy_test
import data.prodgator.policy
test_main_passes if {
policy.results[0].status == "pass" with input as {"release": {"branch": "main"}}
}
test_feature_branch_fails if {
policy.results[0].status == "fail" with input as {"release": {"branch": "feature/x"}}
}What Rego can use
Policies read only the input document described below. , the built-ins and are not available, and a policy that uses them does not compile. Size, time and memory limits are listed under Limits.
How Prodgator runs your Rego
- Policies compile in a separate function that has no access to Prodgator's database and can only write compiled policies to storage. OPA runs there without the function's AWS credentials in its environment.
- , the built-ins and are removed from OPA's capabilities, so a policy or test that calls them does not compile. Policies read only the input document.
- The compiler can reach the internet (it does not run in a private network), but with network built-ins removed and no credentials, Rego has no way to use that access.
- Compiled policies run as WebAssembly in a separate thread with a time and memory limit. A policy that exceeds a limit is stopped and its result is , which holds an enforce policy's deployment (wait) rather than approving it.
The input a policy reads is described in Release input document.