Skip to content

fix(scripts): route make frontend-only through tf-deploy.sh - #1291

Draft
IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-59
Draft

IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-59

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Root cause

Makefile.terraform's frontend-only target was the only state-changing target that didn't go through scripts/tf-deploy.sh. It ran terraform itself:

@cd terraform/environments/$(PROVIDER)/$(PROFILE) && \
	terraform apply -var-file="../../../profiles/$(PROVIDER)/$(PROFILE).tfvars" -target=module.frontend

Because of that it skipped everything the script does around the apply: the profile existence check, the environment-directory bootstrap (mkdir plus the main.tf symlink), the automatic terraform init when .terraform is missing, the AWS_PROFILE warning and the "Deployment Outputs" summary. It also kept its own copy of the ../../../profiles/... path arithmetic.

Fix

tf-deploy.sh already passes extra arguments to terraform unchanged, so the target now reads:

@./scripts/tf-deploy.sh $(PROVIDER) $(PROFILE) apply -target=module.frontend

This matches how deploy, plan, destroy, output and docker-skip already work. The apply still has no -auto-approve, so terraform still asks before changing anything, as it did before.

terraform argv with the env dir present (recorded by a stub terraform):

calls
before apply -var-file=../../../profiles/aws/dev.tfvars -target=module.frontend (no init, even when .terraform is missing)
after, uninitialized env dir init, then apply -var-file=<root>/terraform/profiles/aws/dev.tfvars -target=module.frontend, then output
after, initialized env dir apply -var-file=<root>/terraform/profiles/aws/dev.tfvars -target=module.frontend, then output
after, missing profile no terraform call; exits non-zero with Profile not found: ... (before: apply -var-file=../../../profiles/aws/nope.tfvars ... reached terraform)
after, no env dir the script creates it and its main.tf symlink (before: cd: can't cd to terraform/environments/aws/dev)

Regression test

scripts/test-tf-deploy.sh (new; the repo had no test for tf-deploy.sh) copies Makefile.terraform and scripts/tf-deploy.sh into a mktemp -d tree laid out like the repo. It puts a stub terraform on PATH that only appends its argv to a log, then checks:

  1. make -n frontend-only prints ./scripts/tf-deploy.sh aws dev apply -target=module.frontend and no direct terraform apply.
  2. Fresh tree: the target succeeds, the env dir and main.tf symlink get created, the calls are exactly init / targeted apply / output, and the output includes the outputs summary and the AWS_PROFILE warning.
  3. Env dir that exists but has never been initialized: init still runs first.
  4. Initialized env dir: init is skipped.
  5. Missing profile: the target exits non-zero, prints Profile not found, and terraform is never called.
  6. The script itself adds no empty argument when there are no extras, and passes extras through unchanged (including one with a space).
  7. Static guard: no Makefile.terraform recipe runs terraform apply|plan|destroy|refresh|import directly.

A new tf-deploy-wrapper job in ci.yml runs it (shell only, contents: read, same shape as migration-down-gate), and ci-success now depends on it.

Closes #59

Verification

All of this is local stub and fixture evidence. Nothing ran terraform against a real backend.

  • Fail-before, with the test run against the old Makefile.terraform: bash scripts/test-tf-deploy.sh gave Results: 1 passed, 13 failed (only check 6 passed, since it covers the script alone).
  • Pass-after: bash scripts/test-tf-deploy.sh gave Results: 14 passed, 0 failed.
  • make -n -f Makefile.terraform frontend-only PROVIDER=aws PROFILE=dev prints:
    echo "🎨 Deploying frontend..."
    ./scripts/tf-deploy.sh aws dev apply -target=module.frontend
    
  • shellcheck scripts/test-tf-deploy.sh (0.11.0): clean. scripts/tf-deploy.sh is unchanged; it has pre-existing SC2011/SC2162/SC2181 findings that this PR leaves alone.
  • actionlint .github/workflows/ci.yml: clean.
  • pre-commit run --files Makefile.terraform scripts/test-tf-deploy.sh .github/workflows/ci.yml: all applicable hooks passed (including actionlint and zizmor), except git-secrets and trivy-config, which couldn't run because those binaries aren't installed locally.
  • No Go code changed, so there were no Go build, vet or test runs.

Combined-tree verification. This branch was tested together with the other queued branches on a local, never-pushed tree: queue/platform @ 936996ad = origin/main a14dc8f2 plus these heads, merged in this order:

Branch Tested head
fix/issue-337 ff3420c
fix/issue-169 7d75eb5
fix/issue-342 66b221f
fix/issue-310 5e11c21
fix/issue-326 5f0ea59
fix/issue-243 365e959
fix/issue-343 6c94036
fix/issue-312 d2848a8
fix/issue-316 acd60dd
fix/issue-339 75ea9b9
fix/issue-339-commitment-type 73bdb19
fix/issue-353 5d25fcd
fix/issue-313 3156bc4
fix/issue-357 53d9e58
fix/issue-368 0ba727d
fix/issue-419 61648c9
fix/issue-111 b04c372
fix/issue-686 0ad90b7
fix/issue-674 793095d
fix/issue-1055 4ec96f1
fix/issue-559 dfa9525
fix/issue-558 034eed4
fix/issue-874 1720539
fix/issue-666 1e120da
fix/issue-668 fb26345
fix/issue-568 ce04173
fix/issue-311 e54f250
fix/issue-177 0cbb88a
fix/issue-151 c21377c
fix/issue-166 41fb77b
fix/issue-27 2d67622
fix/issue-323 69288ae
fix/issue-59 36463e8
fix/issue-43 35e4069
fix/issue-158 6f76f8a
fix/issue-104 bb00491
fix/issue-55 971ef1b
fix/issue-361 89ac220
fix/issue-120 b458719
fix/issue-36 42bac6e
fix/issue-525 57c97dd
fix/issue-712 e5bcda0
fix/issue-72 6cc63ed

fix/issue-72 and fix/issue-312 conflict on one line in internal/api/handler_dashboard.go (both change the calculateCommitmentMetrics call). The tree used #72's side with #312's validated provider variable; whichever PR merges second takes that same resolution.

Linux, Go 1.26.9, AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true; Docker Postgres via testcontainers:

  • Passed at 936996ad: go build ./..., go vet ./..., go vet -tags integration ./... and the full go test -race -short -count=1 ./....
  • Static checks at 936996ad: golangci-lint v2.10.1 found 0 issues on every changed package, with and without --build-tags integration. tests/e2e is a separate module, so it was vetted under its own e2e tag. gocyclo -over 10 is silent on the changed non-test files.
  • Integration tests, every changed package, one package at a time (-tags integration -count=1, 60-minute local timeout because the VM was shared):
    • At 936996ad, these pass: internal/server, internal/credentials, internal/scheduler, internal/database/postgres/migrations, internal/purchase and internal/api.
    • internal/config passes except TestPostgresStoreDB_ResolveStaleRIExchange_ValidatesCloudCheck, which fails the same way on unmodified main (fixed by fix(config): compare resolve checked_at at the database's microsecond precision #1252).
    • These were run on the first build of this tree (26795376, same branches at earlier heads): internal/analytics, internal/auth, internal/commitmentopts, internal/database and cmd/lambda/clear-rate-limit all pass. The later fixes don't touch those packages.
  • What the combined run caught, each fixed on its own branch before this PR opened:
    • a compile break between fix/issue-104's test and fix/issue-326's httpToLambdaRequest signature change;
    • fix/issue-316 rejecting legacy-keyed plans;
    • fix/issue-120 versus main's global-filter test.
  • Secrets: git-secrets, using the repo's scripts/setup-git-secrets.sh patterns in a fresh repo with an isolated HOME, found nothing in the changed files, and a planted fake key was flagged. scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.
  • Nothing was applied to any cloud.

Out of scope

  • Other Makefile.terraform targets still call terraform directly, but none of them applies or plans: validate (terraform validate), fmt (terraform fmt -recursive), state-list (terraform state list) and state-show (terraform state show $(RESOURCE)). The issue only covers frontend-only, and routing these through the script wouldn't work as written: its catch-all branch adds -var-file=... straight after the action word (e.g. terraform state -var-file=... list), which state subcommands reject. They're left as they were.
  • Pre-existing and unrelated: as docs/audits/codebase-audit-2026-09-02.md notes, tf-deploy.sh uses terraform/environments/<provider>/<profile> as the working directory, but the repo has one flat root per provider (terraform/environments/aws), and terraform/profiles/aws/ holds only *.tfvars.example. So the local script path, and with this PR make frontend-only too, stops at the profile check until someone creates a profile. That's a problem with the script's layout, not with this target. Routing through the script means a future fix to the layout covers frontend-only automatically, instead of needing a second copy of the path logic.
  • Open PR fix(scripts): make init-backend.sh write the state key CI uses #1142 also edits ci.yml (it adds a step to the aws-tfstate-platform-key job). The hunks don't overlap.

Maintainer: please mirror the issue's triage labels onto this PR: type/chore, impact/internal, priority/p3, severity/low, urgency/eventually, effort/xs, triaged.

🤖 Generated with Claude Code

Makefile.terraform's frontend-only target cd'd into the environment
directory and ran `terraform apply -target=module.frontend` itself, so it
skipped everything tf-deploy.sh does around the apply: the profile
existence check, the environment-directory bootstrap, the automatic
`terraform init`, the AWS_PROFILE warning and the outputs summary. It
also kept its own copy of the ../../../profiles path arithmetic.

tf-deploy.sh forwards extra arguments to terraform verbatim, so the
target now calls `./scripts/tf-deploy.sh $(PROVIDER) $(PROFILE) apply
-target=module.frontend`, like deploy/plan/destroy/docker-skip already do.
The apply still has no -auto-approve, so terraform still asks before
changing anything.

scripts/test-tf-deploy.sh runs the targets and the script in a temp copy
of the layout with a stub terraform that only records its argv, and
fails on the old recipe (13 of 14 checks). A new CI job runs it.

Closes LeanerCloud#59

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added impact/internal Team-internal only type/chore Maintenance / non-user-visible triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline effort/xs Trivial / one-liner labels Oct 23, 2026
@coderabbitai

coderabbitai Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/xs Trivial / one-liner impact/internal Team-internal only priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/chore Maintenance / non-user-visible urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Route Makefile.terraform frontend-only through scripts/tf-deploy.sh (unify deploy paths)

1 participant