Skip to content

mdb/terratest-tf-plan-demo

Repository files navigation

terratest-tf-plan-demo

A reference example illustrating how terratest can be used to programmatically analyze Terraform plan output in a CI/CD pipeline.

Why?

Tools like OPA can automate Terraform plan analysis via policy-as-code. Such tools seek to replace -- or at least offset -- the toil associated with manual plan analysis. But what if you'd prefer to write Go?

Traditionally, terratest is leveraged as a tool for authoring Terraform end-to-end tests that make post-terraform apply assertions on the correctness of the resulting infrastructure.

However, terratest can also be used to programmatically analyze Terraform plan output, effectively offering a Go-based alternative to tools like OPA and similar policy-as-code tools.

This may be especially compelling when the tests need to dynamic evaluate data returned by cloud provider APIs, for example. In such instances, Go-based terratest tests can leverage technologies such as the AWS SDK for Go, or even one of terratest's built-in modules, such as its aws module. terratest-based Terraform plan analysis may also be especially compelling when terratest is already used as an end-to-end testing tool.

Example use cases:

  • fail pull request CI if a Teraform change introduces a destructive action against a production-critical resource

  • verify the correctness of the planned DNS record modifications during a Terraform-orchestrated DNS-based blue/green deployment

  • ensure an ECR repository marked for destruction does not home OCI images used by active ECR task definitions

  • "shift left" on detecting problematic PagerDuty Terraform edits, as some terraform-provider-pagerduty errors don't reveal themselves at plan time; they only occur during an attempt to apply. For example:

    Error: DELETE API call to https://api.pagerduty.com/users/12345 failed 400 Bad Request. Code: 0, Errors: [The user cannot be deleted as they have 1 incident. Please resolve the following incident to continue.], Message:
    

    In such instances, a terratest test of the Terraform plan produced by a pull request CI build can use the PagerDuty API to evaluate whether a user-to-be-deleted is assigned open incidents, in advance of merging the pull request and applying the plan.

GitHub Actions

terratest-tf-plan-demo offers an example of how terratest could be integrated with a CI/CD pipeline. Its test directory homes a single terratest test that fails if the Terraform plan it analyzes indicates any destructive actions.

The main branch CI/CD pipeline is composed of three passing jobs:

  1. terraform-plan - plans the configuration.
  2. test-terraform-plan - runs the terratest tests homed in test/*_test.go against the plan produced by the preceding job.
  3. terraform-apply - gated by test-terraform-plan's succcess, as well the configuration specifying this job only run on the main branch.

PR 2 introduces a change that passes GitHub Actions CI, as its resulting Terraform plan includes no destructive actions. Again, all three jobs pass:

  1. terraform-plan - plans the configuration.
  2. test-terraform-plan - runs the terratest tests homed in test/*_test.go against the plan produced by the preceding job.
  3. terraform-apply - gated by test-terraform-plan's succcess, as well the configuration specifying this job only run on the main branch (the workflow is running against a non-main branch so this job doesn't run).

By contrast, PR 1 introduces a change whose Terraform plan indicates a destructive action. As such, its GitHub Actions CI fails its test-terraform-plan job:

  1. terraform-plan - plans the configuration successfully.
  2. test-terraform-plan - runs the terratest tests homed in test/*_test.go against the plan produced by the preceding job. The tests fail in this case, because the plan introduces a destructive action.
  3. terraform-apply - gated by test-terraform-plan's succcess, as well the configuration specifying this job only run on the main branch.

Run terratest-tf-plan-demo locally

terratest-tf-plan-demo assumes you've installed tfenv and Go.

terratest-tf-plan-demo also assumes Docker is installed and running.

Run terratest-tf-plan-demo

Clone terratest-tf-plan-demo:

git clone git@github.com:mdb/terratest-tf-plan-demo.git \
  && cd terratest-tf-plan-demo

Run localstack to simulate AWS APIs locally:

make up

Create a localstack terratest-demo S3 bucket and pre-populate the bucket with a s3://terratest-demo/terraform.tfstate object used as the the Terraform remote state for the demo's root module project.

make bootstrap

Run terraform plan and save the plan to plan.out:

make plan

Use terraform show to save the plan.out to plan.json:

make show

Run the terratest tests against the plan.json file. Note the tests pass:

make test

Introduce a change to the Terraform configuration by renaming null.foo to be null.foo_new_name:

sed -i "" "s/foo/foo_new_name/g" main.tf

After the change, main.tf should look like this:

resource "null_resource" "foo_new_name" {}
resource "null_resource" "bar" {}
resource "null_resource" "baz" {}

Run terraform plan and save the plan to plan.out:

make plan

Use terraform show to save the plan.out to plan.json:

make show

Run the terratest tests against the plan.json file. Note this time the tests fail, as the plan indicates a destructive action:

make test

Undo the changes to main.tf:

git checkout .

Introduce another change to the Terraform configuration:

echo "resource \"null_resource\" \"foo_new\" {}" >> main.tf

Now, main.tf should look like:

resource "null_resource" "foo" {}
resource "null_resource" "bar" {}
resource "null_resource" "baz" {}
resource "null_resource" "foo_new" {}

Run terraform plan and save the plan to plan.out:

make plan

Use terraform show to save the plan.out to plan.json:

make show

Run the terratest tests against the plan.json file. Note this time the tests pass, as the plan no longer indicates a destructive action:

make test

Tear down localstack mock AWS environment:

make down

About

A reference example illustrating how terratest can be used to programmatically analyze Terraform plan output in a CI/CD pipeline.

Topics

Resources

Stars

Watchers

Forks