🚀 Enhancements
fix(output): stop after-* hooks from corrupting backend.tf.json when backend uses !terraform.state @zack-is-cool (#2358)
## SummaryFixes #2356. The after-terraform-apply store hook path regenerated
backend.tf.json / providers_override.tf.json from un-rendered
component sections when the backend referenced !terraform.state,
overwriting a correctly-rendered file with literal YAML-function strings:
- "bucket": "atmos-tfstate-dev",
- "dynamodb_table": "atmos-tfstate-lock-dev",
+ "bucket": "!terraform.state tfstate-backend dev s3_bucket_id",
+ "dynamodb_table": "!terraform.state tfstate-backend dev dynamodb_table_name",The hook then failed its tofu output call with:
Error: Backend initialization required: please run "tofu init"
Reason: Backend configuration block has changed
Why
Regression introduced in v1.216.0 by #2309 (commit 3c0e748ce) +
follow-up commit c7ef142a9 ("fix: skip-init should skip yaml function
evaluation"). c7ef142a9 added a guard disabling YAML-function
evaluation when SkipInit && authManager == nil to avoid failing on
auth-requiring functions in the post-hook context. The guard is overly
broad — it also disables evaluation of non-auth functions like
!terraform.state — so sections returned from DescribeComponent retain
literal YAML-function strings. execute() then extracts config.Backend
from those sections and writes them to disk via GenerateBackendIfNeeded.
Fix
Thread processYamlFunctions bool through execute() in
pkg/terraform/output/executor.go and guard the artifact-regeneration
block (Step 4 / Step 5) behind it. When YAML functions were not
evaluated upstream, execute() must not regenerate artifacts from the
un-rendered sections. The backend file on disk from the init/apply phase
is already correct; leaving it alone is always safe. Output reading
(tofu output) still works via the on-disk state.
Minimal, localized diff — four commits:
refactor(output): inject BackendGenerator and thread processYamlFunctions through execute()— pure DI plumbing, no behavior change.fix(output): skip artifact regeneration when YAML functions were not processed— the actual guard.test(output): assert backend-generator calls match processYamlFunctions in SkipInit tests— locks in the invariant in four existing SkipInit tests.test(output): regression test for #2356 backend.tf.json corruption— byte-identical integration assertion.
Test plan
- New unit test
TestExecutor_Execute_SkipsArtifactRegen_WhenYamlFunctionsNotProcessed(demonstrably red before the guard, green after). - Four existing SkipInit tests strengthened with zero-call expectations on the backend-generator mock.
- Inverse assertion in
TestExecutor_GetAllOutputs_SkipInit_WithAuthManager_ProcessesYamlFunctions:GenerateBackendIfNeeded+GenerateProvidersIfNeededcalled exactly once when auth is present. - Integration regression test
TestExecutor_Regression_Issue2356_BackendFileUnchangedInSkipInitPath: writes a renderedbackend.tf.json, drivesGetOutputWithOptions(SkipInit=true, authManager=nil), asserts the file is byte-identical. Fails without the guard; passes with it. -
go test ./pkg/terraform/output/... -count=1green. -
make lint/./custom-gcl run --new-from-rev=origin/mainclean (oneduplwarning on the new test vs the existing SkipInit test is suppressed with//nolint:dupl+ justification — they test contrasting invariants at the same call site; extracting shared scaffolding would obscure the red/green comparison). - Manual end-to-end via LocalStack + Redis repro (
ignore/issues/post-apply-hook-backend-racecondition/repro.shin the branch, referenced from #2356). Exits 0 withFIX VERIFIEDon this branch; backend file byte-diff is empty after the after-apply hook. - CI full suite — opening this PR runs it.
Follow-up
The processYamlFunctions = false guard in GetOutputWithOptions /
fetchAndCacheOutputs is the deeper design issue — auth availability
should not gate evaluation of non-auth YAML functions. Tracked in #2357.
This PR is the minimal regression fix for v1.216.x.
Release
fix:conventional commit → patch release (v1.216.1).- No schema changes, no user-facing config changes.
- No roadmap update (regression fix, not a feature).
Summary by CodeRabbit
-
Bug Fixes
- Backend and provider override files are regenerated only when YAML functions are processed, preventing unnecessary rewrites.
- Fixed a case where skip-initialization could overwrite already-rendered backend/provider files, preserving existing configurations.
-
Tests
- Added regression tests to ensure backend/provider files remain unchanged in the skip-initialization path and to validate correct conditional regeneration behavior.