Repository navigation
fix: declare a Terraform floor the module actually runs on (ENG-8760) - #29
Conversation
|
@cubic-dev-ai please review this PR. Filed by Claude on Cory's behalf. |
@johnsca I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 4 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
[ENG-8760](https://stacklet.atlassian.net/browse/ENG-8760) ### what Adds a CI job that validates this module at the Terraform floor it declares, and a `lint-docs` job to keep the README's generated tables honest. ### why ENG-8760 found `terraform-aws-onboarding-account` declaring a floor it could not run on, undetected because `.tool-versions` pins a version far above it. This repo's floor is correct and load-bearing: two variable validations reference objects outside their own variable, which needs Terraform 1.9. `vars.tf:137` checks `subnet_prefix_length` against `local.vnet_mask`, and `vars.tf:62` checks `event_grid_topic_name` against `event_grid_topic_resource_group`. The comment at `vars.tf:59` asserts that dependency; nothing verified it. A guard step ahead of the new job asserts exactly one `required_version`, in `provider.tf`. The action that resolves the floor picks its source file by shortest filename rather than by a canonical name, and `main.tf` is shorter than `provider.tf`. `lint-docs` is unrelated to the floor but adjacent: this repo generates its README tables with terraform-docs and had nothing checking they stayed current. `terraform-aws-stacklet-cur-setup` was in that state and its table had silently drifted to claim the module has no requirements at all. The lint job now passes a token to zizmor. Its five online audits skip silently without one, and the summary line reads identically either way, so CI reported success while the supply-chain audits never ran. ### testing - [x] Ran the version-resolution action locally against this `provider.tf`: resolves `>= 1.9.0, < 2.0.0` to `1.9.0`, and the module validates there against current providers. - [x] Confirmed the README is already byte-identical to freshly regenerated terraform-docs 0.20.0 output, so the new `lint-docs` job passes on arrival. - [x] The guard step exercised both ways under bash and GNU grep: passes as-is, and fails when a second `required_version` is planted in `main.tf`. - [x] `just lint` clean, and zizmor re-run with a token to confirm the five online audits schedule rather than skip. - [ ] Sandbox deploy. Expect a no-op: no resource, variable or output changed. ### docs No documentation change. The new `lint-docs` job enforces the existing terraform-docs output rather than altering it. ### note This repo's pins trail the other four — `actions/checkout` at v7.0.0 against v7.0.1, `prek-action` at v2.0.4 against v3.0.0, `setup-tflint` at v6.2.2 against v6.3.0 — which looks like a stalled Dependabot run rather than anything deliberate. The jobs added here use the current pins, so `ci.yml` now carries two `actions/checkout` SHAs. Left for Dependabot to reconcile rather than hand-bumped in a change about version floors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The old floor of 1.9 was also wrong, not just old. The blank-topic guards in vars.tf read `x == null || trimspace(x) != ""`, and Terraform did not short-circuit `||` until 1.12, so every plan below that failed on trimspace(null). Found by planning the module at 1.9.0 against real Azure. Declare 1.14 instead, which is a line that still receives security fixes, and move the CI pin to the current 1.16.4 so the floor and the pin stay distinct. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
terraform-min-max finds the constraint by regex, takes the first match per file, ignores .tf.json, and picks a file by shortest path. The guard step added to compensate was the same regex with the same blind spots, so it could never be more correct than the thing it was guarding. On a required_version split across lines the action extracts an empty string, which semver reads as "any version", so the floor resolves to the oldest Terraform ever released. terraform-docs parses the configuration and reports every required_version in the module, which removes the question of which file holds the real one. The floor is then the highest declared lower bound, which is what Terraform applies. Pinned by digest because terraform-docs publishes no immutable releases, so neither a tag nor a release checksum is fixed. Also point the docs job at .terraform-docs.yml, which it was ignoring, settle on that spelling over .terraform-docs.yaml, and correct a vars.tf comment that still named 1.9 as the floor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
44d1c3b to
242f5b7
Compare
|
@cubic-dev-ai please review this PR. Filed by Claude on Cory's behalf. |
@johnsca I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
No issues found across 6 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant GH as GitHub Actions CI
participant Lint as Lint Job (pre-commit)
participant LintDocs as Lint Docs Job
participant MinTF as Validate Min Terraform Job
participant TD as terraform-docs container
participant TF as Terraform CLI
participant Module as Terraform Module
participant Zizmor as zizmor
Note over GH,Module: CI pipeline with new validation jobs
GH->>Lint: Run pre-commit checks
Lint->>Lint: Run pre-commit hooks
alt With GH_TOKEN set
Lint->>Zizmor: Run zizmor audits
Note over Zizmor: Online audits now schedule with token
Zizmor-->>Lint: Audit results
end
GH->>LintDocs: Check documentation freshness
LintDocs->>LintDocs: Checkout repo (persist-credentials: false)
LintDocs->>TD: Run terraform-docs with config-file: .terraform-docs.yml
Note over TD: Action fails without this - defaults differ from just docs
TD->>Module: Parse module config
Module-->>TD: Extract requirements
TD-->>LintDocs: Generated README
LintDocs->>LintDocs: Compare generated vs committed README
alt Diff found
LintDocs-->>GH: Fail - README out of date
end
GH->>MinTF: Validate at declared floor
MinTF->>MinTF: Checkout repo (persist-credentials: false)
MinTF->>TD: docker run terraform-docs json . --output-file ''
TD-->>MinTF: JSON with requirements
MinTF->>MinTF: Extract required_version constraints
alt No constraints found
MinTF-->>GH: Fail - no required_version declared
else Multiple constraints
MinTF->>MinTF: Find highest lower bound >= or ~>
Note over MinTF: Effective floor = max of all bounds
end
MinTF->>TF: Setup Terraform at floor version
TF->>Module: terraform init -backend=false
Module-->>TF: Init complete
TF->>Module: terraform validate
Module-->>TF: Validation result
TF-->>MinTF: Result (syntax + provider schema)
MinTF-->>GH: Job result
Note over Module: Validation at CLI version
alt Below 1.12
Module-->>TF: Fail - no short-circuit || for null checks
else 1.12 - 1.13 (unsupported)
Module-->>TF: Pass but no security fixes
else >= 1.14 (declared floor)
Module-->>TF: Pass with supported version
end
695baab to
11fff84
Compare
|
@cubic-dev-ai please review this PR. Filed by Claude on Cory's behalf. |
@johnsca I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
No issues found across 6 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant DV as Terraform Module<br/>(required_version: >= 1.14.0, < 2.0.0)
participant TFCore as Terraform Core<br/>(CLI: 1.16.4 CI pin)
participant Vars as Variables<br/>(cross-variable validation)
participant Guard as Validation Guards<br/>(null || trimspace check)
participant Plan as Terraform Plan<br/>(root module with defaults)
participant CI as GitHub Actions CI
participant TFDocs as terraform-docs<br/>(.terraform-docs.yml)
participant Config as .terraform-docs.yml<br/>(config file)
participant GH as GitHub<br/>(token for zizmor)
Note over DV,TFCore: Terraform Floor Enforcement
CI->>TFCore: CHANGED: Validate at pinned 1.16.4 (lint job)
CI->>TFCore: NEW: Validate at declared floor 1.14.0<br/>(validate-min-terraform action)
TFCore->>DV: Load module (required_version check)
DV-->>TFCore: Constraint >= 1.14.0, < 2.0.0
alt CLI version >= 1.14.0
TFCore->>Vars: Evaluate variable defaults<br/>(null topic args)
Vars->>Guard: Check x == null || trimspace(x) != ""
Note over Guard: NEW: Requires Terraform >= 1.12<br/>(short-circuits || to avoid trimspace(null))
alt Guard passes
Guard-->>TFCore: Valid conditions
TFCore->>Plan: Generate plan (34 resources)
Plan-->>CI: Plan output
else Guard fails (old Terraform < 1.12)
Guard-->>TFCore: CHANGED: trimspace(null) error<br/>(previously unfixed below 1.12)
TFCore-->>CI: Plan failure (older versions)
end
else CLI version < 1.14.0 (unsupported)
TFCore-->>CI: CHANGED: Module rejects CLI<br/>(raised floor from 1.9 to 1.14)
end
Note over TFDocs,Config: Documentation Validation
CI->>Config: CHANGED: Read config file explicitly
Config-->>TFDocs: Configuration (.terraform-docs.yml)
CI->>TFDocs: NEW: Check docs match (fail-on-diff)
TFDocs->>DV: Generate README requirement tables
DV-->>TFDocs: Generated output
alt Docs current
TFDocs-->>CI: Pass (no diff)
else Docs stale
TFDocs-->>CI: FAIL (tables out of date)
end
Note over CI,GH: Lint Token
CI->>GH: CHANGED: Supply GH_TOKEN (zizmor online audits)
GH-->>CI: Token accepted (5 online audits run)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
11fff84 to
d816f39
Compare
There was a problem hiding this comment.
No issues found across 6 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant Dev as Developer
participant CI as GitHub Actions CI
participant Lint as Lint Job
participant Docs as Docs Job
participant Valid as Validate-min-Terraform Job
participant Tooling as stacklet/github-tooling Action
participant Terraform as Terraform CLI
participant Module as Terraform Module
participant README as README.md
Note over Dev,README: CI Pipeline for Terraform Module
Dev->>CI: Push to repo
CI->>CI: Trigger workflow
CI->>Lint: Run lint job
Lint->>Lint: Run pre-commit checks
Note over Lint: Includes zizmor with GH_TOKEN<br/>for supply-chain audits
CI->>Docs: Run docs job (new)
Docs->>README: Check documentation
Note over Docs: Uses .terraform-docs.yml config
Docs->>Docs: fail-on-diff: true
alt README out of date
Docs-->>CI: Fail CI
else README matches
Docs-->>CI: Pass
end
CI->>Valid: Run validate-min-terraform job (new)
Valid->>Tooling: Invoke validate-min-terraform action
Tooling->>Tooling: Extract required_version from provider.tf
Tooling->>Terraform: Install Terraform at floor version (>= 1.14.0)
Terraform->>Module: terraform validate
Note over Terraform,Module: Validates syntax and provider schema<br/>at declared floor, not variable validation
Module-->>Terraform: Validation result
Terraform-->>Tooling: Exit code
Tooling-->>Valid: Pass/fail
Note over Terraform,Module: Floor version 1.14.0 -<br/>1.13 and earlier no security fixes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
32056a3 to
49564f4
Compare
3fb471c to
c5b7fc1
Compare
terraform-docs stays at 0.20.0: terraform-docs/gh-actions v1.4.1, which the docs job runs, bundles that exact version, so moving it here would have CI check the README against a different renderer than `just docs` uses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
c5b7fc1 to
ddf056d
Compare
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ENG-8760
what
Raises the declared Terraform floor to a version the module can actually plan on, moves the CI pin to the current release, adds a job that validates at whatever floor is declared, and adds a docs job to keep the generated README tables honest. The lint job also gains a token for zizmor. The docs job reads
.terraform-docs.yml, which the action ignores by default, and the config file is renamed from.terraform-docs.yamlso all five repos spell it the same way.why
The ticket is about a sibling that declared a floor it could not run on. This module turned out to have the same problem, which the ticket did not predict.
The declared 1.9 was defensible on paper, since two variable validations reach outside their own variable and that needs 1.9. What nobody had checked is whether the module plans there. It does not. The blank-topic guards added in ENG-8756 read
x == null || trimspace(x) != "", and Terraform did not short-circuit||until 1.12, so below that it evaluatedtrimspace(null)and failed the plan outright. Both topic arguments default to null, so this hit every consumer on 1.9 through 1.11 who did not attach an existing topic.Rewriting the guards as conditionals would have fixed it in place. Raising the floor is the better trade, because 1.13 and below no longer receive security fixes and there is no reason to keep supporting them. The floor lands at 1.14, the CI pin moves to 1.16.5, and the guards stay as written.
That bug is also worth knowing about when reading the new job.
terraform validateagainst a module directory treats every variable as unknown, so it never evaluates a validation block, and it reported this module as fine on 1.9. Only a plan from a root module, with the defaults actually applied, exposed it. The job therefore covers syntax and provider schema at the floor, not variable validation. Closing that gap withterraform testandmock_provideris worth a follow-up.The docs job is unrelated to floors but adjacent. This repo generates its README tables and had nothing checking they stayed current, which is precisely the state a sibling was already in, with its tables claiming the module had no requirements at all.
The job itself lives in
stacklet/github-tooling, because five module repositories need the same check and it carries enough shell to be worth one copy rather than five. This PR references that action by commit SHA, naming the commit on github-tooling'smain.testing
||..tf.jsonand one split across lines. It returns all three, and Terraform's owninitagrees on which one binds. The action it replaces returns an empty string for that module..terraform-docs.yml, which it was silently ignoring.just lintclean at the new pin, and zizmor re-run with a token to confirm its five online audits schedule rather than skip. The prek release that job installs now comes from.tool-versionsrather than being resolved at run time, so CI and a local run agree on it.uses:pin at3865043, the commit github-tooling'smaincarries now that feat: add the validate-min-terraform action (ENG-8760) github-tooling#1 merged. The action resolves there, so this job runs for the first time on this PR.just lintat them: terraform 1.16.5, tflint 0.64.0, just 1.58.0, uv 0.12.23. terraform-docs stays at 0.20.0, the versionterraform-docs/gh-actionsv1.4.1 bundles, so the docs job andjust docskeep rendering with the same binary. The plans above ran at 1.16.4, the pin at the time.docs
No documentation change beyond the generated Requirements table. The new job enforces the existing generated output rather than altering it.