Skip to content

fix: declare a Terraform floor the module actually runs on (ENG-8760) - #29

Merged
johnsca merged 9 commits into
mainfrom
johnsca/fix/ENG-8760/min-terraform-version
Oct 13, 2026
Merged

johnsca merged 9 commits into
mainfrom
johnsca/fix/ENG-8760/min-terraform-version

Conversation

@johnsca

@johnsca johnsca commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

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.yaml so 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 evaluated trimspace(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 validate against 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 with terraform test and mock_provider is 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's main.

testing

  • Sandbox deploy. Planned from a root module against the sandbox Azure subscription with a service principal, at the declared floor of 1.14.0 and again at 1.16.4. Both produce the same 34 resources, and no resource, variable or output changed. The plan running at all is the thing to watch, since this is the change that decides whether the module evaluates on a given CLI.
  • The old floor disproved rather than assumed. The module fails on 1.9.0, 1.10.5, 1.11.0 and 1.11.4, and passes from 1.12.0 up, which matches where Terraform started short-circuiting ||.
  • The README confirmed already identical to freshly generated output, so the new docs job passes on arrival rather than going red.
  • The extraction checked against a module carrying three constraints at once, one of them in a .tf.json and one split across lines. It returns all three, and Terraform's own init agrees on which one binds. The action it replaces returns an empty string for that module.
  • The docs job confirmed to load .terraform-docs.yml, which it was silently ignoring.
  • just lint clean 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-versions rather than being resolved at run time, so CI and a local run agree on it.
  • Repointed the uses: pin at 3865043, the commit github-tooling's main carries 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.
  • Refreshed the pinned tools and re-ran just lint at 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 version terraform-docs/gh-actions v1.4.1 bundles, so the docs job and just docs keep 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.

@johnsca johnsca changed the title fix: test the declared Terraform floor in CI (ENG-8760) fix: declare a Terraform floor the module actually runs on (ENG-8760) Sep 30, 2026
@johnsca

johnsca commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai please review this PR.

Filed by Claude on Cory's behalf.

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 30, 2026

Copy link
Copy Markdown

@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.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread provider.tf Resolved
johnsca and others added 3 commits October 5, 2026 15:36
[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>
@johnsca
johnsca force-pushed the johnsca/fix/ENG-8760/min-terraform-version branch from 44d1c3b to 242f5b7 Compare October 5, 2026 15:50
@johnsca

johnsca commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai please review this PR.

Filed by Claude on Cory's behalf.

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 5, 2026

Copy link
Copy Markdown

@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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Loading

Re-trigger cubic

@johnsca
johnsca force-pushed the johnsca/fix/ENG-8760/min-terraform-version branch from 695baab to 11fff84 Compare October 6, 2026 20:04
@johnsca

johnsca commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai please review this PR.

Filed by Claude on Cory's behalf.

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 6, 2026

Copy link
Copy Markdown

@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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Loading

Re-trigger cubic

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@johnsca
johnsca force-pushed the johnsca/fix/ENG-8760/min-terraform-version branch from 11fff84 to d816f39 Compare October 6, 2026 20:47
@johnsca
johnsca marked this pull request as ready for review October 6, 2026 21:03
@johnsca
johnsca requested a review from a team as a code owner October 6, 2026 21:03

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Loading

Re-trigger cubic

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@johnsca
johnsca force-pushed the johnsca/fix/ENG-8760/min-terraform-version branch from 32056a3 to 49564f4 Compare October 6, 2026 22:13

@jtroup jtroup left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments aren't blocking. Thanks @johnsca

Comment thread vars.tf Resolved
Comment thread .tool-versions Resolved
Comment thread provider.tf Resolved
@johnsca
johnsca force-pushed the johnsca/fix/ENG-8760/min-terraform-version branch from 3fb471c to c5b7fc1 Compare October 8, 2026 16:13
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>
@johnsca
johnsca force-pushed the johnsca/fix/ENG-8760/min-terraform-version branch from c5b7fc1 to ddf056d Compare October 9, 2026 21:17
johnsca and others added 3 commits October 13, 2026 14:12
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>
@johnsca
johnsca merged commit 56f8439 into main Oct 13, 2026
5 checks passed
@johnsca
johnsca deleted the johnsca/fix/ENG-8760/min-terraform-version branch October 13, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants