Hook and step model¶
Hooks let you run shell commands at fixed points around init, plan, and apply. The three gates are implemented as hooks, and you can add your own (format, validate, a source scan, a notification). Hook variables use the engine's word project; in Terragrunt mode a project is exactly a unit.
The five seams¶
| Seam | Runs |
|---|---|
pre_init |
before terraform init |
post_init |
after init, before plan |
post_plan |
after plan, with the plan available |
pre_apply |
after plan, before apply |
post_apply |
after a successful apply |
Firing order:
- plan and drift:
pre_init, init,post_init, plan,post_plan - apply:
pre_init, init,post_init, plan,post_plan,pre_apply, apply,post_apply
Each command runs in the unit directory under bash -eo pipefail. A non-zero
command sets the unit's status to error, which blocks the merge gate and stops the
rest of the hooks. End a command with || true to make it advisory.
Environment exposed to hooks¶
| Variable | Meaning |
|---|---|
TFPR_PLANFILE |
The binary plan file. Available from post_plan on. |
TFPR_PLANJSON |
The plan rendered as JSON (terraform show -json). Available from post_plan on. |
TFPR_OUT |
Append markdown here to surface it in the PR comment or drift issue. |
TFPR_ENGINE_DIR |
The engine checkout root. Call bundled gates as bash "$TFPR_ENGINE_DIR/scripts/<gate>.sh". Defaults to the workspace in vendored mode. |
PROJECT_NAME, PROJECT_DIR, PROJECT_LABEL |
The unit's name, directory, and full-path label. |
TFPR_OUT is staged around the plan: output written in pre_init or post_init
renders above the plan as "Pre-plan checks", and output written from post_plan on
renders below the plan as "Additional output". So the comment always follows the
order the hooks ran. The "Additional output" panel is status-aware: it collapses
when clean and auto-expands when your hook output contains a :x: or :warning:
marker, or when the run fails. Emit those markers to have a finding surface
automatically.
The gate hooks¶
All three ship in scripts/ and are meant to run as post_plan hooks.
conftest-gate.sh¶
Tests TFPR_PLANJSON against a policy directory and appends a policy summary to
TFPR_OUT. Gating: exits non-zero on any deny.
| Variable | Default | Purpose |
|---|---|---|
CONFTEST_POLICY_DIR |
<repo>/policy |
Directory of .rego policies. |
CONFTEST_NAMESPACE |
main |
Rego namespace to test. |
CONFTEST_FAIL_ON_WARN |
unset | Set to 1 to also fail on warn. |
Passes with a note if no policies exist yet, so you can enable the hook before you write any.
checkov-gate.sh¶
Scans TFPR_PLANJSON (plan scanning, which also sidesteps Terragrunt) and appends
the failed checks to TFPR_OUT. Gating: exits with checkov's status unless
soft-fail is set.
| Variable | Default | Purpose |
|---|---|---|
CHECKOV_SOFT_FAIL |
unset | Set to 1 to report without ever gating. |
CHECKOV_HARD_FAIL_ON |
unset | Check IDs that must gate. |
CHECKOV_REPO_ROOT |
$PWD |
Enables inline checkov:skip comment support. |
CHECKOV_ARGS |
unset | Extra CLI arguments. |
infracost-gate.sh¶
Runs infracost breakdown against TFPR_PLANJSON and appends a monthly-cost table
to TFPR_OUT, labelled with the unit path. Report-only: always exits zero.
| Variable | Default | Purpose |
|---|---|---|
INFRACOST_API_KEY |
unset | Hosted pricing API key. Without it the hook notes it and skips. |
INFRACOST_BREAKDOWN_ARGS |
unset | Extra arguments for infracost breakdown. |
Writing your own hook¶
A hook is any shell command. For example, run terraform fmt -check before init and
surface the result in the comment: