You know the ritual: a change is made, Terraform plans, someone reviews it, approves it, and it gets applied. At low enough velocity, this works. The reviewer catches the odd mistakes, and everyone sleeps well.
Past a certain point, the reviewer becomes the bottleneck. Plans pile up, engineers either rush through them or let them sit, and you start losing either velocity or review quality. Often both.
Our immediate next thought is to delegate review to AI. And while you can complement your plan review with AI—the most interesting solution I’ve found in this space is Overmind↗—you cannot fully delegate plan review to it, not for production infrastructure:
- it’s non-deterministic: the same plan may pass today and fail tomorrow;
it’s non-deterministic: the same plan may pass today and fail tomorrow;
- it often violates audit/compliance requirements that mandate human sign-off with clear accountability; and critically
it often violates audit/compliance requirements that mandate human sign-off with clear accountability; and critically
- it removes responsibility from the feedback loop, no one owns the decision, which is exactly what you don’t want when something breaks.
it removes responsibility from the feedback loop, no one owns the decision, which is exactly what you don’t want when something breaks.
There’s a third option: evaluating Terraform plans programmatically and deterministically using policy-as-code. That’s what we do, with conftest↗.
conftest↗ is a policy-as-code tool built on Open Policy Agent↗. You write policies in Rego↗, feed it JSON data, and it tells you whether your data satisfies your policy.
The key insight is that Terraform can export its plan as JSON:
terraform plan -out=plan.tfplan terraform show -json plan.tfplan > plan.json
That JSON file contains every resource change Terraform intends to make: what’s being created, updated, deleted, and the before/after values of each attribute. It’s the same information a human reviewer would look at, in a structured format a policy engine—like conftest—can evaluate:
conftest test plan.json
If the plan satisfies your policy, it passes. If it doesn’t, it fails with an explicit reason. The decision is auditable, testable, and reproducible.
## An example policy
Here’s a Rego policy that only allows plans where every change is a no-op, a resource create, or a data source read. Any update or delete fails the policy:
package main import rego.v1 safe_actions := {"no-op", "create", "read"} deny contains msg if { some resource_change in input.resource_changes some action in resource_change.change.actions not action in safe_actions msg := sprintf( "resource %q has action %q, which is not in the safe set %v", [resource_change.address, action, safe_actions], ) }
This policy iterates over every resource_changes entry in the JSON-formatted Terraform plan. For each one, it checks whether all of its actions are in the safe_actions set. If any action falls outside that set (an update or a delete), the policy emits a denial with the offending resource and action.
That’s it. If this policy passes, the plan only creates new resources, reads data sources, or does nothing, so it’s safe to auto-apply. If it fails, the pipeline stops and a human reviews.
> # [Safe Terraform auto-apply with conftest](https://www.bejarano.io/terraform-autoapply/)