{"id":20334927,"url":"https://github.com/mdb/terratest-tf-plan-demo","last_synced_at":"2026-04-19T11:35:02.017Z","repository":{"id":191533595,"uuid":"684203197","full_name":"mdb/terratest-tf-plan-demo","owner":"mdb","description":"A reference example illustrating how terratest can be used to programmatically analyze Terraform plan output in a CI/CD pipeline.","archived":false,"fork":false,"pushed_at":"2023-08-31T12:14:34.000Z","size":5656,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-17T16:55:32.681Z","etag":null,"topics":["github-actions","terraform","terratest"],"latest_commit_sha":null,"homepage":"","language":"HCL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mdb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-28T16:58:38.000Z","updated_at":"2023-08-31T12:36:48.000Z","dependencies_parsed_at":"2024-11-14T20:38:40.268Z","dependency_job_id":"e58163e4-f767-4a49-b447-5a7b113247a0","html_url":"https://github.com/mdb/terratest-tf-plan-demo","commit_stats":null,"previous_names":["mdb/terratest-tf-plan-demo"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mdb/terratest-tf-plan-demo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdb%2Fterratest-tf-plan-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdb%2Fterratest-tf-plan-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdb%2Fterratest-tf-plan-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdb%2Fterratest-tf-plan-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mdb","download_url":"https://codeload.github.com/mdb/terratest-tf-plan-demo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdb%2Fterratest-tf-plan-demo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32005819,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T20:23:30.271Z","status":"online","status_checked_at":"2026-04-19T02:00:07.110Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["github-actions","terraform","terratest"],"created_at":"2024-11-14T20:38:36.201Z","updated_at":"2026-04-19T11:35:01.995Z","avatar_url":"https://github.com/mdb.png","language":"HCL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# terratest-tf-plan-demo\n\nA reference example illustrating how [terratest](https://terratest.gruntwork.io/)\ncan be used to programmatically analyze Terraform plan output in a CI/CD pipeline.\n\n## Why?\n\nTools like [OPA](https://mikeball.info/blog/terraform-plan-validation-with-open-policy-agent/)\ncan automate Terraform plan analysis via policy-as-code. Such tools seek to replace -- or at least offset --\nthe toil associated with manual plan analysis. But what if you'd prefer to write Go?\n\nTraditionally, [terratest](https://terratest.gruntwork.io/) is leveraged as a tool\nfor authoring Terraform end-to-end tests that make post-`terraform apply`\nassertions on the correctness of the resulting infrastructure.\n\nHowever, `terratest` can also be used to programmatically analyze Terraform\nplan output, effectively offering a Go-based alternative to tools like OPA and\nsimilar policy-as-code tools.\n\nThis may be especially compelling when the tests need to dynamic evaluate data\nreturned by cloud provider APIs, for example. In such instances, Go-based `terratest`\ntests can leverage technologies such as the [AWS SDK for Go](https://docs.aws.amazon.com/sdk-for-go/),\nor even one of `terratest`'s built-in modules, such as its [aws module](https://pkg.go.dev/github.com/gruntwork-io/terratest@v0.43.13/modules/aws).\n`terratest`-based Terraform plan analysis may also be especially compelling when\n`terratest` is already used as an end-to-end testing tool.\n\nExample use cases:\n\n* fail pull request CI if a Teraform change introduces a destructive action\n  against a production-critical resource\n* verify the correctness of the planned DNS record modifications during a Terraform-orchestrated\n  DNS-based blue/green deployment\n* ensure an ECR repository marked for destruction does not home OCI images used\n  by active ECR task definitions\n* \"shift left\" on detecting problematic PagerDuty Terraform edits, as some\n  [terraform-provider-pagerduty](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs) errors don't reveal themselves\n  at `plan` time; they only occur during an attempt to `apply`. For example:\n\n  ```\n  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:\n  ```\n\n  In such instances, a `terratest` test of the Terraform plan produced by a pull\n  request CI build can use the PagerDuty API to evaluate whether a user-to-be-deleted\n  is assigned open incidents, in advance of merging the pull request and applying the plan.\n\n## GitHub Actions\n\n`terratest-tf-plan-demo` offers an example of how `terratest` could be\nintegrated with a CI/CD pipeline. Its `test` directory homes a single `terratest`\ntest that fails if the Terraform plan it analyzes indicates any destructive\nactions.\n\nThe `main` branch [CI/CD pipeline](https://github.com/mdb/terratest-tf-plan-demo/actions/runs/6004252774) is composed of three passing jobs:\n\n1. :white_check_mark: [terraform-plan](https://github.com/mdb/terratest-tf-plan-demo/actions/runs/6004252774/job/16284431393) - plans the configuration.\n1. :white_check_mark: [test-terraform-plan](https://github.com/mdb/terratest-tf-plan-demo/actions/runs/6004252774/job/16284476716) - runs the `terratest` tests homed in `test/*_test.go` against the plan produced by the preceding job.\n1. :white_check_mark: [terraform-apply](https://github.com/mdb/terratest-tf-plan-demo/actions/runs/6004252774/job/16284486218) - gated by `test-terraform-plan`'s succcess, as well the configuration specifying this job only run on the `main` branch.\n\n[PR 2](https://github.com/mdb/terratest-tf-plan-demo/pull/2) introduces a change that passes [GitHub Actions CI](https://github.com/mdb/terratest-tf-plan-demo/actions/runs/6006175711), as its resulting Terraform plan includes no destructive actions. Again, all three jobs pass:\n\n1. :white_check_mark: [terraform-plan](https://github.com/mdb/terratest-tf-plan-demo/actions/runs/6006175711/job/16290249080?pr=2) - plans the configuration.\n1. :white_check_mark: [test-terraform-plan](https://github.com/mdb/terratest-tf-plan-demo/actions/runs/6006175711/job/16290272192?pr=2) - runs the `terratest` tests homed in `test/*_test.go` against the plan produced by the preceding job.\n1. :raised_hand: [terraform-apply](https://github.com/mdb/terratest-tf-plan-demo/actions/runs/6006175711/job/16290276703?pr=2) - 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).\n\nBy contrast, [PR 1](https://github.com/mdb/terratest-tf-plan-demo/pull/1) introduces a change whose Terraform plan indicates a destructive action. As such, its [GitHub Actions CI](https://github.com/mdb/terratest-tf-plan-demo/actions/runs/6006174371) fails its `test-terraform-plan` job:\n\n1. :white_check_mark: [terraform-plan](https://github.com/mdb/terratest-tf-plan-demo/actions/runs/6006174371/job/16290245262?pr=1) - plans the configuration successfully.\n1. :x: [test-terraform-plan](https://github.com/mdb/terratest-tf-plan-demo/actions/runs/6006174371/job/16290271949?pr=1) - 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.\n1. :raised_hand: [terraform-apply](https://github.com/mdb/terratest-tf-plan-demo/actions/runs/6006174371/job/16290277863?pr=1) - gated by `test-terraform-plan`'s succcess, as well the configuration specifying this job only run on the `main` branch.\n\n## Run `terratest-tf-plan-demo` locally\n\n`terratest-tf-plan-demo` assumes you've installed [tfenv](https://github.com/tfutils/tfenv) and [Go](https://go.dev/).\n\n`terratest-tf-plan-demo` also assumes [Docker](https://www.docker.com/) is installed and running.\n\n### Run `terratest-tf-plan-demo`\n\nClone `terratest-tf-plan-demo`:\n\n```\ngit clone git@github.com:mdb/terratest-tf-plan-demo.git \\\n  \u0026\u0026 cd terratest-tf-plan-demo\n```\n\nRun `localstack` to simulate AWS APIs locally:\n\n```\nmake up\n```\n\nCreate a `localstack` `terratest-demo` S3 bucket and pre-populate the bucket\nwith a `s3://terratest-demo/terraform.tfstate` object used as the the Terraform\nremote state for the demo's root module project.\n\n```\nmake bootstrap\n```\n\nRun `terraform plan` and save the plan to `plan.out`:\n\n```\nmake plan\n```\n\nUse `terraform show` to save the `plan.out` to `plan.json`:\n\n```\nmake show\n```\n\nRun the `terratest` tests against the `plan.json` file. Note the tests pass:\n\n```\nmake test\n```\n\nIntroduce a change to the Terraform configuration by renaming `null.foo` to be\n`null.foo_new_name`:\n\n```\nsed -i \"\" \"s/foo/foo_new_name/g\" main.tf\n```\n\nAfter the change, `main.tf` should look like this:\n\n```hcl\nresource \"null_resource\" \"foo_new_name\" {}\nresource \"null_resource\" \"bar\" {}\nresource \"null_resource\" \"baz\" {}\n```\n\nRun `terraform plan` and save the plan to `plan.out`:\n\n```\nmake plan\n```\n\nUse `terraform show` to save the `plan.out` to `plan.json`:\n\n```\nmake show\n```\n\nRun the `terratest` tests against the `plan.json` file. Note this time the tests\nfail, as the plan indicates a destructive action:\n\n```\nmake test\n```\n\nUndo the changes to `main.tf`:\n\n```\ngit checkout .\n```\n\nIntroduce another change to the Terraform configuration:\n\n```\necho \"resource \\\"null_resource\\\" \\\"foo_new\\\" {}\" \u003e\u003e main.tf\n```\n\nNow, `main.tf` should look like:\n\n```hcl\nresource \"null_resource\" \"foo\" {}\nresource \"null_resource\" \"bar\" {}\nresource \"null_resource\" \"baz\" {}\nresource \"null_resource\" \"foo_new\" {}\n```\n\nRun `terraform plan` and save the plan to `plan.out`:\n\n```\nmake plan\n```\n\nUse `terraform show` to save the `plan.out` to `plan.json`:\n\n```\nmake show\n```\n\nRun the `terratest` tests against the `plan.json` file. Note this time the tests\npass, as the plan no longer indicates a destructive action:\n\n```\nmake test\n```\n\nTear down `localstack` mock AWS environment:\n\n```\nmake down\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmdb%2Fterratest-tf-plan-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmdb%2Fterratest-tf-plan-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmdb%2Fterratest-tf-plan-demo/lists"}