Collection of git hooks for Terraform to be used with pre-commit framework
Want to contribute? Check open issues and contributing notes.
Automated provisioning of Terraform workflows and Infrastructure as Code.
Cloud cost estimates for Terraform.
If you are using pre-commit-terraform already or want to support its development and many other open-source projects, please become a GitHub Sponsor!
- Sponsors
- Table of content
- How to install
- Available Hooks
- Hooks usage notes and examples
- Authors
- License
pre-commitcheckovrequired forcheckovhook.terraform-docsrequired forterraform_docshook.terragruntrequired forterragrunt_validatehook.terrascanrequired forterrascanhook.TFLintrequired forterraform_tflinthook.TFSecrequired forterraform_tfsechook.infracostrequired forinfracost_breakdownhook.jqrequired forinfracost_breakdownhook.
Docker
Pull docker image with all hooks:
TAG=latest
docker pull ghcr.io/antonbabenko/pre-commit-terraform:$TAGAll available tags here.
Build from scratch:
When --build-arg is not specified, the latest version of pre-commit and terraform will be only installed.
git clone git@github.com:antonbabenko/pre-commit-terraform.git
cd pre-commit-terraform
# Install the latest versions of all the tools
docker 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 to latest:
docker build -t pre-commit-terraform \
--build-arg PRE_COMMIT_VERSION=latest \
--build-arg TERRAFORM_VERSION=latest \
--build-arg CHECKOV_VERSION=2.0.405 \
--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 \
.Set -e PRE_COMMIT_COLOR=never to disable the color output in pre-commit.
MacOS
coreutils is required for terraform_validate hook on MacOS (due to use of realpath).
brew install pre-commit terraform-docs tflint tfsec coreutils checkov terrascan infracost jqUbuntu 18.04
sudo apt update
sudo apt install -y unzip software-properties-common
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt install -y python3.7 python3-pip
python3 -m pip install --upgrade pip
pip3 install --no-cache-dir pre-commit
python3.7 -m pip install -U checkov
curl -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/accurics/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 init
sudo 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 registerUbuntu 20.04
sudo apt update
sudo apt install -y unzip software-properties-common python3 python3-pip
python3 -m pip install --upgrade pip
pip3 install --no-cache-dir pre-commit
pip3 install --no-cache-dir checkov
curl -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/accurics/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 init
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/
sudo 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 registerNote: not needed if you use the Docker image
DIR=~/.git-template
git 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 init
cat <<EOF > .pre-commit-config.yaml
repos:
- 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_docs
EOFExecute this command to run pre-commit on all files in the repository (not only changed files):
pre-commit run -aOr, using Docker (available tags):
TAG=latest
docker run -v $(pwd):/lint -w /lint ghcr.io/antonbabenko/pre-commit-terraform:$TAG run -aExecute this command to list the versions of the tools in Docker:
TAG=latest
docker run --entrypoint cat ghcr.io/antonbabenko/pre-commit-terraform:$TAG /usr/bin/tools_versions_infoThere are several pre-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 |
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 with infracost. Hook notes | infracost, jq, Infracost API key |
terraform_docs_replace |
Runs terraform-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 into README.md without aggregate type defaults. Hook notes same as for terraform_docs |
terraform-docs |
terraform_docs |
Inserts input and output documentation into README.md. Recommended. Hook notes |
terraform-docs |
terraform_fmt |
Reformat all Terraform configuration files to a canonical format. Hook notes | - |
terraform_providers_lock |
Updates provider signatures in dependency lock files. Hook notes | - |
terraform_tflint |
Validates all Terraform configuration files with TFLint. Available TFLint rules. Hook notes. | tflint |
terraform_tfsec |
TFSec static analysis of terraform templates to spot potential security issues. Hook notes | tfsec |
terraform_validate |
Validates all Terraform configuration files. Hook notes | - |
terragrunt_fmt |
Reformat all Terragrunt configuration files (*.hcl) to a canonical format. |
terragrunt |
terragrunt_validate |
Validates all Terragrunt configuration files (*.hcl) |
terragrunt |
terrascan |
terrascan Detect compliance and security violations. Hook notes | terrascan |
Check the source file to know arguments used for each hook.
For checkov you need to specify each argument separately:
- id: checkov
args: [
"-d", ".",
"--skip-check", "CKV2_AWS_8",
]infracost_breakdown executes infracost breakdown command and compare the estimated costs with those specified in the hook-config. infracost breakdown normally runs terraform init, terraform plan, and calls Infracost Cloud Pricing API (remote version or self-hosted version).
Unlike most other hooks, this hook triggers once if there are any changed files in the repository.
-
infracost_breakdownsupports allinfracost breakdownarguments. The following example only shows costs:- id: infracost_breakdown args: - --args=--path=./env/dev verbose: true # Always show costs
Output
Running in "env/dev" Summary: { "unsupportedResourceCounts": { "aws_sns_topic_subscription": 1 } } Total Monthly Cost: 86.83 USD Total Monthly Cost (diff): 86.83 USD
-
(Optionally) Define
cost constrainsthe hook should evaluate successfully in order to pass:- id: infracost_breakdown args: - --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
Running in "env/dev" Passed: .totalHourlyCost|tonumber > 0.1 0.11894520547945205 > 0.1 Failed: .totalHourlyCost|tonumber > 1 0.11894520547945205 > 1 Passed: .projects[].diff.totalMonthlyCost|tonumber !=10000 86.83 != 10000 Passed: .currency == "USD" "USD" == "USD" Summary: { "unsupportedResourceCounts": { "aws_sns_topic_subscription": 1 } } Total Monthly Cost: 86.83 USD Total 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 on jqplay.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)
- To disable hook color output, set
PRE_COMMIT_COLOR=neverenv var.
- Only one path per one hook (
-
Docker usage. In
docker buildordocker runcommand:- You need to provide Infracost 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=trueto skip the Infracost update check if you use this hook as part of your CI/CD pipeline.
- You need to provide Infracost API key via
-
terraform_docsandterraform_docs_without_aggregate_type_defaultswill insert/update documentation generated by terraform-docs framed by markers:<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> <!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
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)
- id: terraform_docs args: - --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
-
You can provide any configuration available in
terraform-docsas an argument toterraform_dochook, for example:- id: terraform_docs args: - --args=--config=.terraform-docs.yml
-
If you need some exotic settings, it can be done too. I.e. this one generates HCL files:
- id: terraform_docs args: - tfvars hcl --output-file terraform.tfvars.model .
DEPRECATED. Will be merged in terraform_docs. See #248 for details.
terraform_docs_replace replaces the entire README.md rather than doing string replacement between markers. Put your additional documentation at the top of your main.tf for it to be pulled in. The optional --dest argument lets you change the filename that gets created/modified.
Example:
- id: terraform_docs_replace
args:
- --sort-by-required
- --dest=TEST.md-
terraform_fmtsupports custom arguments so you can pass supported flags. Eg:- id: terraform_fmt args: - --args=-no-color - --args=-diff - --args=-write=false
-
The hook requires Terraform 0.14 or later.
-
The hook invokes two operations that can be really slow:
terraform init(in case.terraformdirectory is not initialised)terraform providers lock
Both operations require downloading data from remote Terraform registries, and not all of that downloaded data or meta-data is currently being cached by Terraform.
-
terraform_providers_locksupports custom arguments:- id: terraform_providers_lock args: - --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 . -name ".terraform*" -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_tflintsupports custom arguments so you can enable module inspection, deep check mode, etc.Example:
- id: terraform_tflint args: - --args=--deep - --args=--enable-rule=terraform_documented_variables
-
When you have multiple directories and want to run
tflintin all of them and share a single config file, it is impractical to hard-code the path to the.tflint.hclfile. The solution is to use the__GIT_WORKING_DIR__placeholder which will be replaced byterraform_tflinthooks with Git working directory (repo root) at run time. For example:- id: terraform_tflint args: - --args=--config=__GIT_WORKING_DIR__/.tflint.hcl
-
terraform_tfsecwill consume modified files that pre-commit passes to it, so you can perform whitelisting of directories or files to run against via files pre-commit flagExample:
- id: terraform_tfsec files: ^prd-infra/
The above will tell pre-commit to pass down files from the
prd-infra/folder only such that the underlyingtfsectool can run against changed files in this directory, ignoring any other folders at the root level -
To ignore specific warnings, follow the convention from the documentation.
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_tfsec args: - > --args=--format json --no-color -e aws-s3-enable-bucket-logging,aws-s3-specify-public-access-block
-
When you have multiple directories and want to run
tfsecin all of them and share a single config file - use the__GIT_WORKING_DIR__placeholder. It will be replaced byterraform_tfsechooks with Git working directory (repo root) at run time. For example:- id: terraform_tfsec args: - --args=--config-file=__GIT_WORKING_DIR__/.tfsec.json
Otherwise, will be used files that located in sub-folders:
- id: terraform_tfsec args: - --args=--config-file=.tfsec.json
-
terraform_validatesupports custom arguments so you can pass supported-no-coloror-jsonflags:- id: terraform_validate args: - --args=-json - --args=-no-color
-
terraform_validatealso supports custom environment variables passed to the pre-commit runtime:- id: terraform_validate args: - --envs=AWS_DEFAULT_REGION="us-west-2" - --envs=AWS_ACCESS_KEY_ID="anaccesskey" - --envs=AWS_SECRET_ACCESS_KEY="asecretkey"
-
terraform_validatealso supports passing custom arguments to itsterraform init:- id: terraform_validate args: - --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 find and delete all.terraformdirectories in your repository:echo " function rm_terraform { find . -name ".terraform*" -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.Warning: If you use Terraform workspaces, DO NOT use this workaround (details). Wait to
force-initoption implementation.
-
terrascansupports custom arguments so you can pass supported flags like--non-recursiveand--policy-typeto disable recursive inspection and set the policy type respectively:- id: terrascan args: - --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 scaning output. Usuful 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.
This repository is managed by Anton Babenko with help from these awesome contributors:
MIT licensed. See LICENSE for full details.