projects.yml schema¶
projects.yml at the repo root tells the engine what to discover and which hooks
to run. There are three discovery modes. Terragrunt mode is exclusive; the two
plain-Terraform modes can be combined.
The engine's vocabulary is project; in Terragrunt mode a project is exactly a unit, and this site uses the two words interchangeably.
Terragrunt mode¶
terragrunt:
root: live # directory that holds root.hcl
include_dependents: true # re-plan a changed unit's downstream dependents
exclude: # optional globs, relative to root
- "**/scratch/**"
tool: terragrunt # optional
The shipped configuration has no excludes, on purpose: the foundation units are the highest-impact part of the tree, and excluding anything from discovery hides it from the plan, the gates, and the merge gate.
Discovery uses native Terragrunt (terragrunt find with dependencies and the DAG).
A changed unit impacts that unit plus every downstream dependent, in topological
order, so apply runs in the right sequence.
The gate_ignore block¶
Since engine v3.4.0 the merge gate fails a PR whose changed Terraform-pattern
paths belong to no selected unit (see
the merge gate).
gate_ignore: names the paths that belong to no unit on purpose: linter
configuration, bundled tooling, a shared module tree. The globs work like
exclude (a pattern covers its subtree) but are always repo-relative, in both
modes, because the paths they cover live outside the units by definition. Keep
the list short: every entry is a place where Terraform can change without the
gate noticing.
Explicit projects (plain Terraform)¶
The label is <dir>/<name>.
Generated projects (plain Terraform)¶
generate_projects:
include:
- "environments/**"
exclude:
- "**/modules/**"
traverse_to_nested_projects: true # default true
Every folder with *.tf under an include glob (minus excludes and shared modules)
becomes a project, labeled by its path. Globs support **.
By default the walk keeps descending inside a project, so a nested folder with
its own *.tf files becomes a second project. Set
traverse_to_nested_projects: false to stop at the first project on each
branch, so a modules/ folder inside a root never becomes a project of its
own without needing an exclude glob.
The drift block¶
Scopes or disables the daily drift sweep. With no drift: block, every discovered
unit is swept.
The steps block¶
steps:
post_plan:
- bash "$TFPR_ENGINE_DIR/scripts/conftest-gate.sh"
- CHECKOV_SOFT_FAIL=1 bash "$TFPR_ENGINE_DIR/scripts/checkov-gate.sh"
- bash "$TFPR_ENGINE_DIR/scripts/infracost-gate.sh"
This flat form is the plan stage only (engine v6.0.0): the hooks run on every plan and every drift plan, and not on apply, which re-takes a plan the guard has already matched to the reviewed one. To run hooks at apply time, nest the stages:
steps:
plan:
post_plan:
- bash "$TFPR_ENGINE_DIR/scripts/conftest-gate.sh"
apply:
post_apply:
- ./scripts/notify.sh
A flat pre_apply or post_apply, or a misspelt slot, fails discovery with the
fix named.
$TFPR_ENGINE_DIR points at the engine checkout the reusable workflow provides. It
defaults to the workspace, so the same paths resolve in vendored mode.
Hooks run at fixed seams around init, plan, and apply. A global steps: applies to
every unit. An individual project may set its own steps:, which fully replaces the
global one (there is no merge). See the hook and step model.
One parser, loud failures
Since engine v1.12.0, discovery is a compiled part of the engine with a
single parser for this file. A malformed or contradictory config fails the
run with a named error rather than being silently ignored, and a
terragrunt: block that supersedes projects: or generate_projects:
says so in the run log. No Python or PyYAML is involved.