cloudposse / cloudposse/atmos

Wire `atmos terraform generate planfile` into the `atmos ci` hook lifecycle

Open
#2,497 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
1.4k
Forks
175
Avg merge
2d 3h
Merged PRs (30d)
134

Description

### 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 ` runs inside CI (or with `--ci` / `ATMOS_CI=true`):

1. A `before.terraform.generate.planfile` hook fires before the command runs.
2. 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`.
3. A job summary is written using a `generate-planfile.md` template (or aliased to `plan.md`) under `pkg/ci/plugins/terraform/templates/`.
4. CI outputs (`stack`, `component`, `has_changes`, resource counts, etc.) are emitted via the platform's output writer.
5. A commit status / check run is created and updated with the result.
6. Optionally, a PR comment is posted using the same template.
7. 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

1. **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`.
2. **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).
3. **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:

- Current entrypoint: [`cmd/terraform/generate/planfile.go`](https://github.com/cloudposse/atmos/blob/main/cmd/terraform/generate/planfile.go) — no hook wrapping.
- Hook lifecycle reference: [`cmd/terraform/plan.go`](https://github.com/cloudposse/atmos/blob/main/cmd/terraform/plan.go) — `PreRunE`/`PostRunE` `runHooks` pattern, `--ci` flag, output capture.
- Hook events: [`pkg/hooks/event.go`](https://github.com/cloudposse/atmos/blob/main/pkg/hooks/event.go) — currently only `init/plan/apply/deploy`.
- CI plugin handlers: [`pkg/ci/plugins/terraform/handlers.go`](https://github.com/cloudposse/atmos/blob/main/pkg/ci/plugins/terraform/handlers.go) — `onBeforePlan` / `onAfterPlan` / `uploadPlanfile` / `writeSummary` / `updateCheckRun`.
- Planfile bundle interface: [`pkg/ci/plugins/terraform/planfile/interface.go`](https://github.com/cloudposse/atmos/blob/main/pkg/ci/plugins/terraform/planfile/interface.go) — `PlanFilename = "plan.tfplan"`, `Metadata`, `Store`.
- Manual upload path that already exists for the binary planfile: [`cmd/terraform/planfile/upload.go`](https://github.com/cloudposse/atmos/blob/main/cmd/terraform/planfile/upload.go).

Related but distinct:

- #1403 — adds `--dir` flag to `generate planfile` (path ergonomics, not CI integration).
- #2368 — planfile storage priority bug (affects this work transitively).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.