Workflows and discovery config¶
The repository holds no engine code. Two caller workflows in .github/ invoke the
nrit-tf-pr-ops reusable workflows, and projects.yml at the root tells the engine
what to discover and which gates to run. This page covers those files plus the
CODEOWNERS and pull request template that ship alongside them.
.github/workflows/terraform-pr-ops.yml¶
The plan and apply caller. It is about thirty lines and it is stable: engine changes never need an edit here.
It triggers on three things:
pull_requestwith typesopened,synchronize,reopened, which plans every impacted unit, plusclosed, which releases the PR's cross-PR unit locks on merge or close.issue_commentwith typecreated, which is how/plan,/apply, and/unlockcomments reach the engine.workflow_dispatch, with acommandchoice input (planorapply, defaultplan) and an optionalprojectinput to target a single unit.
The permissions block is a ceiling, not a grant to this file. A reusable
workflow cannot ask for more than the caller holds, so the caller has to declare
everything the engine's jobs use:
pull-requests: write posts and edits the run comment and its reactions,
checks: write and statuses: write publish the merge-gate check, and
id-token: write mints the OIDC token for Azure. Cut any of them and the engine
fails inside the reusable workflow, not here.
The single job calls the reusable workflow:
jobs:
engine:
uses: nrit-solutions/nrit-tf-pr-ops/.github/workflows/terraform-pr-ops.yml@v1.5.0
with:
command: ${{ inputs.command || 'plan' }}
project: ${{ inputs.project || '' }}
engine_ref: v1.5.0 # must match the uses: ref above
secrets: inherit
The two pins move together¶
Every caller carries the engine version twice: the uses: ref and the engine_ref
input. uses: selects the reusable-workflow body. engine_ref selects the
engine scripts the workflow checks out at run time. Both sit at an exact
version, so a run only changes when someone commits a new one. The resolve job
logs the exact engine commit it checked out.
Bump both pins together
If the pins skew, the engine checkout does not match the reusable workflow
body. The workflow runs one version's orchestration against another version's
scripts, so the failure is a mismatch inside the run, not a clear version
error. Change uses: ...@vX and engine_ref: vX in the same commit, in both
caller files.
See The PR-ops engine for how the pin is verified at run time and what branch protection to set.
secrets: inherit¶
Both callers pass secrets: inherit. That hands the reusable workflow every
secret in the repository, with no explicit mapping and no per-secret allowlist. It
keeps the caller stable when the engine starts using a new secret, and it means the
engine sees any secret you add to the repository for an unrelated reason. Worth
knowing before you store an unrelated credential in this repository. The
Security model page covers the blast radius.
.github/workflows/drift.yml¶
The daily drift sweep. It runs on cron: "17 6 * * *", so 06:17 UTC every day, and
also on workflow_dispatch with no inputs.
Its permissions are deliberately different and narrower:
Drift reports as GitHub issues, not as a pull request comment, so it needs
issues: write and does not need pull-requests, checks, or statuses at all.
The job takes one input, engine_ref: v1.5.0, matching its own uses: ref, and it also
uses secrets: inherit.
.github/CODEOWNERS¶
Ships as a single catch-all rule:
That is the template's owner, not the customer's. Replace it with the customer's real owning teams during onboarding, or every review request routes to NRIT.
.github/pull_request_template.md¶
Three sections. What and why is free text. Scope asks for the units
impacted, the subscription or landing zone, and any version pins moved, which the
template spells out as the AVM version in main.tf, the policy library ref, and
the two engine pins.
Then a seven-item checklist:
- Plan comment reviewed for every impacted unit, including deletes and replaces
- Policy, security, and cost gate output reviewed
- If an engine pin moved, the
uses:ref andengine_refmatch in both workflows - Affected
subscription.hcl/region.hclvalues confirmed - Reviewer from the required approver group requested
/applyrun after approval and the apply succeededtf-pr-ops / merge-gateis green
The last two matter for the merge gate. It stays red until the change is applied, so applying is part of the pull request, not something that happens after merge.
projects.yml¶
This is the repository's actual discovery config, not the schema. For every field and all three discovery modes, see projects.yml schema.
terragrunt:
root: live
include_dependents: true
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"
root: live points discovery at the directory holding root.hcl.
include_dependents: true means an impacted unit pulls its downstream dependents
into the same run, so a plan shows the full effect of the change and an apply runs
in dependency order.
A unit is impacted when its own directory changed, when a file it reads changed, or
when it is downstream of a unit that is. The read-files part matters here: every
unit reads root.hcl, tenant.hcl, and the subscription.hcl and region.hcl
above it, so a one-line change to live/root.hcl impacts the whole tree. See
Plan and apply.
There is no exclude. The foundation is governed like any other unit: plan review,
required approvals, and the merge gate. Excluding it would hide the highest-impact
units from the same scrutiny, so it stays in.
The three post_plan hooks are the policy, security, and cost gates, and they run
on every plan and every drift plan. What they do and how to enforce them is on
Policy, security, and cost gates.
$TFPR_ENGINE_DIR is exported into the hook environment by the engine and points at
the engine checkout made at engine_ref. The gate scripts live in the engine, not
in this repository, so hooks resolve them there rather than in the consumer
workspace. That also means a pull request cannot edit a gate script to weaken its
own gate.
Next: The live tree.