diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 2ec99d792..2602e9956 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -111,3 +111,13 @@ files: \.tf$ exclude: \.terraform\/.*$ require_serial: true + +- id: tfupdate + name: tfupdate + description: Runs tfupdate on Terraform templates. + language: script + entry: hooks/tfupdate.sh + args: + - --args=terraform + files: \.tf$ + require_serial: true diff --git a/Dockerfile b/Dockerfile index 184846008..5a7d3c364 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,6 +34,7 @@ ARG TERRAGRUNT_VERSION=${TERRAGRUNT_VERSION:-false} ARG TERRASCAN_VERSION=${TERRASCAN_VERSION:-false} ARG TFLINT_VERSION=${TFLINT_VERSION:-false} ARG TFSEC_VERSION=${TFSEC_VERSION:-false} +ARG TFUPDATE_VERSION=${TFUPDATE_VERSION:-false} # Tricky thing to install all tools by set only one arg. @@ -48,6 +49,7 @@ RUN if [ "$INSTALL_ALL" != "false" ]; then \ echo "export TERRASCAN_VERSION=latest" >> /.env && \ echo "export TFLINT_VERSION=latest" >> /.env && \ echo "export TFSEC_VERSION=latest" >> /.env \ + echo "export TFUPDATE_VERSION=latest" >> /.env \ ; else \ touch /.env \ ; fi @@ -126,6 +128,16 @@ RUN . /.env && \ ) && chmod +x tfsec \ ; fi +# TFUpdate +RUN . /.env && \ + if [ "$TFUPDATE_VERSION" != "false" ]; then \ + ( \ + TFUPDATE_RELEASES="https://api.github.com/repos/minamijoyo/tfupdate/releases" && \ + [ "$TFUPDATE_VERSION" = "latest" ] && curl -L "$(curl -s ${TFUPDATE_RELEASES}/latest | grep -o -E "https://.+?/tfupdate_.+_linux_amd64.tar.gz")" > tfupdate.tgz \ + || curl -L "$(curl -s ${TFUPDATE_RELEASES} | grep -o -E "https://.+?/v${TF_VERSION}/tfupdate_${TF_VERSION}_linux_amd64.tar.gz")" > tfupdate.tgz \ + ) && tar -xzf tfupdate.tgz \ + ; fi + # Checking binaries versions and write it to debug file RUN . /.env && \ F=tools_versions_info && \ @@ -138,6 +150,7 @@ RUN . /.env && \ (if [ "$TERRASCAN_VERSION" != "false" ]; then echo "terrascan $(./terrascan version)" >> $F; else echo "terrascan SKIPPED" >> $F ; fi) && \ (if [ "$TFLINT_VERSION" != "false" ]; then ./tflint --version >> $F; else echo "tflint SKIPPED" >> $F ; fi) && \ (if [ "$TFSEC_VERSION" != "false" ]; then echo "tfsec $(./tfsec --version)" >> $F; else echo "tfsec SKIPPED" >> $F ; fi) && \ + (if [ "$TFUPDATE_VERSION" != "false" ]; then echo "tfsec $(./tfupdate --version)" >> $F; else echo "tfupdate SKIPPED" >> $F ; fi) && \ echo -e "\n\n" && cat $F && echo -e "\n\n" @@ -157,7 +170,7 @@ COPY --from=builder \ # Hooks and terraform binaries /bin_dir/ \ /usr/local/bin/checkov* \ - /usr/bin/ + /usr/bin/ # Copy pre-commit packages COPY --from=builder /usr/local/lib/python3.10/site-packages/ /usr/local/lib/python3.10/site-packages/ # Copy terrascan policies diff --git a/README.md b/README.md index 82358c162..5d6566a05 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,11 @@ If you are using `pre-commit-terraform` already or want to support its developme * [terraform_tfsec](#terraform_tfsec) * [terraform_validate](#terraform_validate) * [terrascan](#terrascan) + * [tfupdate](#tfupdate) * [Authors](#authors) * [License](#license) + ## How to install ### 1. Install dependencies @@ -62,6 +64,7 @@ If you are using `pre-commit-terraform` already or want to support its developme * [`TFSec`](https://github.com/liamg/tfsec) required for `terraform_tfsec` hook. * [`infracost`](https://github.com/infracost/infracost) required for `infracost_breakdown` hook. * [`jq`](https://github.com/stedolan/jq) required for `infracost_breakdown` hook. +* [`tfupdate`](https://github.com/minamijoyo/tfupdate) required for `tfupdate` hook.
Docker
@@ -98,6 +101,7 @@ docker build -t pre-commit-terraform \ --build-arg TERRASCAN_VERSION=1.10.0 \ --build-arg TFLINT_VERSION=0.31.0 \ --build-arg TFSEC_VERSION=latest \ + --build-arg TFUPDATE_VERSION=latest \ . ``` @@ -111,7 +115,7 @@ Set `-e PRE_COMMIT_COLOR=never` to disable the color output in `pre-commit`. [`coreutils`](https://formulae.brew.sh/formula/coreutils) is required for `terraform_validate` hook on MacOS (due to use of `realpath`). ```bash -brew install pre-commit terraform-docs tflint tfsec coreutils checkov terrascan infracost jq +brew install pre-commit terraform-docs tflint tfsec coreutils checkov terrascan infracost tfupdate jq ```
@@ -132,6 +136,7 @@ curl -L "$(curl -s https://api.github.com/repos/aquasecurity/tfsec/releases/late 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 register +curl -L "$(curl -s https://api.github.com/repos/minamijoyo/tfupdate/releases/latest | grep -o -E -m 1 "https://.+?/tfupdate_.+_linux_amd64.tar.gz")" > tfupdate.tar.gz && tar -xzf tfupdate.tar.gz tfupdate && rm tfupdate.tar.gz && sudo mv tfupdate /usr/bin/ ``` @@ -151,6 +156,7 @@ curl -L "$(curl -s https://api.github.com/repos/terraform-linters/tflint/release 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 register +curl -L "$(curl -s https://api.github.com/repos/minamijoyo/tfupdate/releases/latest | grep -o -E -m 1 "https://.+?/tfupdate_.+_linux_amd64.tar.gz")" > tfupdate.tar.gz && tar -xzf tfupdate.tar.gz tfupdate && rm tfupdate.tar.gz && sudo mv tfupdate /usr/bin/ ``` @@ -224,7 +230,8 @@ There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform | `terraform_validate` | Validates all Terraform configuration files. [Hook notes](#terraform_validate) | - | | `terragrunt_fmt` | Reformat all [Terragrunt](https://github.com/gruntwork-io/terragrunt) configuration files (`*.hcl`) to a canonical format. | `terragrunt` | | `terragrunt_validate` | Validates all [Terragrunt](https://github.com/gruntwork-io/terragrunt) configuration files (`*.hcl`) | `terragrunt` | -| `terrascan` | [terrascan](https://github.com/accurics/terrascan) Detect compliance and security violations. [Hook notes](#terrascan) | `terrascan` | +| `terrascan` | [terrascan](https://github.com/accurics/terrascan) Detect compliance and security violations. [Hook notes](#terrascan) | `terrascan` | +| `tfupdate` | [tfupdate](https://github.com/minamijoyo/tfupdate) Update version constraints of Terraform core, providers, and modules. [Hook notes](#tfupdate) | `tfupdate` | Check the [source file](https://github.com/antonbabenko/pre-commit-terraform/blob/master/.pre-commit-hooks.yaml) to know arguments used for each hook. @@ -617,6 +624,27 @@ Example: 3. Use `--skip-rules="ruleID1,ruleID2"` parameter to skip one or more rules globally while scanning (e.g.: `--args=--skip-rules="ruleID1,ruleID2"`). 4. Use the syntax `#ts:skip=RuleID optional_comment` inside a resource to skip the rule for that resource. +### tfupdate + +Out of the box tfupdate will pin the terraform version + +```yaml + - id: tfupdate + ``` + + But you can pass `tfupdate` custom commands like `provider ${PROVIDER_NAME}` : + +```yaml + - id: tfupdate + name: tfupdate terraform + - id: tfupdate + name: tfupdate provider vsphere + args: + - --args=provider + - --args=vsphere +``` +See the `tfupdate --help` command line help for available options. No need to pass `--recursive .` as it is added automatically + ## Authors This repository is managed by [Anton Babenko](https://github.com/antonbabenko) with help from these awesome contributors: diff --git a/hooks/tfupdate.sh b/hooks/tfupdate.sh new file mode 100644 index 000000000..f81caf1c4 --- /dev/null +++ b/hooks/tfupdate.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +set -eo pipefail + +# globals variables +# hook ID, see `- id` for details in .pre-commit-hooks.yaml file +readonly HOOK_ID='tfupdate' +# shellcheck disable=SC2155 # No way to assign to readonly variable in separate lines +readonly SCRIPT_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +function main { + common::initialize "$SCRIPT_DIR" + common::parse_cmdline "$@" + # shellcheck disable=SC2153 # False positive + common::per_dir_hook "${ARGS[*]}" "$HOOK_ID" "${FILES[@]}" +} +####################################################################### +# Unique part of `common::per_dir_hook`. The function is executed in loop +# on each provided dir path. Run wrapped tool with specified arguments +# Arguments: +# args (string with array) arguments that configure wrapped tool behavior +# dir_path (string) PATH to dir relative to git repo root. +# Can be used in error logging +# Outputs: +# If failed - print out hook checks status +####################################################################### +function per_dir_hook_unique_part { + local -r args="$1" + # shellcheck disable=SC2034 # Unused var. + local -r dir_path="$2" + + # pass the arguments to hook + # shellcheck disable=SC2068 # hook fails when quoting is used ("$arg[@]") + tfupdate "${args[@]}" "${dir_path}" + + # return exit code to common::per_dir_hook + local exit_code=$? + return $exit_code +} + +####################################################################### +# Unique part of `common::per_dir_hook`. The function is executed one time +# in the root git repo +# Arguments: +# args (string with array) arguments that configure wrapped tool behavior +####################################################################### +function run_hook_on_whole_repo { + local -r args="$1" + # pass the arguments to hook + # shellcheck disable=SC2068 # hook fails when quoting is used ("$arg[@]") + tfupdate ${args[*]} --recursive . + + # return exit code to common::per_dir_hook + local exit_code=$? + return $exit_code +} + +[ "${BASH_SOURCE[0]}" != "$0" ] || main "$@"