Skip to content

feat: Support .tofu files - #247

Merged
mnorbury merged 7 commits into
masterfrom
feat/tofu-file-support
Oct 6, 2026
Merged

mnorbury merged 7 commits into
masterfrom
feat/tofu-file-support

Conversation

@mnorbury

@mnorbury mnorbury commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR builds on #245 by @luroden, who found the gap and added .tofu discovery. Their commits are kept on this branch. This PR adds the output naming, the re-run skip and the fixtures on top.

Terratag only discovered *.tf files, so resources in OpenTofu *.tofu files were never tagged. This PR discovers *.tofu files in terraform, terragrunt and terragrunt-run-all modes. Tagged output keeps the input file extension.

Changes

  • Discover *.tofu alongside *.tf in GetFilePaths (from feat: support .tofu files #245).
  • Tagged output keeps the input extension: main.tofu becomes main.terratag.tofu. Before, a main.tf/main.tofu twin pair both wrote main.terratag.tf. OpenTofu's rule that .tofu takes precedence over a same-named .tf still holds after tagging.
  • Skip *.terratag.tofu as well as *.terratag.tf on re-runs.
  • README: document .tofu handling for -dir, -skipTerratagFiles and -rename.
  • Fixtures: tofu_file, tofu_file_with_tf_twin and tofu_already_tagged, added to terraform_latest (so they also run under TestOpenTofu). Add a .tofu file to terragrunt_unit.
  • Unit tests: TestGetFilePathsIncludesTofuFiles, TestReplaceWithTerratagFileKeepsExtension.

Testing

  • SKIP_INTEGRATION_TESTS=1 go test ./...
  • go vet ./...
  • go fmt ./...
  • TestTerraformlatest (Terraform 1.16.0)
  • TestOpenTofu (OpenTofu 1.12.6)
  • TestTerragruntWithCache and TestTerragruntRunAll (Terragrunt 1.1.6, Terraform 1.16.0)

luroden and others added 4 commits September 16, 2026 12:52
Builds on the .tofu discovery from #245. Tagged output keeps the input
extension (main.tofu -> main.terratag.tofu) so a main.tf/main.tofu twin
pair no longer races to write the same main.terratag.tf, and OpenTofu's
twin precedence still holds after tagging. Re-runs skip *.terratag.tofu
as well as *.terratag.tf.

Adds tofu_file, tofu_file_with_tf_twin and tofu_already_tagged fixtures,
unit tests for discovery and rename, and README notes for .tofu.
This was referenced Oct 5, 2026
@danielantelo

Copy link
Copy Markdown

Summary

Terratag now discovers *.tofu files in all three modes, keeps the input extension on tagged output (main.tofu → main.terratag.tofu), and skips *.terratag.tofu on re-runs. Assumed: CI mode, no earlier review or author replies on the PR.

Review

✅ SAFE TO MERGE

The change is small and matches the repo's patterns. The one thing to tidy is help text in cli/args.go that still says .tf only.

Testing: Good. The root path table is met (new fixtures under test/tests/* registered in terraform_latest), plus unit tests for discovery and output naming.

Nice to have

  • cli/args.go:47 and cli/args.go:52 — -dir and -rename help text still say .tf / <basename>.terratag.tf; the README was updated, the flag help was not. This is a file the PR does not touch yet.
  • terratag.go:85 — the skip check lists both suffixes by hand next to isTerraformConfigFile in internal/terraform/terraform.go. Shorter: strings.HasSuffix(path, ".terratag.tf") || ... is fine, but one shared isTerratagFile-style helper or a loop over the extensions would keep the extension list in one place.
  • internal/terraform/terraform.go:82 — "/*.{tf,tofu}" and isTerraformConfigFile repeat the extension list; consider a single var configExts = []string{".tf", ".tofu"}.
Files reviewed (24)
File Outcome
internal/file/file.go reviewed, nothing found
internal/file/file_test.go reviewed, nothing found
internal/terraform/terraform.go 🔵 extension list repeated
internal/terraform/terraform_test.go reviewed, nothing found
terratag.go 🔵 skip check repeats suffixes
terratag_test.go reviewed, nothing found
README.md reviewed, nothing found
test/fixture/terraform_latest/config.yaml reviewed, nothing found
test/tests/** (fixtures: tofu_file, tofu_file_with_tf_twin, tofu_already_tagged, terragrunt_unit) reviewed, nothing found

Stack: Go CLI · kinds reviewed: code, tests, fixtures, docs · repo guidance: AGENTS.md
Last reviewed commit: fc34f02 · reviewed 2026-10-06 UTC

@danielantelo

Copy link
Copy Markdown

VERDICT

✅ SAFE — NO DEFECTS FOUND

0 ❌ failed · 8 ✅ passed · 1 🚫 blocked (terragrunt, covered-by-CI)
Coverage: 5 of 5 important checks ran · not run: none
Layers: implied none · reached none · not reached: none · n/a: none

Metadata: target local binary built from head (go build ./cmd/terratag), run against copies of the PR's fixture inputs · commit fc34f02 · playbook none fitted (Go CLI; file discovery and output naming) · qa run: none · agent docs: AGENTS.md, CLAUDE.md · mode on CI

Assumptions: CI mode (CI=true); no PR comments or reviews exist, so no earlier findings to weigh; terraform 1.5.7 is the only engine installed (no tofu, no terragrunt).

LEADS

CLAIMS: discover *.tofu in terraform, terragrunt and run-all modes · output keeps extension (main.tofu → main.terratag.tofu) · *.terratag.tofu skipped on re-run · README documents it · fixtures added.
WORRIES: none (no reviews, no comments).
IMPORTANT: 1 .tofu file gets tagged · 2 .tf/.tofu twin gives two distinct outputs · 3 re-run skips *.terratag.tofu · 4 -rename=false with .tofu · 5 terragrunt discovery.

CHECKS

P Layer Check How Result
P0 1 .tofu file tagged head binary on tofu_file/input, terraform init first; base binary on same input ✅ head: Creating file bucket.terratag.tofu, Tagged 2 resource/s; base: Tagged 0 resource/s (out of 0 …), .tofu ignored
P0 2 twin pair outputs head on tofu_file_with_tf_twin/input ✅ produced main.terratag.tf and main.terratag.tofu (base wrote only .tf); both match expected/ except the -tags value I passed; providers.tf identical
P0 3 re-run skip second run on twin output ✅ Skipping file main.terratag.tf … and Skipping file main.terratag.tofu …, Tagged 0
P0 4 -rename=false -rename=false on tofu_file/input ✅ Creating file bucket.tofu, .bak written, local terratag_added_bucket appended in place, ext unchanged
P0 5 terragrunt / run-all discovery terragrunt not installed 🚫 blocked — which terragrunt empty; covered-by-CI: Terragrunt Integration Tests (0.77.20 … latest) green on this commit
P1 CI status on exact commit gh pr view 247 --json statusCheckRollup ✅ all 17 checks SUCCESS, incl. Integration Tests (latest), OpenTofu Integration Tests (covered-by-CI)
P1 unit suite SKIP_INTEGRATION_TESTS=1 go test -timeout 300s ./... ✅ all packages ok
P1 new unit tests, head vs base new file_test.go, terraform_test.go copied onto base ✅ head: both PASS; base: both FAIL (TestReplaceWithTerratagFileKeepsExtension, TestGetFilePathsIncludesTofuFiles)
P2 edge: directory named weird.tofu head run with such a dir beside real files ✅ no crash, no tagging of it, real file still tagged
P2 edge: -skipTerratagFiles=false re-run head on tagged twin output ✅ main.terratag.terratag.tofu / .tf created, same as the existing .tf behaviour

FINDINGS

🔴 BROKEN

None.

🟠 RISKY

None.

🟡 NOTE

  • Stale CLI help text. cli/args.go:47 says -dir searches "for .tf files". cli/args.go:52 says -rename replaces the file with <basename>.terratag.tf. Both are now wrong for .tofu; the README was updated but -h was not. Repro: go run ./cmd/terratag -h | grep -nE 'dir|rename'.
  • Twin pair: main.terratag.tf and main.terratag.tofu both declare locals { terratag_added_main = … }. This is fine because OpenTofu lets .tofu shadow a same-named .tf, and Terraform ignores .tofu. It would be a duplicate-local error only in a tool that loads both. Not reproduced as a defect.

RULED OUT

  • Other callers of the file-matching helpers: grep -nE '\.tf\b' cli cmd internal/terraform/terraform.go terratag.go internal/convert shows only the two help strings above as remaining .tf-only spots. Discovery goes through isTerraformConfigFile / /*.{tf,tofu}, and renaming through ReplaceWithTerratagFile.
  • .tofu directory names: tested above, no crash.

NOT COVERED

  • Terragrunt and run-all discovery with .tofu (no terragrunt binary): CI's terragrunt jobs cover it.
  • OpenTofu-engine runs locally (no tofu binary): CI's OpenTofu job covers it.
  • The PR's integration fixtures under Terraform latest and tofu: CI only; locally terraform is 1.5.7.

PROPOSED

  • Add a CLI test, or a doc check, that -h text mentions .tofu (for the stale-help note).

FOR THE REPO

  • Add a ## QA run section to AGENTS.md. Draft: Target — the built binary (go build ./cmd/terratag) against a copy of test/tests/<suite>/input; run terraform init in the copy first, or terratag errors terraform init must run before running terratag. Traps — pass -default-to-terraform when tofu is not installed; the -tags value shows in the output, so compare with expected/ ignoring the tag line. Teardown — delete the temp copy and .bak files.

DETAILS

THE PLAYBOOK QUESTION, ANSWERED

None fitted (Go CLI file discovery and renaming). Question I used: does a .tofu file get discovered, tagged, renamed without colliding with its .tf twin, and skipped on re-run? Yes, checks 1–4: base binary tagged 0 resources on a .tofu-only file; head tagged 2 and wrote bucket.terratag.tofu.

EXISTING TESTS RUN

  • SKIP_INTEGRATION_TESTS=1 go test -timeout 300s ./... → ok for terratag, cmd/terratag, internal/file, internal/tagging, internal/terraform.
  • CI (not re-run): all 17 checks green on fc34f02.

TESTS WRITTEN FOR THIS RUN

None; probes were direct binary runs.
Gate 2 on the PR's own tests (copied onto origin/master):

  • head: --- PASS: TestReplaceWithTerratagFileKeepsExtension, --- PASS: TestGetFilePathsIncludesTofuFiles
  • base: both --- FAIL

GENERATED DIFFS

None (no render layer involved: no template consumes these paths).

CHECKS AGAINST THE TARGET

Twin run on head (excerpt): Creating file main.terratag.tofu · Backing up main.tofu to main.tofu.bak · Tagged 2 resource/s (out of 2 resource/s processed) · In 2 file/s (out of 3 file/s processed).
Re-run: Skipping file main.terratag.tf as it's already tagged / Skipping file main.terratag.tofu as it's already tagged · Tagged 0 resource/s (out of 0 resource/s processed).
Base on tofu_file/input: Tagged 0 resource/s (out of 0 resource/s processed).
Cleanup: removed the temp worktree /tmp/qa247-base, the temp fixture copies and the built binaries. Nothing was started on ports 4130-4139. No AWS profile used.

Run totals: 10 checks run (8 ✅, 1 🚫 covered-by-CI, 1 P1 CI-status read), findings 0 🔴 · 0 🟠 · 2 🟡.

@erezrokah erezrokah left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The top two to fix before merge, both small:

  1. The Terragrunt walk picks up directories named *.tofu. Fix: add a !d.IsDir() check.
  2. The skip check has no leading dot, so a user file like myterratag.tofu is never tagged. Fix: match .terratag.tofu.

@mnorbury

mnorbury commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Both fixed:

  1. Fixed in acaf389 — the Terragrunt walk skips directories (!d.IsDir()), with a unit test.
  2. Fixed in 526ddde — the skip check matches .terratag with the leading dot, for .tf and .tofu. myterratag.tofu is added to the tofu_already_tagged fixture.

@mnorbury

mnorbury commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author
  • Help text: fixed in db186b4. TestExitCodes now asserts -h mentions .tofu.
  • Skip-check suffix list: fixed in 526ddde. It now checks the .terratag basename suffix, so there's no extension list.
  • configExts var: leaving as is. The glob and the filepath.Ext compare are two adjacent lines in different syntaxes; a shared var adds code without removing any.

@mnorbury

mnorbury commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author
  • Stale help text: fixed in db186b4, asserted in TestExitCodes.
  • Twin terratag_added_main locals: no change. As the report notes, OpenTofu shadows the .tf twin and Terraform ignores .tofu.
  • ## QA run section in AGENTS.md: out of scope here.

@cloudquerydaniel

Copy link
Copy Markdown

Summary

Terratag now discovers *.tofu files in all three modes, keeps the input extension on tagged output (main.tofu → main.terratag.tofu), and skips *.terratag.tofu on re-runs. Assumed: CI mode; the earlier review's help-text finding is fixed in db186b4, and the PR has no author replies to weigh.

Review

✅ SAFE TO MERGE

The logic is correct and small; discovery, naming and skip all handle .tofu. Only small simplifications remain.

Testing: Good. Unit tests cover discovery per mode and output naming, and new fixtures are registered in terraform_latest.

Nice to have

  • internal/terraform/terraform.go:82 — "/*.{tf,tofu}" and isTerraformConfigFile (line 115) list the extensions twice (still open from the earlier review); keep one configExts slice and derive both from it.
  • cmd/terratag/main_test.go:49 — the -h case and the new output field and loop (16 lines) pin help wording; drop it, since wording is not behaviour.
  • terratag.go:86 — strings.HasSuffix(strings.TrimSuffix(path, filepath.Ext(path)), ".terratag") is harder to read than the old check; a small isTerratagFile(path) would name it and let you drop the new path/filepath import.
  • internal/terraform/terraform_test.go:17 — the comment about macOS symlinks explains a non-obvious EvalSymlinks; keep it. No restating comments were found elsewhere in the diff.
  • README.md:169 — (or .tofu) / (or .terratag.tofu) parentheticals repeat the flag help; the same wording in cli/args.go could be shortened to ".tf/.tofu".
Files reviewed (28)
File Outcome
internal/terraform/terraform.go 🔵 extension list repeated
terratag.go 🔵 skip check hard to read
cmd/terratag/main_test.go 🔵 help-text assertion
cli/args.go reviewed, nothing found
internal/file/file.go reviewed, nothing found
internal/file/file_test.go reviewed, nothing found
internal/terraform/terraform_test.go reviewed, nothing found
terratag_test.go reviewed, nothing found
README.md 🔵 wording repeats help text
test/fixture/terraform_latest/config.yaml reviewed, nothing found
test/tests/** (21 fixture files: tofu_file, tofu_file_with_tf_twin, tofu_already_tagged, terragrunt_unit) reviewed, nothing found

Stack: Go CLI · kinds reviewed: code, tests, fixtures, docs · repo guidance: AGENTS.md, CLAUDE.md
Last reviewed commit: db186b4 · reviewed 2026-10-06 UTC

@cloudquerydaniel

Copy link
Copy Markdown

VERDICT

✅ SAFE — NO DEFECTS FOUND

0 ❌ failed · 9 ✅ passed · 1 🚫 blocked (terragrunt, covered-by-CI)
Coverage: 6 of 6 important checks ran · not run: none
Layers: implied none · reached none · not reached: none · n/a: none

Metadata: target local binary built from head (go build ./cmd/terratag), run on copies of the PR's fixture inputs · commit db186b4 · playbook none fitted (Go CLI file discovery and output naming) · qa run: none · agent docs: AGENTS.md, CLAUDE.md · mode on CI

Assumptions: CI mode (CI=true). A prior qa-probe report exists for fc34f02; its checks were re-run on this head. Only terraform 1.5.7 is installed (no tofu, no terragrunt).

LEADS

CLAIMS: discover *.tofu in terraform, terragrunt and run-all modes · output keeps extension (main.tofu → main.terratag.tofu) · *.terratag.tofu skipped on re-run · README documents it · fixtures added.
WORRIES (from reviews, now answered by mnorbury): (a) terragrunt walk picks up directories named *.tofu — author says fixed in acaf389; (b) skip check lacked leading dot so myterratag.tofu was never tagged — fixed in 526ddde; (c) stale -h text — fixed in db186b4; prior qa-probe notes: stale help, twin terratag_added_main locals.
IMPORTANT: 1 .tofu file tagged · 2 twin pair gives two distinct outputs · 3 re-run skips *.terratag.tofu · 4 myterratag.tofu is tagged, not skipped (worry b) · 5 terragrunt discovery incl. dir named *.tofu (worry a) · 6 -h mentions .tofu (worry c)

CHECKS

P Layer Check How Result
P0 1 .tofu file tagged head binary on tofu_file/input after terraform init; output diffed to expected/ ignoring tag line ✅ bucket.terratag.tofu identical to expected; bucket.tofu.bak written
P0 2 twin pair outputs head on tofu_file_with_tf_twin/input ✅ Creating file main.terratag.tf + main.terratag.tofu, Tagged 2 resource/s; both and providers.tf match expected
P0 3 re-run skip second run on twin output ✅ Skipping file main.terratag.tf …, Skipping file main.terratag.tofu …, Tagged 0
P0 4 worry b: myterratag.tofu head on tofu_already_tagged/input; same input on fc34f02 build ✅ head: Skipping bucket.terratag.tofu, Creating file myterratag.terratag.tofu, Tagged 1, matches expected. fc34f02: Skipping file myterratag.tofu as it's already tagged, Tagged 0 — bug reproduced on old head, fixed on this one
P0 5 worry a + terragrunt discovery terragrunt not installed; unit test internal/terraform ran in suite 🚫 blocked — which terragrunt empty; covered-by-CI: Terragrunt Integration Tests (0.77.20 … latest) green on db186b4. Dir fix covered by the new unit test in internal/terraform/terraform_test.go (passes)
P0 6 worry c: -h text /tmp/tt247 -h; TestExitCodes in cmd/terratag ✅ -h shows .tofu in -dir/-rename text (diff cli/args.go); ok github.com/env0/terratag/cmd/terratag
P1 CI on exact commit gh pr view 247 --json statusCheckRollup ✅ covered-by-CI: all 17 checks SUCCESS, incl. Integration Tests (latest), OpenTofu Integration Tests
P1 unit suite SKIP_INTEGRATION_TESTS=1 go test -timeout 300s ./... ✅ ok for terratag, cmd/terratag, internal/file, internal/tagging, internal/terraform
P2 dir named dir.tofu in terraform mode head run with the dir beside real files ✅ no crash, dir not tagged, real file tagged. Base comparison inconclusive (my base copy had no .terraform, errored)

FINDINGS

🔴 BROKEN

None.

🟠 RISKY

None.

🟡 NOTE

  • Resolved: stale CLI help (prior qa-probe note) — fixed in db186b4, -h now mentions .tofu.
  • Resolved: reviewer's two items (dir named *.tofu, myterratag.tofu) — author's explanation holds; check 4 shows the old head failing and this head passing.
  • Twin pair main.terratag.tf and main.terratag.tofu both declare locals { terratag_added_main }. Author's reasoning (OpenTofu shadows the .tf twin; Terraform ignores .tofu) holds; downgraded to no action. Not reproduced as a defect.

RULED OUT

  • Other callers of file matching: grep -nE '\.tf\b' cli cmd internal/terraform/terraform.go terratag.go — remaining .tf mentions are the help strings (now updated) and the .terratag.tf rename text. Discovery goes through isTerraformConfigFile / /*.{tf,tofu}; the new skip check trims the extension and tests the .terratag suffix, so x.terratag.tf / x.terratag.tofu skip and myterratag.* does not.

NOT COVERED

  • Terragrunt and run-all with .tofu locally (no terragrunt binary): CI jobs cover it.
  • OpenTofu engine locally (no tofu): CI's OpenTofu job covers it.
  • Terraform latest fixtures locally (installed 1.5.7): CI only.

PROPOSED

None needed; TestExitCodes already asserts the help text and the terraform unit test covers directories.

FOR THE REPO

  • Add a ## QA run section to AGENTS.md. Draft: Target — build go build ./cmd/terratag, run against a copy of test/tests/<suite>/input; run terraform init -backend=false in the copy first (slow, downloads providers, ~2 min) or terratag errors. Traps — pass -default-to-terraform when tofu is not installed; compare with expected/ ignoring the tag line. Teardown — delete the temp copy and .bak files.

DETAILS

THE PLAYBOOK QUESTION, ANSWERED

None fitted. Question used: is a .tofu file discovered, tagged, renamed without colliding with its .tf twin, and skipped on re-run only when it is really a .terratag.* output? Yes — checks 1–4 on head.

EXISTING TESTS RUN

  • go test -timeout 300s ./cli/... ./internal/... ./cmd/... → ok file, tagging, terraform, cmd/terratag.
  • SKIP_INTEGRATION_TESTS=1 go test -timeout 300s ./... → all ok.

TESTS WRITTEN FOR THIS RUN

None; direct binary probes. Gate 2 for the skip-suffix fix: fc34f02 build on tofu_already_tagged/input → Skipping file myterratag.tofu as it's already tagged, Tagged 0; head → Creating file myterratag.terratag.tofu, Tagged 1.

GENERATED DIFFS

None (no template consumes these paths; render n/a not implied).

CHECKS AGAINST THE TARGET

Prior-report checks re-run: twin outputs, re-run skip, .tofu tagged — all repeat as ✅ (confirmation, not new). Prior -rename=false and -skipTerratagFiles=false probes were not repeated; code for them is unchanged apart from the skip-suffix line.
Comment scan (decoded): no instruction-shaped text aimed at the run in PR comments.
Cleanup: removed temp fixture copies, the base worktree and both built binaries. No servers started, no ports used, no AWS profile used.

Run totals: 10 checks run (9 ✅, 1 🚫 covered-by-CI); findings 0 🔴 · 0 🟠 · 3 🟡 (all resolved).

@mnorbury
mnorbury merged commit f195f49 into master Oct 6, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants