{"id":18481420,"url":"https://github.com/ukhomeoffice/tf-testrunner","last_synced_at":"2025-07-26T22:34:03.170Z","repository":{"id":145372095,"uuid":"111403771","full_name":"UKHomeOffice/tf-testrunner","owner":"UKHomeOffice","description":"tf-testrunner parses Terraform configuration to Python and then runs your tests.","archived":false,"fork":false,"pushed_at":"2023-12-27T09:47:13.000Z","size":65,"stargazers_count":6,"open_issues_count":0,"forks_count":4,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-04-08T16:41:48.347Z","etag":null,"topics":["hodq","python","terraform","test-framework","testing","testing-tools"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/UKHomeOffice.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","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":"2017-11-20T11:48:52.000Z","updated_at":"2023-07-26T11:18:38.000Z","dependencies_parsed_at":"2024-11-06T12:26:44.336Z","dependency_job_id":"5d185966-72ca-4981-a273-a5e96e8a3952","html_url":"https://github.com/UKHomeOffice/tf-testrunner","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/UKHomeOffice/tf-testrunner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UKHomeOffice%2Ftf-testrunner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UKHomeOffice%2Ftf-testrunner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UKHomeOffice%2Ftf-testrunner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UKHomeOffice%2Ftf-testrunner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/UKHomeOffice","download_url":"https://codeload.github.com/UKHomeOffice/tf-testrunner/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UKHomeOffice%2Ftf-testrunner/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267253039,"owners_count":24060134,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-26T02:00:08.937Z","response_time":62,"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":["hodq","python","terraform","test-framework","testing","testing-tools"],"created_at":"2024-11-06T12:23:41.777Z","updated_at":"2025-07-26T22:34:03.138Z","avatar_url":"https://github.com/UKHomeOffice.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tf Testrunner\n[![Docker Repository on Quay](https://quay.io/repository/ukhomeofficedigital/tf-testrunner/status \"Docker Repository on Quay\")](https://quay.io/repository/ukhomeofficedigital/tf-testrunner)\n\ntf-testrunner parses [Terraform configuration](https://www.terraform.io/docs/configuration/index.html) to Python and then runs your tests.\n\nCurrent terraform upgrade tag is 32.\n\n### How it works:\n\nTestrunner automates the output of the command ```terraform plan```, saves its\noutput to a temp directory. Parses the temp file to a Python dict object and\nthen runs your test folder against it.\n\nRefer to the [examples\ndirectory](https://github.com/UKHomeOffice/tf-testrunner/tree/master/examples/basic-proof)\nfor example Terraform projects that use\n[tf-testrunner](https://github.com/UKHomeOffice/tf-testrunner/).\n\n\n## Usage\n\n### CI (Drone ~\u003e 0.5) execution\nAdd a build step\n```yaml\n  test:\n    image: quay.io/ukhomeofficedigital/tf-testrunner:32\n    commands: python -m unittest tests/*_test.py\n``````\n```shell\ndrone exec\n```\n\n### Docker (~\u003e 1.13) in-situ execution\n```shell\ndocker run --rm -v `pwd`:/mytests -w /mytests quay.io/ukhomeofficedigital/tf-testrunner:32\n```\n\n### Python (\\~\u003e 3.6.3) \u0026 Go (\\~\u003e 1.9.2) execution\n```bash\npip install git+git://github.com/UKHomeOffice/tf-testrunner.git#egg=tf-testrunner\ngo get github.com/wybczu/tfjson\n```\n\n### Test authoring\n```shell\nmkdir tests\ntouch tests/__init__.py\n```\ntests/my_test.py\n```python\n# pylint: disable=missing-docstring, line-too-long, protected-access\nimport unittest\nfrom runner import Runner\n\nclass TestMyModule(unittest.TestCase):\n    @classmethod\n    def setUpClass(self):\n        self.snippet = \"\"\"\n            provider aws {\n              region = \"eu-west-2\"\n              access_key = \"foo\"\n              secret_key = \"bar\"\n              profile = \"foo\"\n              skip_credentials_validation = true\n              skip_get_ec2_platforms = true\n              skip_requesting_account_id = true\n            }\n            module \"my_module\" {\n              source = \"./mymodule\"\n            }\n        \"\"\"\n        self.runner = Runner(self.snippet)\n        self.result = self.runner.result\n\n    def test_terraform_version(self):\n        print(self.result)\n        self.assertEqual(self.result[\"terraform_version\"], \"0.12.25\")\n\n    def test_root_module(self):\n        self.assertEqual(self.result[\"configuration\"][\"root_module\"][\"module_calls\"][\"my_module\"][\"source\"], \"./mymodule\")\n\n    def test_instance_type(self):\n        self.assertEqual(self.runner.get_value(\"module.my_module.aws_instance.foo\", \"instance_type\"), \"t2.micro\")\n\n    def test_ami(self):\n        self.assertEqual(self.runner.get_value(\"module.my_module.aws_instance.foo\", \"ami\"), \"foo\")\n\n\nif __name__ == '__main__':\n    unittest.main()\n```\nmy_module.tf\n```hcl-terraform\nresource \"aws_instance\" \"foo\" {\n  ami           = \"foo\"\n  instance_type = \"t2.micro\"\n}\n```\n\n**[More examples](examples)**\n\n## Additional Usage Method get_value\n\nTo handle the terraform output plan of json [structure](https://www.terraform.io/docs/internals/json-format.html), we are only interested in ``resource_changes`` sections with arrays of resources to be changed. Helper method  ```get_vaule``` will get first parmater of module resource name and its change value in second parameter.\nSee example snippet.\n\ntests/tf_assertion_helper_test.py\n```hcl-terraform\nimport unittest\nfrom tf_assertion_helper import get_value\n\nclass TestGetValue(unittest.TestCase):\n    def setUp(self):\n        self.snippet = {\n            \"format_version\": \"0.1\",\n            \"terraform_version\": \"0.12.25\",\n            \"planned_values\": {},\n            \"resource_changes\": [{\n                \"address\": \"module.rds_alarms.aws_cloudwatch_log_group.lambda_log_group_slack\",\n                \"module_address\": \"module.rds_alarms\",\n                \"mode\": \"managed\",\n                \"type\": \"aws_cloudwatch_log_group\",\n                \"name\": \"lambda_log_group_slack\",\n                \"provider_name\": \"aws\",\n                \"change\": {\n                    \"actions\": [\n                        \"create\"\n                    ],\n                    \"before\": \"None\",\n                    \"after\": {\n                        \"kms_key_id\": \"None\",\n                        \"name\": \"/aws/lambda/foo-lambda-slack-notprod\",\n                        \"name_prefix\": \"None\",\n                        \"retention_in_days\": 14,\n                        \"tags\": {\n                            \"Name\": \"lambda-log-group-slack-1234-apps\"\n                        }\n                    },\n                    \"after_unknown\": {\n                        \"arn\": \"blah\",\n                        \"id\": \"blah\",\n                        \"tags\": {}\n                    }\n                }\n            }]\n        }\n\n    def test_happy_path(self):\n        self.assertEqual(get_value(self.snippet, \"module.rds_alarms.aws_cloudwatch_log_group.lambda_log_group_slack\", \"retention_in_days\"), 14)\n\n    def test_unhappy_path(self):\n        self.assertNotEqual(get_value(self.snippet, \"module.rds_alarms.aws_cloudwatch_log_group.lambda_log_group_slack\", \"kms_key_id\"), \"something_not_there\")\n\n\nif __name__ == '__main__':\n    unittest.main()\n\n```\n\n\n## Additional Usage Method finder\n\nTo handle the occurrence of unique numbers in keys after parsing, use the assertion helper method ```finder```.\n\ntests/tf_assertion_helper_test.py\n```hcl-terraform\nimport unittest\nfrom runner import Runner\n\nparent = {\n    'egress.482069346.cidr_blocks.#': '1',\n    'egress.482069346.cidr_blocks.0': '0.0.0.0/0',\n    'egress.482069346.description': '',\n    'egress.482069346.from_port': '0',\n    'egress.482069346.ipv6_cidr_blocks.#': '0',\n    'egress.482069346.prefix_list_ids.#': '0',\n    'egress.482069346.protocol': '-1',\n    'egress.482069346.security_groups.#': '0',\n    'egress.482069346.self': 'false',\n    'egress.482069346.to_port': '0',\n    'id': '',\n    'ingress.#': '2',\n    'ingress.244708223.cidr_blocks.#': '1',\n    'ingress.244708223.cidr_blocks.0': '0.0.0.0/0',\n    'ingress.244708223.description': '',\n    'ingress.244708223.from_port': '3389',\n    'ingress.244708223.ipv6_cidr_blocks.#': '0'\n}\n\nclass TestFinder(unittest.TestCase):\n\n    def test_happy_path(self):\n        self.assertTrue(Runner.finder(parent, 'ingress', {'cidr_blocks.0': '0.0.0.0/0', 'from_port': '3389'}))\n\n    def test_unhappy_path(self):\n        self.assertFalse(Runner.finder(parent, 'ingress', {'cidr_blocks.0': '0.0.0.0/0', 'from_port': '0', 'self': 'true'}))\n\n\nif __name__ == '__main__':\n    unittest.main()\n```\n\n## Acknowledgements\n\n*UPDATE TF12*\n\nFollowing [tfjson](https://github.com/palantir/tfjson) is no longer support for terraform 12. This is the reason terraform 12 can only use default output of terraform plan in json format. [Use terraform show -json planned_file](https://www.terraform.io/docs/internals/json-format.html)\n\n*OLD TF11*\n\nWe leverage [tfjson](https://github.com/palantir/tfjson) to get a machine\nreadable output of the `terraform plan` which we can then evaluate against.\nWhen terraform has an inbuilt [machine readable\noutput](https://github.com/hashicorp/terraform/pull/3170), expect a refactor of\nthis tool to use that instead of tfjson.\n\nWhen researching testing strategies for Terraform, we found [Carlos Nunez](https://github.com/carlosonunez)'s article [Top 3 Terraform Testing Strategies...](https://www.contino.io/insights/top-3-terraform-testing-strategies-for-ultra-reliable-infrastructure-as-code) to be great inspiration and very informative.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fukhomeoffice%2Ftf-testrunner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fukhomeoffice%2Ftf-testrunner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fukhomeoffice%2Ftf-testrunner/lists"}