Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
869f6b6
chore(AI): Init OpenSpec skills, wire into AGENTS.md
MaxymVlasov Jul 23, 2026
3707561
f
MaxymVlasov Jul 23, 2026
0051ab7
Merge branch 'master' into openspec
MaxymVlasov Jul 24, 2026
f053b65
commit part that makes +- sense
MaxymVlasov Jul 23, 2026
3df47b5
And commit a second part
MaxymVlasov Jul 23, 2026
c95abe6
Archive spec
MaxymVlasov Jul 23, 2026
e24e21b
Drop extralinks, as #1003 discarded
MaxymVlasov Jul 24, 2026
4c23c39
Apply suggestions from code review - docs
MaxymVlasov Jul 28, 2026
2e303f6
Rename function resolve_tool_version to resolve_tool_path
MaxymVlasov Jul 28, 2026
2764a0e
Apply suggestions from code review - code, 1st round
MaxymVlasov Jul 28, 2026
c915e7b
Apply suggestions from code review
MaxymVlasov Jul 28, 2026
b0bc55b
Apply review suggestions and little improvements
MaxymVlasov Jul 28, 2026
a5e62cc
chore: untrack local openspec.ai tooling files
MaxymVlasov Jul 28, 2026
4438ee8
Still ask AI to generate and use openspec, but do not commit skills
MaxymVlasov Jul 28, 2026
b85f444
fix(hooks): stop masking tool_path resolution failures
MaxymVlasov Jul 28, 2026
a432cff
revert: drop OpenSpec-related changes, moved to #1005
MaxymVlasov Jul 28, 2026
a475d47
fix(hooks): check terragrunt syntax version against tool_path, not $PATH
MaxymVlasov Jul 28, 2026
4dfc05c
Merge branch 'master' into downloadable_and_version_controlled_hooks
MaxymVlasov Jul 28, 2026
243e6f9
Apply suggestion from @MaxymVlasov
MaxymVlasov Jul 28, 2026
1c881c1
Add table from PR review comments
MaxymVlasov Jul 28, 2026
2ed31af
Fix numbering
MaxymVlasov Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ You can use [this PR](https://github.com/antonbabenko/pre-commit-terraform/pull/
* `docker build -t pre-commit --build-arg INSTALL_ALL=true .`
* `docker build -t pre-commit --build-arg <NEW_HOOK>_VERSION=latest .`
* `docker build -t pre-commit --build-arg <NEW_HOOK>_VERSION=<1.2.3> .`

> [!NOTE]
> `tools/install/<tool>.sh` has a second call site besides the Dockerfile: if the new tool is distributed as a GitHub (or HashiCorp) release binary, `hooks/_common.sh::common::resolve_tool_path` invokes this same script at hook run-time to support [`--hook-config=--tool-version=`](../README.md#most-hooks-pin-a-specific-tool-version). Wire your new hook to it the same way the existing binary-wrapping hooks are (look at how `terraform_tflint.sh` or `terrascan.sh` call `common::resolve_tool_path`), unless the tool has a different distribution model (e.g. pip, like `checkov`) - in that case, skip this and leave a comment explaining why, as `terraform_checkov.sh` does.
2. Add Docker structure tests to [`.github/.container-structure-test-config.yaml`](.container-structure-test-config.yaml)
3. Add new hook to [`.pre-commit-hooks.yaml`](../.pre-commit-hooks.yaml)
4. Create hook file. Don't forget to make it executable via `chmod +x /path/to/hook/file`.
Expand Down
101 changes: 101 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ If you want to support the development of `pre-commit-terraform` and [many other
* [All hooks: Set env vars inside hook at runtime](#all-hooks-set-env-vars-inside-hook-at-runtime)
* [All hooks: Disable color output](#all-hooks-disable-color-output)
* [All hooks: Log levels](#all-hooks-log-levels)
* [Most hooks: Pin a specific tool version](#most-hooks-pin-a-specific-tool-version)
* [Keeping pinned versions up-to-date using Renovate](#keeping-pinned-versions-up-to-date-using-renovate)
* [Many hooks: Parallelism](#many-hooks-parallelism)
* [checkov (deprecated) and terraform\_checkov](#checkov-deprecated-and-terraform_checkov)
* [infracost\_breakdown](#infracost_breakdown)
Expand All @@ -76,6 +78,7 @@ If you want to support the development of `pre-commit-terraform` and [many other
* [About Docker image security](#about-docker-image-security)
* [File Permissions](#file-permissions)
* [Download Terraform modules from private GitHub repositories](#download-terraform-modules-from-private-github-repositories)
* [Mount tools cache directory](#mount-tools-cache-directory)
* [GitHub Actions](#github-actions)
* [Authors](#authors)
* [License](#license)
Expand Down Expand Up @@ -433,6 +436,89 @@ PCT_LOG=trace pre-commit run -a

Less verbose log levels will be implemented in [#562](https://github.com/antonbabenko/pre-commit-terraform/issues/562).

### Most hooks: Pin a specific tool version

> All hooks, which wrap a tool distributed as a downloadable release asset. Not supported for `checkov`/`terraform_checkov` (distributed via PyPi) and for deprecated `terraform_docs_replace` hook.

1. You can pin a specific version of the wrapped tool per hook, independent of whatever is on your `$PATH` or baked into the Docker image. If that version isn't already cached locally, it's downloaded from the tool's GitHub releases on first use, then reused (without re-downloading) on every subsequent run.

@coderabbitai coderabbitai Bot Jul 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Avoid claiming every binary is downloaded from GitHub releases.

Terraform/OpenTofu resolution uses HashiCorp release installers, so this should say “the tool’s upstream release assets” (or similar) rather than specifically “GitHub releases.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 443, Update the version-pinning description near the
wrapped-tool download behavior to replace the GitHub-specific wording with “the
tool’s upstream release assets” or equivalent, while preserving the existing
caching and first-use download behavior.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't see an issue here. "Github release" is more undersandable, IMO.
@yermulnik ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"GitHub releases" describes the feature, but e.g. "GitHub releases page" (or with capitalized "Releases" to mark the section on the web-site) describes the actual location where we download assets from.
So I'd add "page" after "releases" to provide clarity. However, tech-savvies do indeed often say "Github releases" meaning "Github Releases page" =)
Up to you 👍

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


Config example:

```yaml
- id: terraform_tflint
args:
- --hook-config=--tool-version=0.50.0
Comment thread
Copilot marked this conversation as resolved.
```

2. The same `--tool-version` key also works for `terraform_validate`, `terraform_fmt` and `terraform_providers_lock`, which resolve their Terraform/OpenTofu binary through [`--tf-path`](#11-custom-terraform-binaries-and-opentofu-support) - by default it downloads/uses whichever of `terraform`/`opentofu` binary the rest of that precedence chain would otherwise have picked (`terraform`, unless it's missing from `$PATH` while `tofu` is present, in which case `opentofu`). To pick explicitly instead of relying on the auto-detection mechanism, set `--tf-path` to the literal value of `terraform`, `opentofu` or `tofu`:

```yaml
- id: terraform_validate
args:
- --hook-config=--tf-path=opentofu
- --hook-config=--tool-version=1.12.0
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

`--tf-path` and `--tool-version` either can be set on its own, or combined:

| `--tf-path` | `--tool-version` | Behavior |
| ------------------------------------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| set to a literal path/binary name | unset | Use that local binary as-is (normal [`--tf-path`](#11-custom-terraform-binaries-and-opentofu-support) behavior); `--tool-version` is ignored |
| unset | set | Auto-detect `terraform`/`opentofu` as described above, then download/use that tool at the pinned version |
| set to `terraform`, `opentofu`, or `tofu` | set | Download/use the pinned version of whichever of the two was selected |
| set to anything else (e.g. an actual path) | set | Error - combined with `--tool-version`, `--tf-path` only accepts `terraform`, `opentofu`, or `tofu` |
| unset | unset | Falls back to the normal [`--tf-path`](#11-custom-terraform-binaries-and-opentofu-support) precedence chain (env vars, then `$PATH`) |

> [!TIP]
> 3. Since this resolves to a version-specific cached binary rather than mutating `$PATH`, the same hook can be listed multiple times with different pinned versions, e.g. to test compatibility across tool versions in one run:
>
> ```yaml
> - id: terraform_tflint
> args:
> - --hook-config=--tool-version=0.50.0
> - id: terraform_tflint
> args:
> - --hook-config=--tool-version=0.55.0
> ```

4. By default, if a different version of the tool is already on `$PATH`, the pinned version still wins (with a warning message logged) based on `--hook-config=--tool-version-mode=strict`. Set it to `prefer-local` (as opposite to `strict`) to invert that: if the tool already resolves via `$PATH`, this local binary is used as-is and no download is attempted; the pinned version is only downloaded/used as a fallback when nothing is found locally.

```yaml
- id: terraform_tflint
args:
- --hook-config=--tool-version=0.50.0
- --hook-config=--tool-version-mode=prefer-local
```

5. By default, downloaded binaries are cached under `$XDG_CACHE_HOME/pre-commit-terraform/` (or `$HOME/.cache/pre-commit-terraform/` if `XDG_CACHE_HOME` is unset). Override this location with the `PCT_TOOL_CACHE_DIR` environment variable - see [Mount tools cache directory](#mount-tools-cache-directory) for the Docker case.

6. If a `GITHUB_TOKEN` environment variable is set, it's inherited automatically to authenticate GitHub API requests made during version resolution, the same way it already is utilized for [building your own Docker image](#docker-usage).

#### Keeping pinned versions up-to-date using Renovate

Neither Renovate's built-in [`pre-commit` manager](https://docs.renovatebot.com/modules/manager/pre-commit/) (which only understands `repo:`/`rev:` and `additional_dependencies` for Go/Node/Python) nor its `dockerfileVersions` preset can "look" inside hook's `args:`, so the `--tool-version` pin needs its own [`customManagers`](https://docs.renovatebot.com/modules/manager/regex/) entry in your own `renovate.json5` config file, using the same `# renovate: datasource=... depName=...` annotation convention commonly used for Dockerfile `ARG *_VERSION` pins:

```yaml
- id: terraform_tflint
args:
# renovate: datasource=github-releases depName=terraform-linters/tflint
- --hook-config=--tool-version=0.50.0
```

```json5
{
customManagers: [
{
customType: "regex",
managerFilePatterns: ["/\\.pre-commit-config\\.ya?ml$/"],
matchStrings: [
"# renovate: datasource=(?<datasource>\\S+) depName=(?<depName>\\S+)\\s+-\\s+--hook-config=--tool-version=(?<currentValue>\\S+)",
],
},
],
}
```

### Many hooks: Parallelism

> All, except deprecated hooks: `checkov`, `terraform_docs_replace` and hooks which can't be paralleled this way: `infracost_breakdown`, `terraform_wrapper_module_for_each`.
Expand Down Expand Up @@ -1276,6 +1362,21 @@ Finally, you can execute `docker run` with an additional volume mount so that th
docker run --rm -e "USERID=$(id -u):$(id -g)" -v ~/.netrc:/root/.netrc -v $(pwd):/lint -w /lint ghcr.io/antonbabenko/pre-commit-terraform:latest run -a
```

### Mount tools cache directory

A container's own filesystem is discarded after `docker run` exits, so a version downloaded via [`--tool-version`](#most-hooks-pin-a-specific-tool-version) would otherwise be re-downloaded on every single run. Mount the cache directory as a volume to persist it across runs, the same way you would for [`TF_PLUGIN_CACHE_DIR`](https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache):

```bash
TAG=latest
docker run \
-e "USERID=$(id -u):$(id -g)" \
-v ~/.cache/pre-commit-terraform:/root/.cache/pre-commit-terraform \
-v $(pwd):/lint -w /lint \
ghcr.io/antonbabenko/pre-commit-terraform:$TAG run -a
```

If you set `PCT_TOOL_CACHE_DIR` to a custom location, mount that path instead (and pass the same env var to the container with `-e PCT_TOOL_CACHE_DIR=...`).

## GitHub Actions

You can use this hook in your GitHub Actions workflow together with [pre-commit](https://pre-commit.com). To easy up
Expand Down
Loading
Loading