Repository navigation
feat: add the validate-min-terraform action (ENG-8760) #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
95f8186
feat: add the validate-min-terraform action (ENG-8760)
johnsca 13a7e2f
Accept a single-component bound, and read requirements explicitly
johnsca a938803
Move the parse into a script, test it, and clamp the floor
johnsca 904e90a
Port the floor parser from shell to Python
johnsca 34049ab
Pin the prek version the lint job installs
johnsca 4f0a80b
Cover the multiline constraint, and say where the floor check bites
johnsca 8522610
Raise the minimum floor to a supported release, and simplify the parse
johnsca 8a5dff0
Accept an exact version as a floor
johnsca e47cbf9
Reject an exclusion only when it removes the floor
johnsca c9cef9b
Add CODEOWNERS
johnsca File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| * @stacklet/engineering |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| name: CI | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| pull_request: | ||
| branches: | ||
| - main | ||
|
|
||
| permissions: {} | ||
|
|
||
| jobs: | ||
| lint: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Set tool versions | ||
| uses: wistia/parse-tool-versions@32f568a4ffd4bfa7720ebf93f171597d1ebc979a # v2.1.1 | ||
| with: | ||
| postfix: _TOOL_VERSION | ||
|
|
||
| - uses: j178/prek-action@4e14d07f9231acabce116ccfca13b13dd9755ece # v3.0.0 | ||
| with: | ||
| # Without a pin this installs whatever prek released most recently, | ||
| # so a hostile release reaches CI with no change here. The version | ||
| # comes from .tool-versions, so CI and a local run agree on it. | ||
| prek-version: ${{ env.PREK_TOOL_VERSION }} | ||
| env: | ||
| # zizmor's five online audits skip silently without a token, and its | ||
| # summary line reads the same either way, so CI would pass while the | ||
| # supply-chain audits never ran. | ||
| GH_TOKEN: ${{ github.token }} | ||
|
|
||
| test: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Run the tests | ||
| run: python3 -m unittest discover -s validate-min-terraform -p 'test_*.py' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| __pycache__/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| repos: | ||
| - repo: https://github.com/rhysd/actionlint | ||
| rev: 393031adb9afb225ee52ae2ccd7a5af5525e03e8 # frozen: v1.7.11 | ||
| hooks: | ||
| - id: actionlint | ||
|
|
||
| - repo: https://github.com/zizmorcore/zizmor-pre-commit | ||
| rev: ea2eb407b4cbce87cf0d502f36578950494f5ac9 # frozen: v1.23.1 | ||
| hooks: | ||
| - id: zizmor | ||
|
|
||
| - repo: https://github.com/astral-sh/ruff-pre-commit | ||
| rev: f12be1ebaa5351c1fc76472de98db2c3446c8253 # frozen: v0.16.10 | ||
| hooks: | ||
| - id: ruff-check | ||
| - id: ruff-format | ||
|
|
||
| - repo: https://github.com/codespell-project/codespell | ||
| rev: 2ccb47ff45ad361a21071a7eedda4c37e6ae8c5a # frozen: v2.4.2 | ||
| hooks: | ||
| - id: codespell |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| just 1.58.0 | ||
| prek 0.5.5 | ||
| uv 0.12.23 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,24 @@ | ||
| # github-tooling | ||
| Reusable GitHub Actions and workflows shared across Stacklet repositories | ||
|
|
||
| Reusable GitHub Actions and workflows shared across Stacklet repositories. | ||
|
|
||
| Each tool lives in its own top-level directory with an `action.yml` and a `README.md`. Reference one by path: | ||
|
|
||
| ```yaml | ||
| - uses: stacklet/github-tooling/<tool>@<commit-sha> | ||
| ``` | ||
|
|
||
| Pin by commit SHA. This repository publishes no release tags, so a SHA is the only reference that cannot move. | ||
|
|
||
| ## Tools | ||
|
|
||
| | Tool | Purpose | | ||
| | --- | --- | | ||
| | [validate-min-terraform](validate-min-terraform/) | Runs `terraform validate` at the floor a module's `required_version` declares, and fails when that floor is below a configured minimum. | | ||
|
|
||
| ## Adding a tool | ||
|
|
||
| 1. Create a directory named for the tool. | ||
| 2. Add an `action.yml` and a `README.md` covering usage, inputs, and outputs. | ||
| 3. Keep any non-trivial logic in its own script beside the `action.yml`, so that a linter reads it and a test can run it without a runner. Prefer Python over shell for anything that parses or compares. | ||
| 4. Add a row to the table above. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # lint files | ||
| lint: | ||
| prek run --all-files | ||
|
|
||
| # run the tests | ||
| test: | ||
| python3 -m unittest discover -s validate-min-terraform -p 'test_*.py' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| # validate-min-terraform | ||
|
|
||
| Runs `terraform validate` against a Terraform module at the oldest release its | ||
| `required_version` allows, so the bottom of the declared range gets tested | ||
| rather than assumed. | ||
|
|
||
| ## Usage | ||
|
|
||
| ```yaml | ||
| jobs: | ||
| validate-min-terraform: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: stacklet/github-tooling/validate-min-terraform@<commit-sha> | ||
| ``` | ||
|
|
||
| > [!WARNING] | ||
| > `terraform init` installs the providers the module names, and `validate` runs | ||
| > each provider binary to read its schema. On a pull request that configuration | ||
| > is untrusted, so give the job `contents: read`, and no secrets. | ||
|
|
||
| ## Inputs | ||
|
|
||
| | Name | Default | Description | | ||
| | --- | --- | --- | | ||
| | `working-directory` | `.` | Directory holding the module. One directory per call, with no descent into subdirectories. | | ||
| | `minimum-floor` | `1.14.0` | Refuse to run below this release. The default is the oldest release HashiCorp still ships fixes for. | | ||
| | `terraform-docs-image` | a digest-pinned terraform-docs 0.20.0 | Image used to parse the configuration. | | ||
|
|
||
| ## Outputs | ||
|
|
||
| | Name | Description | | ||
| | --- | --- | | ||
| | `version` | The Terraform release the module declares as its floor. | | ||
|
|
||
| ## Requirements | ||
|
|
||
| The runner needs Docker and `python3`. `ubuntu-latest` has both, and the macOS | ||
| runners have no Docker. | ||
|
|
||
| ## Version constraints | ||
|
|
||
| `required_version` must state a lower bound with `>=`, `~>` or `=`, or name an | ||
| exact version such as `1.14.0`. Upper bounds are allowed and ignored, as is | ||
| `!=` unless it excludes the floor itself. The action rejects any constraint it | ||
| cannot resolve to a definite release, and fails when the floor it resolves | ||
| falls below `minimum-floor`. | ||
|
|
||
| Every `required_version` in the directory counts, including one in a `.tf.json` | ||
| or split across lines. The floor is the highest lower bound among them, which | ||
| is what Terraform itself applies. | ||
|
|
||
| ## Known limitation | ||
|
|
||
| > [!NOTE] | ||
| > `terraform validate` treats every root variable as unknown, so it never | ||
| > evaluates `validation` blocks. A floor that is wrong only inside a variable | ||
| > validation still passes here. | ||
|
|
||
| ## Tests | ||
|
|
||
| `just test` runs `test_read_floor.py`, and CI runs the same command. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| name: Validate the declared Terraform floor | ||
| description: >- | ||
| Reads the Terraform floor out of a module's required_version and runs | ||
| terraform validate at that release. | ||
|
|
||
| inputs: | ||
| working-directory: | ||
| description: Directory holding the Terraform module to validate. | ||
| required: false | ||
| default: . | ||
| minimum-floor: | ||
| description: >- | ||
| Refuse to run below this release. The floor comes out of the | ||
| configuration under test, so on a pull request the branch decides which | ||
| Terraform CI installs. | ||
| required: false | ||
| # HashiCorp routes a version to a support regime by release date, and 1.14 | ||
| # falls in the band still receiving fixes and security patches, so this is | ||
| # the oldest release with a published claim to them. It wants revisiting | ||
| # when a new minor lands. See the README. | ||
| default: 1.14.0 | ||
| terraform-docs-image: | ||
| description: >- | ||
| Image used to parse the configuration. Pin by digest, not by tag. | ||
| required: false | ||
| # terraform-docs publishes no immutable releases, which leaves both a tag | ||
| # and a release checksum able to change underneath us. A digest is content | ||
| # addressed, so it cannot. This one is terraform-docs 0.20.0, the last | ||
| # release published as a multi-arch index. Later tags are per-architecture, | ||
| # so a digest pin would have to choose one. | ||
| default: quay.io/terraform-docs/terraform-docs@sha256:37329e2dc2518e7f719a986a3954b10771c3fe000f50f83fd4d98d489df2eae2 | ||
|
|
||
| outputs: | ||
| version: | ||
| description: The Terraform release the module declares as its floor. | ||
| value: ${{ steps.floor.outputs.version }} | ||
|
|
||
| runs: | ||
| using: composite | ||
| steps: | ||
| # The floor is read out of required_version rather than passed in, so that | ||
| # editing the constraint moves what CI tests in the same commit. | ||
| # terraform-docs parses the configuration instead of matching a regex | ||
| # against one file, so it reports every required_version in the directory, | ||
| # including one in a .tf.json or split across lines. | ||
| - name: Read the declared Terraform floor | ||
| id: floor | ||
| shell: bash | ||
| working-directory: ${{ inputs.working-directory }} | ||
| env: | ||
| TERRAFORM_DOCS_IMAGE: ${{ inputs.terraform-docs-image }} | ||
| MINIMUM_FLOOR: ${{ inputs.minimum-floor }} | ||
| # The script writes the step output itself, so this step carries no shell | ||
| # logic. A composite step has to name a shell even so. | ||
| run: '"$GITHUB_ACTION_PATH/read_floor.py"' | ||
|
|
||
| - name: Setup Terraform | ||
| uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 | ||
| with: | ||
| terraform_version: ${{ steps.floor.outputs.version }} | ||
|
|
||
| - name: Validate at the declared floor | ||
| shell: bash | ||
| working-directory: ${{ inputs.working-directory }} | ||
| run: | | ||
| terraform init -backend=false -input=false | ||
| terraform validate | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@johnsca Hmm, why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hold-over from pre-refactor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A composite step has to declare a shell, so
shell: bashitself cannot go. The hold-over was the logic on that line.read_floor.pynow writesversion=to$GITHUB_OUTPUTitself, so the step is a bare invocation of the script. A test covers the file contents.Filed by Claude on Cory's behalf.