linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/kb/Testing/terraform-compliance

kb/testing ── Testing ── intermediate

terraform-compliance: BDD checks against a plan file

terraform-compliance reads a plan file (`plan.json`) and applies BDD rules written in Gherkin. "Given a resource of type X, it must contain a property Y" reads cleanly for non-engineers and enforces policy before apply. It is an alternative to OPA/Rego for teams that prefer natural language, though it is less capable: you cannot write complex cross-resource checks.

view as markdownaka: bdd-terraform, terraform-policy-bdd

What it is and why

terraform-compliance is an open-source utility written in Python. It takes a Terraform plan in JSON and a set of .feature files written in Gherkin (the same style as Cucumber or behave). It checks each step of a feature against the plan.

Running it:

bash
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
terraform-compliance --planfile plan.json --features ./policies

The idea is that rules are written in a language the security or compliance team can read, not just an engineer. They can add policies themselves without knowing HCL.

A minimal feature file

policies/s3-buckets.feature:

gherkin
Feature: S3 buckets must be encrypted
  Scenario: Encryption is required
    Given I have aws_s3_bucket defined
    Then it must contain server_side_encryption_configuration
  Scenario: Versioning is required
    Given I have aws_s3_bucket defined
    Then it must contain versioning
      And it must contain enabled
      And its value must be true

This walks every aws_s3_bucket in the plan and checks that the rules hold. If even one bucket lacks encryption, the exit code is 1 and CI fails.

Gherkin structure for terraform-compliance

The basic steps:

StepWhat it does
Given I have <resource_type> definedFilter: consider only resources of this type.
When its <property> is "value"Extra filter, only resources with this property.
Then it must contain <key>Assertion: the resource must have this field.
Then it must not contain <key>The opposite assertion.
Then its value must be <X>Value comparison.
Then its value must match the "regex"Regex match against the value.

The extended steps:

StepWhat it does
Given I have any resource definedAll resources.
Given I have <module> action_name definedFilter by module.
Then it must have tagsA common tag check.
Then it must contain tags ([k1,k2,k3])Specific keys in the tags.

Tags in features

You can label a scenario and run a subset:

gherkin
@encryption @critical
Scenario: Encryption is required
  Given I have aws_s3_bucket defined
  Then it must contain server_side_encryption_configuration

Run only the critical ones:

bash
terraform-compliance -p plan.json -f ./policies --tags critical

CI integration

GitHub Actions:

yaml
jobs:
  plan-and-validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init
      - run: terraform plan -out=plan.tfplan
      - run: terraform show -json plan.tfplan > plan.json
      - name: Install terraform-compliance
        run: pip install terraform-compliance==1.3.50
      - name: Validate
        run: terraform-compliance -p plan.json -f ./policies/

It returns a non-zero exit on any failed scenario, so the PR turns red.

When terraform-compliance, when OPA

Aspectterraform-complianceOPA + Rego (conftest)
SyntaxGherkin, reads as EnglishRego, reads awkwardly
Learning curveLow for the rule authorNoticeable, a different language
Cross-resource checksWeak, each rule is localStrong, you can check relationships
External data (lookup)NoYes, through data
Maturity / ecosystemSmaller, narrowly focusedWider, OPA is a general policy engine
Speed on large plansNoticeably slowerFaster
Vendor supportOpen-source, has a maintainerOpen-source, CNCF, vendor support through Styra

For a small team with simple policies ("every bucket has a CostCenter tag"), terraform-compliance is quicker to get running. For mature infrastructure with dozens of complex rules, or for cross-account and cross-region logic, use OPA. See tf-policy-as-code.

Example of a grown-up policy suite

policies/
├── tagging.feature
├── encryption.feature
├── naming.feature
├── public-access.feature
└── networking.feature

tagging.feature:

gherkin
Feature: All resources must have mandatory tags
  Scenario: CostCenter tag
    Given I have any resource defined
    When it contains tags
    Then it must contain CostCenter
  Scenario Outline: Allowed environments
    Given I have any resource defined
    When it contains tags
    Then it must contain Environment
    And its value must be one of <envs>
    Examples:
      | envs                 |
      | dev,stage,prod       |

Note the Scenario Outline: rules parameterized through a table.

Pitfalls

  • It only checks what is in the plan. If a resource already exists and the plan has no changes for it, terraform-compliance does not see it. This is not a tool for auditing current state. It is a gate on new changes.

  • It does not understand expressions. tags = local.standard_tags is already expanded into a map in the plan ({"CostCenter": "foo"}), which is fine. But if a value is (known after apply), terraform-compliance has no data to check and treats the scenario as "not applicable". That is a gap a rule can slip through.

  • Competing markup. Some steps from older versions stop working in newer ones, and the documentation lags well behind the changelog. Before you adopt it, pin the version (==1.3.50) and upgrade deliberately.

  • CI integration needs a plan file. That means a pipeline of plan → show -json → terraform-compliance. If the plan is already an artifact passed between jobs (see tf-plan-apply-ci), it slots in easily. If the pipeline is linear and the plan lives only in memory, you have to rework it.

  • It does not replace static analysis of HCL. terraform validate, tflint, and checkov work on the source. terraform-compliance works on the plan. These are different layers, and you want both.

§ команды

bash
pip install terraform-compliance

Install it. In CI, pin the version: ==1.3.50.

bash
terraform plan -out=plan.tfplan && terraform show -json plan.tfplan > plan.json

Produce the JSON plan. The standard preprocessing step.

bash
terraform-compliance -p plan.json -f ./policies

Run every feature file from ./policies against plan.json.

bash
terraform-compliance -p plan.json -f ./policies --tags critical

Selectively: only scenarios tagged critical.

§ см. также

  • tf-test-frameworkNative test framework: .tftest.hcl, run, and assertSince version 1.6, Terraform ships a built-in test runner. Files named `*.tftest.hcl` describe scenarios through `run` blocks (each a mini plan or apply) and `assert` checks. The `terraform test` command runs all of them and reports pass/fail. No cloud account is required: with `command = plan` the runner evaluates expressions against plan output and creates no resources.
  • iac-testing-theoryWhat to Test in Terraform, and What to SkipInfrastructure is not an application, so do not apply the test pyramid literally. Test module contracts, business rules, complex expressions, and refactors that should produce no destroy. Do not test that the provider works, that the AWS API returns 200, or that a trivial `name = var.name` holds. The goal is to catch regressions, not to prove correctness.
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies