Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork577
pre-commit git hooks to take care of Terraform configurations 🇺🇦
License
antonbabenko/pre-commit-terraform
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Collection of git hooks for Terraform to be used withpre-commit framework
pre-commit-terraform provides a collection ofGit Hooks for Terraform and related tools and is driven by thepre-commit framework. It helps ensure that Terraform, OpenTofu, and Terragrunt configurations are kept in good shape by automatically running various checks and formatting code before committing changes to version control system. This helps maintain code quality and consistency across the project.
It can be run:
- Locally and in CI
- As standalone Git hooks or as a Docker image
- For the entire repository or just for change-related files (e.g., local git stash, last commit, or all changes in a Pull Request)
Want to contribute?Checkopen issuesandcontributing notes.
If you want to support the development ofpre-commit-terraform andmany other open-source projects, please become aGitHub Sponsor!
- Sponsors
- Table of content
- How to install
- Available Hooks
- Hooks usage notes and examples
- Known limitations
- All hooks: Usage of environment variables in
--args - All hooks: Usage of
__GIT_WORKING_DIR__placeholder in--args - All hooks: Set env vars inside hook at runtime
- All hooks: Disable color output
- All hooks: Log levels
- Many hooks: Parallelism
- checkov (deprecated) and terraform_checkov
- infracost_breakdown
- terraform_docs
- terraform_docs_replace (deprecated)
- terraform_fmt
- terraform_providers_lock
- terraform_tflint
- terraform_tfsec (deprecated)
- terraform_trivy
- terraform_validate
- terraform_wrapper_module_for_each
- terrascan
- tfupdate
- terragrunt_providers_lock
- terragrunt_validate_inputs
- Docker Usage
- GitHub Actions
- Authors
- License
Docker
Pull docker image with all hooks:
TAG=latestdocker pull ghcr.io/antonbabenko/pre-commit-terraform:$TAGAll available tagshere.
CheckAbout Docker image security section to learn more about possible security issues and why you probably want to build and maintain your own image.
Build from scratch:
IMPORTANT
To build image you need to havedocker buildxenabled as default builder.
Otherwise - provideTARGETOSandTARGETARCHas additional--build-arg's todocker build.
When hooks-related--build-args are not specified, only the latest version ofpre-commit andterraform will be installed.
git clone git@github.com:antonbabenko/pre-commit-terraform.gitcd pre-commit-terraform# Install the latest versions of all the toolsdocker build -t pre-commit-terraform --build-arg INSTALL_ALL=true.
To install a specific version of individual tools, define it using--build-arg arguments or set it tolatest:
docker build -t pre-commit-terraform \ --build-arg PRE_COMMIT_VERSION=latest \ --build-arg OPENTOFU_VERSION=latest \ --build-arg TERRAFORM_VERSION=1.5.7 \ --build-arg CHECKOV_VERSION=2.0.405 \ --build-arg HCLEDIT_VERSION=latest \ --build-arg INFRACOST_VERSION=latest \ --build-arg TERRAFORM_DOCS_VERSION=0.15.0 \ --build-arg TERRAGRUNT_VERSION=latest \ --build-arg TERRASCAN_VERSION=1.10.0 \ --build-arg TFLINT_VERSION=0.31.0 \ --build-arg TFSEC_VERSION=latest \ --build-arg TFUPDATE_VERSION=latest \ --build-arg TRIVY_VERSION=latest \.Set-e PRE_COMMIT_COLOR=never to disable the color output inpre-commit.
NOTEThe build install scripts are calling the GitHub API to resolve the release URL. If you need to authenticate those calls, you can pass a GitHub token (the
GITHUB_TOKENenvironment variable is expected to be set with anaccess token):docker build -t pre-commit-terraform --build-arg GITHUB_TOKEN.
MacOS
brew install pre-commit terraform-docs tflint tfsec trivy checkov terrascan infracost tfupdate minamijoyo/hcledit/hcledit jq
Ubuntu 18.04
sudo apt updatesudo apt install -y unzip software-properties-commonsudo add-apt-repository ppa:deadsnakes/ppasudo apt install -y python3.7 python3-pippython3 -m pip install --upgrade pippip3 install --no-cache-dir pre-commitpython3.7 -m pip install -U checkovcurl -L"$(curl -s https://api.github.com/repos/terraform-docs/terraform-docs/releases/latest| grep -o -E -m 1"https://.+?-linux-amd64.tar.gz")"> terraform-docs.tgz&& tar -xzf terraform-docs.tgz&& rm terraform-docs.tgz&& chmod +x terraform-docs&& sudo mv terraform-docs /usr/bin/curl -L"$(curl -s https://api.github.com/repos/terraform-linters/tflint/releases/latest| grep -o -E -m 1"https://.+?_linux_amd64.zip")"> tflint.zip&& unzip tflint.zip&& rm tflint.zip&& sudo mv tflint /usr/bin/curl -L"$(curl -s https://api.github.com/repos/aquasecurity/tfsec/releases/latest| grep -o -E -m 1"https://.+?tfsec-linux-amd64")"> tfsec&& chmod +x tfsec&& sudo mv tfsec /usr/bin/curl -L"$(curl -s https://api.github.com/repos/aquasecurity/trivy/releases/latest| grep -o -E -i -m 1"https://.+?/trivy_.+?_Linux-64bit.tar.gz")"> trivy.tar.gz&& tar -xzf trivy.tar.gz trivy&& rm trivy.tar.gz&& sudo mv trivy /usr/bincurl -L"$(curl -s https://api.github.com/repos/tenable/terrascan/releases/latest| grep -o -E -m 1"https://.+?_Linux_x86_64.tar.gz")"> terrascan.tar.gz&& tar -xzf terrascan.tar.gz terrascan&& rm terrascan.tar.gz&& sudo mv terrascan /usr/bin/&& terrascan initsudo apt install -y jq&& \curl -L"$(curl -s https://api.github.com/repos/infracost/infracost/releases/latest| grep -o -E -m 1"https://.+?-linux-amd64.tar.gz")"> infracost.tgz&& tar -xzf infracost.tgz&& rm infracost.tgz&& sudo mv infracost-linux-amd64 /usr/bin/infracost&& infracost auth logincurl -L"$(curl -s https://api.github.com/repos/minamijoyo/tfupdate/releases/latest| grep -o -E -m 1"https://.+?_linux_amd64.tar.gz")"> tfupdate.tar.gz&& tar -xzf tfupdate.tar.gz tfupdate&& rm tfupdate.tar.gz&& sudo mv tfupdate /usr/bin/curl -L"$(curl -s https://api.github.com/repos/minamijoyo/hcledit/releases/latest| grep -o -E -m 1"https://.+?_linux_amd64.tar.gz")"> hcledit.tar.gz&& tar -xzf hcledit.tar.gz hcledit&& rm hcledit.tar.gz&& sudo mv hcledit /usr/bin/
Ubuntu 20.04+
sudo apt updatesudo apt install -y unzip software-properties-common python3 python3-pip python-is-python3python3 -m pip install --upgrade pippip3 install --no-cache-dir pre-commitpip3 install --no-cache-dir checkovcurl -L"$(curl -s https://api.github.com/repos/terraform-docs/terraform-docs/releases/latest| grep -o -E -m 1"https://.+?-linux-amd64.tar.gz")"> terraform-docs.tgz&& tar -xzf terraform-docs.tgz terraform-docs&& rm terraform-docs.tgz&& chmod +x terraform-docs&& sudo mv terraform-docs /usr/bin/curl -L"$(curl -s https://api.github.com/repos/tenable/terrascan/releases/latest| grep -o -E -m 1"https://.+?_Linux_x86_64.tar.gz")"> terrascan.tar.gz&& tar -xzf terrascan.tar.gz terrascan&& rm terrascan.tar.gz&& sudo mv terrascan /usr/bin/&& terrascan initcurl -L"$(curl -s https://api.github.com/repos/terraform-linters/tflint/releases/latest| grep -o -E -m 1"https://.+?_linux_amd64.zip")"> tflint.zip&& unzip tflint.zip&& rm tflint.zip&& sudo mv tflint /usr/bin/curl -L"$(curl -s https://api.github.com/repos/aquasecurity/tfsec/releases/latest| grep -o -E -m 1"https://.+?tfsec-linux-amd64")"> tfsec&& chmod +x tfsec&& sudo mv tfsec /usr/bin/curl -L"$(curl -s https://api.github.com/repos/aquasecurity/trivy/releases/latest| grep -o -E -i -m 1"https://.+?/trivy_.+?_Linux-64bit.tar.gz")"> trivy.tar.gz&& tar -xzf trivy.tar.gz trivy&& rm trivy.tar.gz&& sudo mv trivy /usr/binsudo apt install -y jq&& \curl -L"$(curl -s https://api.github.com/repos/infracost/infracost/releases/latest| grep -o -E -m 1"https://.+?-linux-amd64.tar.gz")"> infracost.tgz&& tar -xzf infracost.tgz&& rm infracost.tgz&& sudo mv infracost-linux-amd64 /usr/bin/infracost&& infracost auth logincurl -L"$(curl -s https://api.github.com/repos/minamijoyo/tfupdate/releases/latest| grep -o -E -m 1"https://.+?_linux_amd64.tar.gz")"> tfupdate.tar.gz&& tar -xzf tfupdate.tar.gz tfupdate&& rm tfupdate.tar.gz&& sudo mv tfupdate /usr/bin/curl -L"$(curl -s https://api.github.com/repos/minamijoyo/hcledit/releases/latest| grep -o -E -m 1"https://.+?_linux_amd64.tar.gz")"> hcledit.tar.gz&& tar -xzf hcledit.tar.gz hcledit&& rm hcledit.tar.gz&& sudo mv hcledit /usr/bin/
Windows 10/11
We highly recommend usingWSL/WSL2 with Ubuntu and following the Ubuntu installation guide. Or use Docker.
IMPORTANT
We won't be able to help with issues that can't be reproduced in Linux/Mac.
So, try to find a working solution and send PR before open an issue.
Otherwise, you can followthis gist:
Ensure your PATH environment variable looks forbash.exe inC:\Program Files\Git\bin (the one present inC:\Windows\System32\bash.exe does not work withpre-commit.exe)
Forcheckov, you may need to also set yourPYTHONPATH environment variable with the path to your Python modules.
E.g.C:\Users\USERNAME\AppData\Local\Programs\Python\Python39\Lib\site-packages
Full list of dependencies and where they are used:
pre-commit,terraformoropentofu,git,BASH3.2.57or newer,Internet connection (on first run),x86_64 or arm64 compatible operating system,Some hardware where this OS will run,Electricity for hardware and internet connection,Some basic physical laws,Hope that it all will work.checkovrequired forterraform_checkovhookterraform-docs0.12.0+ required forterraform_docshookterragruntrequired forterragrunt_validateandterragrunt_valid_inputshooksterrascanrequired forterrascanhookTFLintrequired forterraform_tflinthookTFSecrequired forterraform_tfsechookTrivyrequired forterraform_trivyhookinfracostrequired forinfracost_breakdownhookjqrequired forterraform_validatewith--retry-once-with-cleanupflag, and forinfracost_breakdownhooktfupdaterequired fortfupdatehookhcleditrequired forterraform_wrapper_module_for_eachhook
It is possible to set custom path toterraform binary.
This makes it possible to useOpenTofu binary (tofu) instead ofterraform.
How binary discovery works and how you can redefine it (first matched takes precedence):
- Check if per hook configuration
--hook-config=--tf-path=<path_to_binary_or_binary_name>is set - Check if
PCT_TFPATH=<path_to_binary_or_binary_name>environment variable is set - Check if
TERRAGRUNT_TFPATH=<path_to_binary_or_binary_name>environment variable is set - Check if
terraformbinary can be found in the user's$PATH - Check if
tofubinary can be found in the user's$PATH
Note
Not needed if you use the Docker image
DIR=~/.git-templategit config --global init.templateDir${DIR}pre-commit init-templatedir -t pre-commit${DIR}
Step into the repository you want to have the pre-commit hooks installed and run:
git initcat<<EOF > .pre-commit-config.yamlrepos:- repo: https://github.com/antonbabenko/pre-commit-terraform rev: <VERSION> # Get the latest from: https://github.com/antonbabenko/pre-commit-terraform/releases hooks: - id: terraform_fmt - id: terraform_docsEOF
If this repository was initialized locally viagit init orgit clonebeforeyou installed the pre-commit hook globally (step 2),you will need to run:
pre-commit install
Execute this command to runpre-commit on all files in the repository (not only changed files):
pre-commit run -a
Or, using Docker (available tags):
Tip
This command uses your user id and group id for the docker container to use to access the local files. If the files are owned by another user, update theUSERID environment variable. SeeFile Permissions section for more information.
TAG=latestdocker run -e"USERID=$(id -u):$(id -g)" -v"$(pwd):/lint" -w"/lint""ghcr.io/antonbabenko/pre-commit-terraform:$TAG" run -a
Execute this command to list the versions of the tools in Docker:
TAG=latestdocker run --rm --entrypoint cat ghcr.io/antonbabenko/pre-commit-terraform:$TAG /usr/bin/tools_versions_infoThere are severalpre-commit hooks to keep Terraform configurations (both*.tf and*.tfvars) and Terragrunt configurations (*.hcl) in a good shape:
| Hook name | Description | Dependencies Install instructions here |
|---|---|---|
checkov andterraform_checkov | checkov static analysis of terraform templates to spot potential security issues.Hook notes | checkovUbuntu deps: python3,python3-pip |
infracost_breakdown | Check how much your infra costs withinfracost.Hook notes | infracost,jq,Infracost API key |
terraform_docs | Inserts input and output documentation intoREADME.md.Hook notes | terraform-docs |
terraform_docs_replace | Runsterraform-docs and pipes the output directly to README.md.DEPRECATED, see#248.Hook notes | python3,terraform-docs |
terraform_docs_without_aggregate_type_defaults | Inserts input and output documentation intoREADME.md without aggregate type defaults. Hook notes same as forterraform_docs | terraform-docs |
terraform_fmt | Reformat all Terraform configuration files to a canonical format.Hook notes | - |
terraform_providers_lock | Updates provider signatures independency lock files.Hook notes | - |
terraform_tflint | Validates all Terraform configuration files withTFLint.Available TFLint rules.Hook notes. | tflint |
terraform_tfsec | TFSec static analysis of terraform templates to spot potential security issues.DEPRECATED, useterraform_trivy.Hook notes | tfsec |
terraform_trivy | Trivy static analysis of terraform templates to spot potential security issues.Hook notes | trivy |
terraform_validate | Validates all Terraform configuration files.Hook notes | jq, only for--retry-once-with-cleanup flag |
terragrunt_fmt | Reformat allTerragrunt configuration files (*.hcl) to a canonical format. | terragrunt |
terragrunt_validate | Validates allTerragrunt configuration files (*.hcl) | terragrunt |
terragrunt_validate_inputs | ValidatesTerragrunt unused and undefined inputs (*.hcl) | |
terragrunt_providers_lock | Generates.terraform.lock.hcl files usingTerragrunt. | terragrunt |
terraform_wrapper_module_for_each | Generates Terraform wrappers withfor_each in module.Hook notes | hcledit |
terrascan | terrascan Detect compliance and security violations.Hook notes | terrascan |
tfupdate | tfupdate Update version constraints of Terraform core, providers, and modules.Hook notes | tfupdate |
Check thesource file to know arguments used for each hook.
Terraform operates on a per-dir basis, whilepre-commit framework only supports files and files that exist. This means if you only remove the TF-related file without any other changes in the same dir, checks will be skipped. Example and detailshere.
All, except deprecated hooks:
checkov,terraform_docs_replace
You can use environment variables for the--args section.
Important
Youmust use the${ENV_VAR} definition,$ENV_VAR will not expand.
Config example:
-id:terraform_tflintargs: ---args=--config=${CONFIG_NAME}.${CONFIG_EXT} ---args=--call-module-type="all"
If for config above set upexport CONFIG_NAME=.tflint; export CONFIG_EXT=hcl beforepre-commit run, args will be expanded to--config=.tflint.hcl --call-module-type="all".
All, except deprecated hooks:
checkov,terraform_docs_replace
You can use__GIT_WORKING_DIR__ placeholder in--args. It will be replacedby the Git working directory (repo root) at run time.
For instance, if you have multiple directories and want to runterraform_tflint in all of them while sharing a single config file — use the__GIT_WORKING_DIR__ placeholder in the file path. For example:
-id:terraform_tflintargs: ---args=--config=__GIT_WORKING_DIR__/.tflint.hcl
All, except deprecated hooks:
checkov,terraform_docs_replace
You can specify environment variables that will be passed to the hook at runtime.
Important
Variable values are exportedverbatim:
- No interpolation or expansion are applied
- The enclosing double quotes are removed if they are provided
Config example:
-id:terraform_validateargs: ---env-vars=AWS_DEFAULT_REGION="us-west-2" ---env-vars=AWS_PROFILE="my-aws-cli-profile"
All, except deprecated hooks:
checkov,terraform_docs_replace
To disable color output for all hooks, setPRE_COMMIT_COLOR=never var. Eg:
PRE_COMMIT_COLOR=never pre-commit run
In case you need to debug hooks, you can setPCT_LOG=trace.
For example:
PCT_LOG=trace pre-commit run -a
Less verbose log levels will be implemented in#562.
All, except deprecated hooks:
checkov,terraform_docs_replaceand hooks which can't be paralleled this way:infracost_breakdown,terraform_wrapper_module_for_each.
Also, there's a chance that parallelism have no effect onterragrunt_fmtandterragrunt_validatehooks
By default, parallelism is set tonumber of logical CPUs - 1.
If you'd like to disable parallelism, set it to1
-id:terragrunt_validateargs: ---hook-config=--parallelism-limit=1
In the same way you can set it to any positive integer.
If you'd like to set parallelism value relative to number of CPU logical cores - provide valid Bash arithmetic expression and useCPU as a reference to the number of CPU logical cores
-id:terraform_providers_lockargs: ---hook-config=--parallelism-limit=CPU*4
Tip
Info useful for parallelism fine-tunning
Tests below were run on repo with 45 Terraform dirs on laptop with 16 CPUs, SSD and 1Gbit/s network. Laptop was slightly used in the process.
Observed results may vary greatly depending on your repo structure, machine characteristics and their usage.
If during fine-tuning you'll find that your results are very different from provided below and you think that this data could help someone else - feel free to send PR.
| Hook | Most used resource | Comparison of optimization results / Notes |
|---|---|---|
| terraform_checkov | CPU heavy | - |
| terraform_fmt | CPU heavy | - |
terraform_providers_lock (3 platforms,--mode=always-regenerate-lockfile) | Network & Disk heavy | defaults (CPU-1) - 3m 39s;CPU*2 - 3m 19s;CPU*4 - 2m 56s |
| terraform_tflint | CPU heavy | - |
| terraform_tfsec | CPU heavy | - |
| terraform_trivy | CPU moderate | defaults (CPU-1) - 32s;CPU*2 - 30s;CPU*4 - 31s |
| terraform_validate (t validate only) | CPU heavy | - |
| terraform_validate (t init + t validate) | Network & Disk heavy, CPU moderate | defaults (CPU-1) - 1m 30s;CPU*2 - 1m 25s;CPU*4 - 1m 41s |
| terragrunt_fmt | CPU heavy | N/A? need more info from TG users |
| terragrunt_validate | CPU heavy | N/A? need more info from TG users |
| terrascan | CPU moderate-heavy | defaults (CPU-1) - 8s;CPU*2 - 6s |
| tfupdate | Disk/Network? | too quick in any settings. More info needed |
args: ---hook-config=--parallelism-ci-cpu-cores=N
If you don't see code above in yourpre-commit-config.yaml or logs - you don't need it.--parallelism-ci-cpu-cores used only in edge cases and is ignored in other situations. Check out its usage inhooks/_common.sh
checkovhook is deprecated, please useterraform_checkov.
Note thatterraform_checkov runs recursively during-d . usage. That means, for example, if you change.tf file in repo root, all existing.tf files in the repo will be checked.
You can specify custom arguments. E.g.:
-id:terraform_checkovargs: ---args=--quiet ---args=--skip-check CKV2_AWS_8
Check all available argumentshere.
For deprecated hook you need to specify each argument separately:
-id:checkovargs:["-d", ".","--skip-check", "CKV2_AWS_8",]
infracost_breakdown executesinfracost breakdown command and compare the estimated costs with those specified in the hook-config.infracost breakdown parses Terraform HCL code, and calls Infracost Cloud Pricing API (remote version orself-hosted version).
Unlike most other hooks, this hook triggers once if there are any changed files in the repository.
infracost_breakdownsupports allinfracost breakdownarguments (runinfracost breakdown --helpto see them). The following example only shows costs:-id:infracost_breakdownargs: ---args=--path=./env/devverbose:true# Always show costs
Output
Runningin"env/dev"Summary: {"unsupportedResourceCounts": {"aws_sns_topic_subscription": 1 }}Total Monthly Cost: 86.83 USDTotal Monthly Cost (diff): 86.83 USD
Note that spaces are not allowed in
--args, so you need to split it, like this:-id:infracost_breakdownargs: ---args=--path=./env/dev ---args=--terraform-var-file="terraform.tfvars" ---args=--terraform-var-file="../terraform.tfvars"
(Optionally) Define
cost constraintsthe hook should evaluate successfully in order to pass:-id:infracost_breakdownargs: ---args=--path=./env/dev ---hook-config='.totalHourlyCost|tonumber > 0.1' ---hook-config='.totalHourlyCost|tonumber > 1' ---hook-config='.projects[].diff.totalMonthlyCost|tonumber != 10000' ---hook-config='.currency == "USD"'
Output
Runningin"env/dev"Passed: .totalHourlyCost|tonumber> 0.1 0.11894520547945205> 0.1Failed: .totalHourlyCost|tonumber> 1 0.11894520547945205> 1Passed: .projects[].diff.totalMonthlyCost|tonumber!=10000 86.83!= 10000Passed: .currency =="USD""USD" =="USD"Summary: {"unsupportedResourceCounts": {"aws_sns_topic_subscription": 1 }}Total Monthly Cost: 86.83 USDTotal Monthly Cost (diff): 86.83 USD
- Only one path per one hook (
- id: infracost_breakdown) is allowed. - Set
verbose: trueto see cost even when the checks are passed. - Hook uses
jqto process the cost estimation report returned byinfracost breakdowncommand - Expressions defined as
--hook-configargument should be in a jq-compatible format (e.g..totalHourlyCost,.totalMonthlyCost)To study json output produced byinfracost, run the commandinfracost breakdown -p PATH_TO_TF_DIR --format json, and explore it onjqplay.org. - Supported comparison operators:
<,<=,==,!=,>=,>. - Most useful paths and checks:
.totalHourlyCost(same as.projects[].breakdown.totalHourlyCost) - show total hourly infra cost.totalMonthlyCost(same as.projects[].breakdown.totalMonthlyCost) - show total monthly infra cost.projects[].diff.totalHourlyCost- show the difference in hourly cost for the existing infra and tf plan.projects[].diff.totalMonthlyCost- show the difference in monthly cost for the existing infra and tf plan.diffTotalHourlyCost(for Infracost version 0.9.12 or newer) or[.projects[].diff.totalMonthlyCost | select (.!=null) | tonumber] | add(for Infracost older than 0.9.12)
- Only one path per one hook (
Docker usage. In
docker buildordocker runcommand:- You need to provideInfracost API key via
-e INFRACOST_API_KEY=<your token>. By default, it is saved in~/.config/infracost/credentials.yml - Set
-e INFRACOST_SKIP_UPDATE_CHECK=truetoskip the Infracost update check if you use this hook as part of your CI/CD pipeline.
- You need to provideInfracost API key via
terraform_docsandterraform_docs_without_aggregate_type_defaultswill insert/update documentation generated byterraform-docs framed by markers:<!-- BEGIN_TF_DOCS --><!-- END_TF_DOCS -->
if they are present in
README.md.It is possible to pass additional arguments to shell scripts when using
terraform_docsandterraform_docs_without_aggregate_type_defaults.It is possible to automatically:
create a documentation file
extend existing documentation file by appending markers to the end of the file (see item 1 above)
use different filename for the documentation (default is
README.md)use the same insertion markers as
terraform-docs. It's default starting fromv1.93.
To migrate everything toterraform-docsinsertion markers, run in repo root:sed --version&> /dev/null&& SED_CMD=(sed -i)|| SED_CMD=(sed -i'')grep -rl --null'BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK'.| xargs -0"${SED_CMD[@]}" -e's/BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK/BEGIN_TF_DOCS/'grep -rl --null'END OF PRE-COMMIT-TERRAFORM DOCS HOOK'.| xargs -0"${SED_CMD[@]}" -e's/END OF PRE-COMMIT-TERRAFORM DOCS HOOK/END_TF_DOCS/'
-id:terraform_docsargs: ---hook-config=--path-to-file=README.md# Valid UNIX path. I.e. ../TFDOC.md or docs/README.md etc. ---hook-config=--add-to-existing-file=true# Boolean. true or false ---hook-config=--create-file-if-not-exist=true# Boolean. true or false ---hook-config=--use-standard-markers=true# Boolean. Defaults to true (v1.93+), false (<v1.93). Set to true for compatibility with terraform-docs# The following two options "--custom-marker-begin" and "--custom-marker-end" are ignored if "--use-standard-markers" is set to false ---hook-config=--custom-marker-begin=<!-- BEGIN_TF_DOCS --># String.# Set to use custom marker which helps you with using other formats like asciidoc.# For Asciidoc this could be "--hook-config=--custom-marker-begin=// BEGIN_TF_DOCS" ---hook-config=--custom-marker-end=<!-- END_TF_DOCS --># String.# Set to use custom marker which helps you with using other formats like asciidoc.# For Asciidoc this could be "--hook-config=--custom-marker-end=// END_TF_DOCS" ---hook-config=--custom-doc-header="# "# String. Defaults to "# "# Set to use custom marker which helps you with using other formats like asciidoc.# For Asciidoc this could be "--hook-config=--custom-marker-end=\= "
If you want to use a terraform-docs config file, you must supply the path to the file, relative to the git repo root path:
-id:terraform_docsargs: ---args=--config=.terraform-docs.yml
Warning
Avoid userecursive.enabled: truein config file, that can cause unexpected behavior.You can provideany configuration available in
terraform-docsas an argument toterraform_docshook:-id:terraform_docsargs: ---args=--output-mode=replace
If you need some exotic settings, it can be done too. I.e. this one generates HCL files:
-id:terraform_docsargs: -tfvars hcl --output-file terraform.tfvars.model .
DEPRECATED. Will be merged interraform_docs.
terraform_docs_replace replaces the entireREADME.md rather than doing string replacement between markers. Put your additional documentation at the top of yourmain.tf for it to be pulled in.
To replicate functionality interraform_docs hook:
Create
.terraform-docs.ymlin the repo root with the following content:formatter:"markdown"output:file:"README.md"mode:replacetemplate:|- {{/** End of file fixer */}}
Replace
terraform_docs_replacehook config in.pre-commit-config.yamlwith:-id:terraform_docsargs: ---args=--config=.terraform-docs.yml
terraform_fmt supports custom arguments so you can passsupported flags. Eg:
-id:terraform_fmtargs: ---args=-no-color ---args=-diff ---args=-write=false
Note
The hook requires Terraform 0.14 or later.
Note
The hook can invoketerraform providers lock that can be really slow and requires fetching metadata from remote Terraform registries - not all of that metadata is currently being cached by Terraform.
Note
Read this if you used this hook before v1.80.0 | Planned breaking changes in v2.0
We introduced `--mode` flag for this hook. If you'd like to continue using this hook as before, please:
- Specify
--hook-config=--mode=always-regenerate-lockfileinargs: - Before
terraform_providers_lock, addterraform_validatehook with--hook-config=--retry-once-with-cleanup=true - Move
--tf-init-args=toterraform_validatehook
In the end, you should get config like this:
-id:terraform_validateargs: ---hook-config=--retry-once-with-cleanup=true# - --tf-init-args=-upgrade-id:terraform_providers_lockargs: ---hook-config=--mode=always-regenerate-lockfile
Why? When v2.x will be introduced - the default mode will be changed, probably, toonly-check-is-current-lockfile-cross-platform.
You can check available modes for hook below.
The hook can work in a few different modes:
only-check-is-current-lockfile-cross-platformwith and withoutterraform_validate hook andalways-regenerate-lockfile- only with terraform_validate hook.only-check-is-current-lockfile-cross-platformwithout terraform_validate - only checks that lockfile has all required SHAs for all providers already added to lockfile.-id:terraform_providers_lockargs: ---hook-config=--mode=only-check-is-current-lockfile-cross-platform
only-check-is-current-lockfile-cross-platformwithterraform_validate hook - make up-to-date lockfile by adding/removing providers and only then check that lockfile has all required SHAs.ImportantNext
terraform_validateflag requires additional dependency to be installed:jq. Also, it could run another slow and time consuming command -terraform init-id:terraform_validateargs: ---hook-config=--retry-once-with-cleanup=true-id:terraform_providers_lockargs: ---hook-config=--mode=only-check-is-current-lockfile-cross-platform
always-regenerate-lockfileonly withterraform_validate hook - regenerate lockfile from scratch. Can be useful for upgrading providers in lockfile to latest versions-id:terraform_validateargs: ---hook-config=--retry-once-with-cleanup=true ---tf-init-args=-upgrade-id:terraform_providers_lockargs: ---hook-config=--mode=always-regenerate-lockfile
terraform_providers_locksupports custom arguments:-id:terraform_providers_lockargs: ---args=-platform=windows_amd64 ---args=-platform=darwin_amd64
It may happen that Terraform working directory (
.terraform) already exists but not in the best condition (eg, not initialized modules, wrong version of Terraform, etc.). To solve this problem, you can find and delete all.terraformdirectories in your repository:echo"function rm_terraform { find . \( -iname".terraform*" ! -iname".terraform-docs*" \) -print0 | xargs -0 rm -r}">>~/.bashrc# Reload shell and use `rm_terraform` command in the repo root
terraform_providers_lockhook will try to reinitialize directories before running theterraform providers lockcommand.terraform_providers_locksupport passing custom arguments to itsterraform init:Warning
DEPRECATION NOTICE: This is available only inno-modemode, which will be removed in v2.0. Please provide this keys toterraform_validatehook, which, to take effect, should be called beforeterraform_providers_lock-id:terraform_providers_lockargs: ---tf-init-args=-upgrade
terraform_tflintsupports custom arguments so you can enable module inspection, enable / disable rules, etc.Example:
-id:terraform_tflintargs: ---args=--module ---args=--enable-rule=terraform_documented_variables
By default, pre-commit-terraform performs directory switching into the terraform modules for you. If you want to delegate the directory changing to the binary - this will allow tflint to determine the full paths for error/warning messages, rather than just module relative paths.Note: this requires
tflint>=0.44.0. For example:-id:terraform_tflintargs: ---hook-config=--delegate-chdir
DEPRECATED.tfsec was replaced by trivy, so please useterraform_trivy.
terraform_tfsecwill consume modified files that pre-commitpasses to it, so you can perform whitelisting of directoriesor files to run against viafilespre-commit flagExample:
-id:terraform_tfsecfiles:^prd-infra/
The above will tell pre-commit to pass down files from the
prd-infra/folderonly such that the underlyingtfsectool can run against changed files in thisdirectory, ignoring any other folders at the root levelTo ignore specific warnings, follow the convention from thedocumentation.
Example:
resource"aws_security_group_rule""my-rule" {type="ingress"cidr_blocks=["0.0.0.0/0"]#tfsec:ignore:AWS006}
terraform_tfsecsupports custom arguments, so you can pass supported--no-coloror--format(output),-e(exclude checks) flags:-id:terraform_tfsecargs: -> --args=--format json --no-color -e aws-s3-enable-bucket-logging,aws-s3-specify-public-access-block
terraform_trivywill consume modified files that pre-commitpasses to it, so you can perform whitelisting of directoriesor files to run against viafilespre-commit flagExample:
-id:terraform_trivyfiles:^prd-infra/
The above will tell pre-commit to pass down files from the
prd-infra/folderonly such that the underlyingtrivytool can run against changed files in thisdirectory, ignoring any other folders at the root levelTo ignore specific warnings, follow the convention from thedocumentation.
Example:
#trivy:ignore:AVD-AWS-0107#trivy:ignore:AVD-AWS-0124resource"aws_security_group_rule""my-rule" {type="ingress"cidr_blocks=["0.0.0.0/0"]}
terraform_trivysupports custom arguments, so you can pass supported--format(output),--skip-dirs(exclude directories) and other flags:-id:terraform_trivyargs: ---args=--format=json ---args=--skip-dirs="**/.terraform"
Important
If you useTF_PLUGIN_CACHE_DIR, we recommend enabling--hook-config=--retry-once-with-cleanup=true or disabling parallelism (--hook-config=--parallelism-limit=1) to avoidrace conditions whenterraform init writes to it.
terraform_validatesupports custom arguments so you can pass supported-no-coloror-jsonflags:-id:terraform_validateargs: ---args=-json ---args=-no-color
terraform_validatealso supports passing custom arguments to itsterraform init:-id:terraform_validateargs: ---tf-init-args=-upgrade ---tf-init-args=-lockfile=readonly
It may happen that Terraform working directory (
.terraform) already exists but not in the best condition (eg, not initialized modules, wrong version of Terraform, etc.). To solve this problem, you can delete broken.terraformdirectories in your repository:Option 1
-id:terraform_validateargs: ---hook-config=--retry-once-with-cleanup=true# Boolean. true or false
Important
The flag requires additional dependency to be installed:jq.Note
Reinit can be very slow and require downloading data from remote Terraform registries, and not all of that downloaded data or meta-data is currently being cached by Terraform.When
--retry-once-with-cleanup=true, in each failed directory the cached modules and providers from the.terraformdirectory will be deleted, before retrying once more. To avoid unnecessary deletion of this directory, the cleanup and retry will only happen if Terraform produces any of the following error messages:- "Missing or corrupted provider plugins"
- "Module source has changed"
- "Module version requirements have changed"
- "Module not installed"
- "Could not load plugin"
Warning
When using--retry-once-with-cleanup=true, problematic.terraform/modules/and.terraform/providers/directories will be recursively deleted without prompting for consent. Other files and directories will not be affected, such as the.terraform/environmentfile.Option 2
An alternative solution is to find and delete all
.terraformdirectories in your repository:echo"function rm_terraform { find . \( -iname".terraform*" ! -iname".terraform-docs*" \) -print0 | xargs -0 rm -r}">>~/.bashrc# Reload shell and use `rm_terraform` command in the repo root
terraform_validatehook will try to reinitialize them before running theterraform validatecommand.Caution
If you use Terraform workspaces, DO NOT use this option (details). Consider the first option, or wait forforce-initoption implementation.terraform_validatein a repo with Terraform module, written using Terraform 0.15+ and which uses providerconfiguration_aliases(Provider Aliases Within Modules), errors out.When running the hook against Terraform code where you have provider
configuration_aliasesdefined in arequired_providersconfiguration block, terraform will throw an error like:Error: Provider configuration not presentTo work with
<resource>its original provider configuration at provider["registry.terraform.io/hashicorp/aws"].<provider_alias>is required, but it has been removed. This occurs when a provider configuration is removed whileobjects created by that provider still exist in the state. Re-add the provider configuration to destroy<resource>, after which you can remove the provider configuration again.This is aknown issue with Terraform and how providers are initialized in Terraform 0.15 and later. To work around this you can add an
excludeparameter to the configuration ofterraform_validatehook like this:-id:terraform_validateexclude:'^[^/]+$'
This will exclude the root directory from being processed by this hook. Then add a subdirectory like "examples" or "tests" and put an example implementation in place that defines the providers with the proper aliases, and this will give you validation of your module through the example. If instead you are using this with multiple modules in one repository you'll want to set the path prefix in the regular expression, such as
exclude: modules/offendingmodule/[^/]+$.Alternately, you can useterraform-config-inspect and use a variant ofthis script to generate a providers file at runtime:
terraform-config-inspect --json.| jq -r' [.required_providers[].aliases] | flatten | del(.[] | select(. == null)) | reduce .[] as $entry ( {}; .provider[$entry.name] //= [] | .provider[$entry.name] += [{"alias": $entry.alias}] )'| tee aliased-providers.tf.json
Save it as
.generate-providers.shin the root of your repository and add apre-commithook to run it before all other hooks, like so:-repos: -repo:localhooks: -id:generate-terraform-providersname:generate-terraform-providersrequire_serial:trueentry:.generate-providers.shlanguage:scriptfiles:\.tf(vars)?$pass_filenames:false -repo:https://github.com/pre-commit/pre-commit-hooks
Tip
The latter method will leave an "aliased-providers.tf.json" file in your repo. You will either want to automate a way to clean this up or add it to your.gitignoreor both.
terraform_wrapper_module_for_each generates module wrappers for Terraform modules (useful for Terragrunt wherefor_each is not supported). When using this hook without arguments it will create wrappers for the root module and all modules available in "modules" directory.
You may want to customize some of the options:
--module-dir=...- Specify a single directory to process. Values: "." (means just root module), "modules/iam-user" (a single module), or empty (means include all submodules found in "modules/*").--module-repo-org=...- Module repository organization (e.g. "terraform-aws-modules").--module-repo-shortname=...- Short name of the repository (e.g. "s3-bucket").--module-repo-provider=...- Name of the repository provider (e.g. "aws" or "google").
Sample configuration:
-id:terraform_wrapper_module_for_eachargs: ---args=--module-dir=.# Process only root module ---args=--dry-run# No files will be created/updated ---args=--verbose# Verbose output
If you use hook inside Docker:
Theterraform_wrapper_module_for_each hook attempts to determine the module's short name to be inserted into the generatedREADME.md files for thesource URLs. Since the container uses a bind mount at a static location, it can cause this short name to be incorrect.
If the generated name is incorrect, set them by providing themodule-repo-shortname option to the hook:
-id:terraform_wrapper_module_for_eachargs: -'--args=--module-repo-shortname=ec2-instance'
terrascansupports custom arguments so you can pass supported flags like--non-recursiveand--policy-typeto disable recursive inspection and set the policy type respectively:-id:terrascanargs: ---args=--non-recursive# avoids scan errors on subdirectories without Terraform config files ---args=--policy-type=azure
See the
terrascan run -hcommand line help for available options.Use the
--args=--verboseparameter to see the rule ID in the scanning output. Useful to skip validations.Use
--skip-rules="ruleID1,ruleID2"parameter to skip one or more rules globally while scanning (e.g.:--args=--skip-rules="ruleID1,ruleID2").Use the syntax
#ts:skip=RuleID optional_commentinside a resource to skip the rule for that resource.
Out of the box
tfupdatewill pin the terraform version:-id:tfupdatename:Autoupdate Terraform versions
If you'd like to pin providers, etc., use custom arguments, i.e
provider=PROVIDER_NAME:-id:tfupdatename:Autoupdate AWS provider versionsargs: ---args=provider aws# Will be pined to latest version-id:tfupdatename:Autoupdate Helm provider versionsargs: ---args=provider helm ---args=--version 2.5.0# Will be pined to specified version
Checktfupdate usage instructions for other available options and usage examples.
No need to pass--recursive . as it is added automatically.
Tip
Use this hook only in infrastructure repos managed solely byterragrunt and do not mix withterraform_providers_lock to avoid conflicts.
Warning
Hookmay be very slow, because terragrunt invokest init under the hood.
Hook produces same results asterraform_providers_lock, but for terragrunt root modules.
It invokesterragrunt providers lock under the hood and terragruntdoes its' own magic for handling lock files.
-id:terragrunt_providers_lockname:Terragrunt providers lockargs: ---args=-platform=darwin_arm64 ---args=-platform=darwin_amd64 ---args=-platform=linux_amd64
Validates Terragrunt unused and undefined inputs. This is useful for keepingconfigs clean when module versions change or if configs are copied.
See theTerragrunt docs for more details.
Example:
-id:terragrunt_validate_inputsname:Terragrunt validate inputsargs:# Optionally check for unused inputs ---args=--terragrunt-strict-validate
Note
This hook requires authentication to a given account if defined by config to work properly. For example, if you use a third-party tool to store AWS credentials likeaws-vault you must be authenticated first.
See docs for theiam_role attribute and--terragrunt-iam-role flag for more.
Pre-built Docker images contain the latest versions of tools available at the time of their build and remain unchanged afterward. Tags should be immutable whenever possible, and it is highly recommended to pin them using hash sums for security and reproducibility.
This means that most Docker images will include known CVEs, and the longer an image exists, the more CVEs it may accumulate. This applies even to the latestvX.Y.Z tags.To address this, you can use thenightly tag, which rebuilds nightly with the latest versions of all dependencies and latestpre-commit-terraform hooks. However, using mutable tags introduces different security concerns.
Note: Currently, we DO NOT test third-party tools or their dependencies for security vulnerabilities, corruption, or injection (including obfuscated content). If you have ideas for introducing image scans or other security improvements, please open an issue or submit a PR. Some ideas are already tracked in#835.
From a security perspective, the best approach is to manage the Docker image yourself and update its dependencies as needed. This allows you to remove unnecessary dependencies, reducing the number of potential CVEs and improving overall security.
A mismatch between the Docker container's user and the local repository file ownership can cause permission issues in the repository wherepre-commit is run. The container runs as theroot user by default, and uses atools/entrypoint.sh script to assume a user ID and group ID if specified by the environment variableUSERID.
Therecommended command to run the Docker container is:
TAG=latestdocker run -e"USERID=$(id -u):$(id -g)" -v$(pwd):/lint -w /lint ghcr.io/antonbabenko/pre-commit-terraform:$TAG run -a
which uses your current session's user ID and group ID to set the variable in the run command. Without this setting, you may find files and directories owned byroot in your local repository.
If the local repository is using a different user or group for permissions, you can modify theUSERID to the user ID and group ID needed.Do not use the username or groupname in the environment variable, as it has no meaning in the container. You can get the current directory's owner user ID and group ID from the 3rd (user) and 4th (group) columns inls output:
$ ls -aldn.drwxr-xr-x 9 1000 1000 4096 Sep 1 16:23.
If you use a private Git repository as your Terraform module source, you are required to authenticate to GitHub using aPersonal Access Token.
When running pre-commit on Docker, both locally or on CI, you need to configure the~/.netrc file, which contains login and initialization information used by the auto-login process.
This can be achieved by firstly creating the~/.netrc file including yourGITHUB_PAT andGITHUB_SERVER_HOSTNAME
# set GH values (replace with your own values)GITHUB_PAT=ghp_bl481aBlabl481aBlaGITHUB_SERVER_HOSTNAME=github.com# create .netrc fileecho -e"machine$GITHUB_SERVER_HOSTNAME\n\tlogin$GITHUB_PAT">>~/.netrc
The~/.netrc file will look similar to the following:
machine github.com login ghp_bl481aBlabl481aBlaTip
The value ofGITHUB_SERVER_HOSTNAME can also refer to a GitHub Enterprise server (i.e.github.my-enterprise.com).
Finally, you can executedocker run with an additional volume mount so that the~/.netrc is accessible within the container
# run pre-commit-terraform with docker# adding volume for .netrc file# .netrc needs to be in /root/ dirdocker 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
You can use this hook in your GitHub Actions workflow together withpre-commit. To easy updependency management, you can use the manageddocker image within your workflow. Make sure to set theimage tag to the version you want to use.
In this repository's pre-commitworkflow file we run pre-commit without the container image.
Here's an example using the container image. It includes caching of pre-commit dependencies and utilizes the pre-commitcommand to run checks (Note: Fixes will not be automatically pushed back to your branch, even when possible.):
name:pre-commit-terraformon:pull_request:jobs:pre-commit:runs-on:ubuntu-latestcontainer:image:ghcr.io/antonbabenko/pre-commit-terraform:latest# latest used here for simplicity, not recommendeddefaults:run:shell:bashsteps: -uses:actions/checkout@v4with:fetch-depth:0ref:${{ github.event.pull_request.head.sha }} -run:| git config --global --add safe.directory $GITHUB_WORKSPACE git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* -name:Get changed filesid:file_changesrun:| export DIFF=$(git diff --name-only origin/${{ github.base_ref }} ${{ github.sha }}) echo "Diff between ${{ github.base_ref }} and ${{ github.sha }}" echo "files=$( echo "$DIFF" | xargs echo )" >> $GITHUB_OUTPUT -name:fix tar dependency in alpine container imagerun:| apk --no-cache add tar # check python modules installed versions python -m pip freeze --local -name:Cache pre-commit since we use pre-commit from containeruses:actions/cache@v4with:path:~/.cache/pre-commitkey:pre-commit-3|${{ hashFiles('.pre-commit-config.yaml') }} -name:Execute pre-commitrun:| pre-commit run --color=always --show-diff-on-failure --files ${{ steps.file_changes.outputs.files }}
This repository is managed byAnton Babenko with help from these awesome contributors:
MIT licensed. SeeLICENSE for full details.
- Russia hasillegally annexed Crimea in 2014 andbrought the war in Donbas followed byfull-scale invasion of Ukraine in 2022.
- Russia has brought sorrow and devastations to millions of Ukrainians, killed hundreds of innocent people, damaged thousands of buildings, and forced several million people to flee.
- Putin khuylo!
About
pre-commit git hooks to take care of Terraform configurations 🇺🇦
Topics
Resources
License
Code of conduct
Contributing
Security policy
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
