# Configuration file for `prek`, a git hook framework written in Rust. # See https://prek.j178.dev for more information. #:schema https://www.schemastore.org/prek.json # Every hook runs at commit time and at no other stage. A hook without `stages` runs at EVERY # stage, so a `.git/hooks/pre-push` shim left by an older `prek install` (from when the # `pytest` hook ran pre-push, removed 2026-10-02) re-ran all of them over the pushed range, # fixers included (#4018). Setting this here covers every checkout and any future shim; a hook # that genuinely belongs at another stage sets its own `stages`. # # default_stages does NOT reach a hook whose upstream manifest declares `stages` itself, and # pre-commit-hooks v6.0.0 lists `[pre-commit, pre-push, manual]` on three of ours. So every hook # from a remote repo repeats `stages = ["pre-commit"]`, which also covers a manifest that gains # `stages` on a Renovate bump. ENFORCED: ansible/tests/repo/test_prek_hooks_run_only_at_commit.py default_stages = ["pre-commit"] # Standard cleanup hooks [[repos]] repo = "https://github.com/pre-commit/pre-commit-hooks" rev = "v6.0.0" [[repos.hooks]] id = "trailing-whitespace" stages = ["pre-commit"] # A vendored upstream manifest under a role's files/upstream/ is pinned byte for byte to a # sha256 (scripts/dev/vendor_manifest.py), so no fixer may rewrite it. exclude = "^ansible/roles/[^/]+/[^/]+/files/upstream/" [[repos.hooks]] id = "end-of-file-fixer" stages = ["pre-commit"] exclude = "^ansible/roles/[^/]+/[^/]+/files/upstream/" [[repos.hooks]] id = "check-yaml" stages = ["pre-commit"] # Flux's vendored install is a multi-document stream, which this hook refuses without # --allow-multiple-documents. Allowing that repo-wide would pass a stray `---` in every other # file, so the one stream is excluded instead: the validate-k8s-manifests hook parses its # render, and ansible/tests/k8s/test_flux_role.py reads it. # An upstream file under files/upstream/ is such a stream too, and its own pin covers it. exclude = "^ansible/roles/(k8s/flux/files/gotk-components\\.yaml$|[^/]+/[^/]+/files/upstream/)" [[repos.hooks]] # Guards renovate.json (a syntax error there silently disables ALL dependency management) # and every other JSON in the repo (Grafana dashboards, settings). id = "check-json" stages = ["pre-commit"] [[repos.hooks]] # prek.toml and pyproject.toml configure every other gate; a TOML syntax error in either # fails those gates with a parser traceback rather than a pointer at the line. id = "check-toml" stages = ["pre-commit"] [[repos.hooks]] id = "check-merge-conflict" stages = ["pre-commit"] [[repos.hooks]] id = "check-added-large-files" stages = ["pre-commit"] [[repos.hooks]] id = "mixed-line-ending" stages = ["pre-commit"] args = ["--fix=lf"] exclude = "^ansible/roles/[^/]+/[^/]+/files/upstream/" # Ansible linting [[repos]] repo = "https://github.com/ansible/ansible-lint" rev = "v26.10.1" [[repos.hooks]] id = "ansible-lint" stages = ["pre-commit"] # Scoped to `ansible/` and paired with pass_filenames — ansible-lint lints the files it is # GIVEN instead of walking the tree itself. Its cost is almost purely per-file (measured: # one task file 1.45s, five 1.98s, the whole repo 37s locally / 89.7s on the # runner), so handing it a diff is what makes a PR's lint ~2s instead of ~90s. # # Coverage is unchanged on a full run. A walk examines 954 files, but exactly 325 of those are # YAML and all 325 are tracked files under `ansible/` — the rest are .py/.j2/.sh that # ansible-lint classifies and no ansible rule applies to. That 325 is precisely what this # files/exclude pair passes. # # The regex must do its own excluding: `.ansible-lint`'s exclude_paths are NOT honoured for # explicitly-passed files (verified — it happily lints `.github/workflows/ci.yml` when named # directly). `ansible/collections/` needs no exclude here because it is gitignored and prek # only ever passes tracked files, but secrets.yml is tracked AND encrypted, so it does. files = "^ansible/.*\\.ya?ml$" # home-assistant/files/ is Home Assistant config, not Ansible: configuration.yaml carries HA's # own YAML tags (`!include`, `!include_dir_merge_list`, `!secret`), which ansible-lint's # loader cannot construct, and the validate-ha-config hook below is that tree's linter. # flux/files/gotk-components.yaml is Flux's install as `flux install --export` writes it, never # edited, so a regeneration diffs as upstream's change alone; its block-sequence indentation # trips yaml[indentation]. The validate-k8s-manifests hook validates its render. # vars/cluster-manifests/ holds SOPS-encrypted Secret manifests (target slice D3). A YAML file # under a `vars/` directory reads to ansible-lint as a vars file, which fails on `apiVersion`. # They stay under ansible/vars/ because the creation rules in ansible/.sops.yaml and the # runbooks point there, and `cluster_vars.py check` validates them instead. # files/upstream/ holds upstream manifests byte for byte, which this repo's lint rules do not # govern (scripts/dev/vendor_manifest.py). exclude ="^ansible/(vars/secrets\\.yml|vars/cluster-manifests/.*|roles/k8s/home-assistant/files/.*|roles/k8s/flux/files/gotk-components\\.yaml|roles/[^/]+/[^/]+/files/upstream/.*)$" # Cap ansible-core <3 so a surprise upstream major can't silently change lint behavior # or break this hook on a day nothing in the repo changed (these deps aren't lock-pinned). additional_dependencies = ["ansible-core>=2.18,<3.0", "jmespath"] # NB the `--` sentinel and `"$@"`, same as bash-syntax-check below: prek appends the filenames # to the entry, and after `bash -c '