Describe the Feature
atmos terraform generate planfile currently bypasses the atmos ci integration entirely. The command writes a JSON/YAML representation of a Terraform plan to disk, but unlike atmos terraform plan/apply/deploy, it does not fire any CI hooks. As a result, none of the CI features that already exist for plan/apply — job summary, CI outputs, planfile upload to storage, GitHub commit status / check run, or PR comments — are available when a user uses generate planfile in CI.
This issue tracks wiring generate planfile into the same hook lifecycle that plan already uses, so that running it in CI produces the same artifacts and surface area.
Expected Behavior
When atmos terraform generate planfile -s <stack> <component> runs inside CI (or with --ci / ATMOS_CI=true):
- A
before.terraform.generate.planfile hook fires before the command runs.
- The generated JSON/YAML planfile (and, optionally, the underlying binary
*.tfplan produced internally) is uploaded to the configured planfile store (S3 / GitHub Artifacts / local) via the same Components.Terraform.Planfiles configuration used by plan.
- A job summary is written using a
generate-planfile.md template (or aliased to plan.md) under pkg/ci/plugins/terraform/templates/.
- CI outputs (
stack, component, has_changes, resource counts, etc.) are emitted via the platform's output writer.
- A commit status / check run is created and updated with the result.
- Optionally, a PR comment is posted using the same template.
- An
after.terraform.generate.planfile hook fires on success or failure (matching the runHooks / runHooksOnErrorWithOutput pattern from cmd/terraform/plan.go).
Use Case
Today, teams that want a JSON-shaped plan artifact in CI (for audit, OPA/Conftest policy evaluation, manual review, etc.) have to either:
- Run
atmos terraform plan and post-process the canonical binary planfile, or
- Run
atmos terraform generate planfile and then separately invoke atmos terraform planfile upload and re-create their own summary/status logic.
The first path forces a binary-then-JSON two-step; the second path duplicates the CI plumbing that already exists for plan. Wiring generate planfile into the hook lifecycle gives users a single, declarative CI integration that works regardless of which terraform subcommand is being driven.
Describe Ideal Solution
Minimal changes:
- Add
BeforeTerraformGeneratePlanfile / AfterTerraformGeneratePlanfile to pkg/hooks/event.go.
- Bind the new events in
pkg/ci/plugins/terraform/plugin.go GetHookBindings(), reusing onBeforePlan / onAfterPlan semantics. Adjust uploadPlanfile to either:
- upload the JSON/YAML output as an additional
FileEntry alongside the binary plan.tfplan, or
- upload it under a distinct key dimension (e.g.
kind=plan-json) so it does not collide with the binary planfile produced by terraform plan.
- Stop deleting the binary
-out file when --ci is active in internal/exec/terraform_generate_planfile.go (currently removed by defer os.RemoveAll(tmpDir)), so the upload step has something to ship.
- Wrap
RunE in cmd/terraform/generate/planfile.go with the same PreRunE / PostRunE runHooks / runHooksWithOutput pattern used by cmd/terraform/plan.go.
- Add a
--ci flag (mirroring cmd/terraform/plan.go:129-132) with pkg/ci.IsCI() autodetection, and tee terraform stdout/stderr through ansi.Strip for templates.
- Add a
generate-planfile.md template (or alias to plan.md) under pkg/ci/plugins/terraform/templates/.
- Cover the new events in
pkg/ci/executor_test.go and cmd/terraform/utils_hooks_test.go (parallel to the existing runCIHooksForPlanComponent coverage).
Alternatives Considered
- Document a two-command workflow — keep
generate planfile as-is and tell users to run atmos terraform planfile upload after. Rejected because it duplicates CI plumbing (summary/checks/comments are not produced) and forces users to re-implement what already exists for plan.
- Promote
generate planfile JSON to a sub-resource of the plan artifact — emit it only as a side effect of atmos terraform plan with a --json / --also-emit-json flag. Rejected because it conflates two commands and breaks workflows that intentionally call generate planfile standalone (e.g., policy gates that do not need to run a full plan).
- Add an
atmos ci generate-planfile orchestrator command — a new subcommand that wraps generate planfile and runs the upload/summary/checks itself. Rejected because the rest of the terraform commands wire CI through hook events; introducing a parallel orchestrator would split the model.
Additional Context
Relevant code references:
Related but distinct:
Describe the Feature
atmos terraform generate planfilecurrently bypasses theatmos ciintegration entirely. The command writes a JSON/YAML representation of a Terraform plan to disk, but unlikeatmos terraform plan/apply/deploy, it does not fire any CI hooks. As a result, none of the CI features that already exist forplan/apply— job summary, CI outputs, planfile upload to storage, GitHub commit status / check run, or PR comments — are available when a user usesgenerate planfilein CI.This issue tracks wiring
generate planfileinto the same hook lifecycle thatplanalready uses, so that running it in CI produces the same artifacts and surface area.Expected Behavior
When
atmos terraform generate planfile -s <stack> <component>runs inside CI (or with--ci/ATMOS_CI=true):before.terraform.generate.planfilehook fires before the command runs.*.tfplanproduced internally) is uploaded to the configured planfile store (S3 / GitHub Artifacts / local) via the sameComponents.Terraform.Planfilesconfiguration used byplan.generate-planfile.mdtemplate (or aliased toplan.md) underpkg/ci/plugins/terraform/templates/.stack,component,has_changes, resource counts, etc.) are emitted via the platform's output writer.after.terraform.generate.planfilehook fires on success or failure (matching therunHooks/runHooksOnErrorWithOutputpattern fromcmd/terraform/plan.go).Use Case
Today, teams that want a JSON-shaped plan artifact in CI (for audit, OPA/Conftest policy evaluation, manual review, etc.) have to either:
atmos terraform planand post-process the canonical binary planfile, oratmos terraform generate planfileand then separately invokeatmos terraform planfile uploadand re-create their own summary/status logic.The first path forces a binary-then-JSON two-step; the second path duplicates the CI plumbing that already exists for
plan. Wiringgenerate planfileinto the hook lifecycle gives users a single, declarative CI integration that works regardless of which terraform subcommand is being driven.Describe Ideal Solution
Minimal changes:
BeforeTerraformGeneratePlanfile/AfterTerraformGeneratePlanfiletopkg/hooks/event.go.pkg/ci/plugins/terraform/plugin.goGetHookBindings(), reusingonBeforePlan/onAfterPlansemantics. AdjustuploadPlanfileto either:FileEntryalongside the binaryplan.tfplan, orkind=plan-json) so it does not collide with the binary planfile produced byterraform plan.-outfile when--ciis active ininternal/exec/terraform_generate_planfile.go(currently removed bydefer os.RemoveAll(tmpDir)), so the upload step has something to ship.RunEincmd/terraform/generate/planfile.gowith the samePreRunE/PostRunErunHooks/runHooksWithOutputpattern used bycmd/terraform/plan.go.--ciflag (mirroringcmd/terraform/plan.go:129-132) withpkg/ci.IsCI()autodetection, and tee terraform stdout/stderr throughansi.Stripfor templates.generate-planfile.mdtemplate (or alias toplan.md) underpkg/ci/plugins/terraform/templates/.pkg/ci/executor_test.goandcmd/terraform/utils_hooks_test.go(parallel to the existingrunCIHooksForPlanComponentcoverage).Alternatives Considered
generate planfileas-is and tell users to runatmos terraform planfile uploadafter. Rejected because it duplicates CI plumbing (summary/checks/comments are not produced) and forces users to re-implement what already exists forplan.generate planfileJSON to a sub-resource of theplanartifact — emit it only as a side effect ofatmos terraform planwith a--json/--also-emit-jsonflag. Rejected because it conflates two commands and breaks workflows that intentionally callgenerate planfilestandalone (e.g., policy gates that do not need to run a full plan).atmos ci generate-planfileorchestrator command — a new subcommand that wrapsgenerate planfileand runs the upload/summary/checks itself. Rejected because the rest of the terraform commands wire CI through hook events; introducing a parallel orchestrator would split the model.Additional Context
Relevant code references:
cmd/terraform/generate/planfile.go— no hook wrapping.cmd/terraform/plan.go—PreRunE/PostRunErunHookspattern,--ciflag, output capture.pkg/hooks/event.go— currently onlyinit/plan/apply/deploy.pkg/ci/plugins/terraform/handlers.go—onBeforePlan/onAfterPlan/uploadPlanfile/writeSummary/updateCheckRun.pkg/ci/plugins/terraform/planfile/interface.go—PlanFilename = "plan.tfplan",Metadata,Store.cmd/terraform/planfile/upload.go.Related but distinct:
--dirflag togenerate planfile(path ergonomics, not CI integration).