{"slug": "terraform-and-opentofu-a-ci-checklist-for-ai-generated-infra", "title": "Terraform and OpenTofu: A CI Checklist for AI-Generated Infra", "summary": "Masterpoint's Matt Gowie published a five-part CI checklist for validating AI-generated Terraform and OpenTofu infrastructure code, covering formatting and validation, linting, tests, security scanning, and documentation. The checklist recommends running terraform fmt or tofu fmt and terraform validate or tofu validate, using TFLint with rules such as terraform_unused_declarations, and writing native HCL tests for reusable child modules, with each check run locally and in CI on every pull request. Gowie notes validation requires an initialized working directory with referenced modules and provider plugins installed, and that passing validation does not prove a deployment will succeed with particular credentials, input values, or existing cloud resources.", "body_md": "Published: 9.24.2026\nTerraform and OpenTofu: A Continuous Integration Checklist for AI-Generated Infrastructure\nBy Matt Gowie\nRun formatting and validation, linting, tests, security scans, and documentation checks locally and in CI for Terraform and OpenTofu.\nAI makes Terraform and OpenTofu code faster to produce. That doesn’t remove the need to validate every change before it ships. It raises the stakes for consistent validation: more code can reach review in less time, and every change still has to meet the same standard.\nIf you’re using AI for your infrastructure code, you should be using it to shorten the validation loop. It can help write tests, update documentation, diagnose clearly reported problems, and revise the code. At Masterpoint, we put five automated checks around that work: formatting and validation, linting, tests, security scanning, and documentation. Run them locally while you work and in continuous integration (CI) on every pull request. They catch routine failures so engineers can focus on the decisions that still need human judgment: whether the infrastructure design makes sense and behaves as intended.\nUse the checklist below as a starting point for your own organization. It’s the five-part baseline we follow at Masterpoint, and each section explains what the check catches, how to run it, and what passing still doesn’t prove.\n\nDownload as PDF\n1. Formatting and validation: check the configuration\n\n#\nFormatting is an easy one. Use \nterraform fmt\n or \ntofu fmt\n to apply the tool’s canonical format, then have CI check the committed files. That keeps formatting differences out of code review so reviewers can focus on the infrastructure change.\nterraform validate\n or \ntofu validate\n checks internal consistency, including references and argument types. A reference to an undeclared resource should be caught before a reviewer starts reasoning about the design.\nTerraform’s validation command\n requires an initialized working directory with the referenced modules and provider plugins installed. Account for that setup in CI. A repository with several root modules needs validation in the relevant module directories. Running one command from the repository root doesn’t automatically validate everything underneath it.\nBe aware that validation doesn’t establish that a deployment will succeed with particular credentials, input values, or existing cloud resources.\n2. Linting: make the team’s rules explicit\n\n#\nA linter can enforce additional rules your team has chosen for its code. We recommend \nTFLint\n.\nA good example of a TFLint rule that showcases why it’s useful is the \nterraform_unused_declarations\n rule\n. This rule flags variables, data sources, locals, and provider aliases declared in code but never used. For an unused input variable, changing the value won’t change the module’s behavior. With TFLint pointing this out, you can connect the variable to the intended behavior or remove an option the code doesn’t need. Review unused data sources separately: Terraform still refreshes them even when nothing references their results.\nThe rules should be chosen deliberately, with their configuration kept in the repository. Provider-specific checks also depend on the appropriate plugins. Installing TFLint alone doesn’t mean every cloud-specific rule is running.\nIf the team regularly ignores a rule, resolve why: fix the code, adjust the rule, or document a justified exception. Otherwise, every engineer has to remember which warnings require action and which ones the team has decided to ignore.\n3. Tests: check the behavior people depend on\n\n#\nTesting takes more work to get right. Start with reusable child modules, especially where you’ve written custom logic. Several callers may depend on that behavior, so write down the expectation in a test before someone changes it.\nTerraform\n and \nOpenTofu\n both provide native testing capabilities. You can describe inputs and expected behavior in HCL alongside the module. Cover a normal use case, invalid inputs the module should reject, and failure cases you’ve learned matter.\nFor a separate illustration of what a test can protect, consider a small AWS S3 child module for a bucket that should stay private. The monorepo template linked below uses a simpler Random module example. The S3 module accepts a bucket name and enables all four S3 Block Public Access settings. Three cases give us a useful baseline:\nCase\nExpected result\nValid bucket name\nThe configuration can produce a plan with all four public-access controls enabled\nInvalid bucket name\nThe module rejects the input\nA public-access control is disabled via a future request\nThe test fails on the planned configuration to ensure this is not done lightly\nThe test uses a mocked AWS provider so it can inspect the planned configuration without AWS credentials or cloud resources:\n\n```\nmock_provider \"aws\" {}\n\nrun \"private_bucket\" {\n  command = plan\n\n  variables {\n    bucket_name = \"example-private-bucket\"\n  }\n\n  assert {\n    condition = (\n      aws_s3_bucket_public_access_block.this.block_public_acls &&\n      aws_s3_bucket_public_access_block.this.block_public_policy &&\n      aws_s3_bucket_public_access_block.this.ignore_public_acls &&\n      aws_s3_bucket_public_access_block.this.restrict_public_buckets\n    )\n    error_message = \"The module must enable all four S3 Block Public Access settings.\"\n  }\n}\n\nrun \"reject_invalid_name\" {\n  command = plan\n\n  variables {\n    bucket_name = \"INVALID\"\n  }\n\n  expect_failures = [var.bucket_name]\n}\n```\n\nThe first run protects a real infrastructure decision: a future refactor can’t quietly turn off one of the module’s public-access controls. The second run expects the module’s bucket-name validation to reject uppercase input. If that validation disappears, the test fails because the expected rejection never occurs. An unrelated provider or configuration error doesn’t satisfy \nexpect_failures\n.\nHow the tests run matters. Provider mocking needs a CLI release that supports it. Terraform added \nmock_provider\n in 1.7. Native test runs default to an apply operation, but using the above \ncommand = plan\n avoids creating infrastructure for that run. This example still needs the AWS provider plugin so Terraform or OpenTofu can load its schema, but the mock avoids credentials and API calls. Other plan-only tests may still need provider configuration or API access. Use mocks where appropriate, and use live infrastructure tests when you need evidence about behavior that a plan or mock cannot establish. Those tests need their own credentials, isolation, and cleanup.\nAI can help generate tests, but a human still needs to decide which behavior matters and verify that the tests cover it. In \nour test-generation experiments\n, the team had to refactor generated tests that added code without meaningful coverage. A useful test fails when the behavior it protects is broken. If changing that behavior doesn’t produce a failure, the test needs more work.\n4. Security: surface misconfigurations and exposed secrets\n\n#\nSecurity checks are useful even when compliance isn’t the reason for running them. If a database is meant to stay private, a rule exposing it to the public internet should be caught before merge. A useful baseline covers two different failure modes: infrastructure misconfigurations and exposed secrets. A network rule and a committed credential require different checks, even when one tool can run both.\nTrivy\n can scan infrastructure code for misconfigurations, while \nTruffleHog\n detects exposed secrets. \nCheckov\n is another option for infrastructure scanning. Whichever tools you choose, verify which modes and rules actually run and surface the results on the pull request.\nIt’s worth calling out that the scanners themselves are another dependency to maintain: Trivy was affected by a \nsupply-chain compromise in March 2026\n, so scanner versions need the same maintenance and review diligence as the rest of your tooling.\nOnce those scans are running in CI, the team needs to decide which findings block merge and how exceptions are reviewed. A passing scan means the configured rules found no violations. Human review still determines whether the proposed access and exposure are appropriate for the environment.\n5. Documentation: keep the module interface readable\n\n#\nDocumentation is part of a module’s interface. When an input or output changes, the reference tables and working example need to change with it. Otherwise, engineers and AI assistants can both rely on instructions the module no longer supports.\nterraform-docs\n is the go-to tooling for generating reference documentation from module code. Regenerate it locally when the interface changes via a pre-commit check, and have a CI check fail if the committed documentation differs from the expected output.\nGenerated tables still need an explanation around them: what the module is for, how to use it, and which assumptions or tradeoffs a consumer needs to understand. The generator can describe an input, but you need to explain why someone would choose it.\nPut the checklist into practice\n\n#\nIf you’re looking for a concrete starting point, \nMasterpoint’s infrastructure monorepo template\n is the example we’re using for this checklist. Compare its setup against the five checks above, then adapt it to your modules and team conventions.\nWe really like \nTrunk for coordinating these checks\n. At Masterpoint, we use it to run supported checks through local pre-commit hooks and as a CI check on pull requests. Configure those checks there, and make sure the workflow also runs native tests in the intended module directories.\nHere are links to locations where you can find each of the checks above:\nFormatting and validation: Trunk uses \nOpenTofu for formatting\n in this template. The separate validation command described above still needs to be configured for the module directories you want to check.\nLinting: \nTrunk configures TFLint as a linter\n.\nTests: See our \nexample tests\n and \nGitHub Actions test runner\n.\nSecurity: \nTrunk configures Trivy and TruffleHog as linters\n.\nTerraform Docs: We run \nterraform-docs as a Trunk action for local pre-commit changes\n and run a \nseparate documentation check in CI\n to confirm that the committed documentation is up to date before merge.\nThe way we do things is that we like to avoid a lot of problems via checks that are run before the commit is even made. The separate \npre-commit framework\n is another way to organize checks, but we like Trunk because it doesn’t require it. If you’re already using that framework successfully, keep using it. If you’re starting without a consistent setup, we recommend Trunk.\nAn engineer new to the repository might forget to run a local check, or they might not have Trunk set up locally. CI needs to catch that. We use our standard \nlint.yaml\n GitHub Actions workflow\n for the Trunk checks, with separate jobs for native tests and documentation freshness. To enforce the full checklist, configure the relevant status checks as required through branch protection or rulesets. A failure in any required check then blocks the merge, subject to the bypass permissions your team has configured.\nA deliberate failure is the final setup check. An unused input, a broken test expectation, or a variable description change without regenerated documentation should trigger a failure on the corresponding check and prevent you from merging. Make sure that you run into at least one failed check during your setup of the above before you start relying on a green result.\nKeep tool versions aligned\n\n#\nThe CI tools and workflows running these checks need regular maintenance. For example, if an engineer starts using a feature from a newer OpenTofu release while CI still runs an older version, the code can work locally and fail on the pull request. Keeping those versions aligned helps engineers reproduce the same checks on their machines. Updates to the tools and workflows should go through review and testing so you can confirm the checks still behave as intended.\nTools like \nmise\n and \nAqua\n let you define tool versions in the repository so developers and CI can use the same versions. Our infrastructure monorepo template uses Aqua for this: its \naqua.yaml file\n pins Terraform, OpenTofu, and terraform-docs, and the test and documentation workflows install those versions. That gives the team one place to update them and review the change.\nUse automated checks to focus human review\n\n#\nThe reviewer still needs to decide whether a resource should exist, whether access is appropriate, and whether the plan matches the intended change. Use the above to give them reliable feedback from the routine checks so they can concentrate on those decisions.\n👋 \nIf your team needs help establishing these standards across Terraform or OpenTofu repositories, \nget in touch with Masterpoint\n.\n We can help you turn the above checklist into a pattern your team can reliably use and maintain.", "url": "https://wpnews.pro/news/terraform-and-opentofu-a-ci-checklist-for-ai-generated-infra", "canonical_source": "https://masterpoint.io/blog/terraform-opentofu-ci-checklist/", "published_at": "2026-09-26 14:43:18+00:00", "updated_at": "2026-09-26 15:01:16.807743+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "mlops", "ai-infrastructure"], "entities": ["Terraform", "OpenTofu", "Matt Gowie", "Masterpoint", "TFLint", "HCL"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/terraform-and-opentofu-a-ci-checklist-for-ai-generated-infra", "markdown": "https://wpnews.pro/news/terraform-and-opentofu-a-ci-checklist-for-ai-generated-infra.md", "text": "https://wpnews.pro/news/terraform-and-opentofu-a-ci-checklist-for-ai-generated-infra.txt", "jsonld": "https://wpnews.pro/news/terraform-and-opentofu-a-ci-checklist-for-ai-generated-infra.jsonld"}}