From 8910cd3358c2b7691265166666dbc34be3200a48 Mon Sep 17 00:00:00 2001 From: Scott Vitale Date: Wed, 21 Apr 2021 23:15:10 -0600 Subject: [PATCH] feat: Add an option `--tf-version` to terraform_docs, terraform_fmt, and terraform_validate Enables the use of a version parameter which is in turn used by `tfenv` to dynamically switch the version of terraform being used at runtime. --- CHANGELOG.md | 4 +- README.md | 19 ++++++-- lib_tfenv | 21 +++++++++ terraform_docs.sh | 14 +++++- terraform_fmt.sh | 104 ++++++++++++++++++++++++++++++++---------- terraform_validate.sh | 14 +++++- 6 files changed, 144 insertions(+), 32 deletions(-) create mode 100644 lib_tfenv diff --git a/CHANGELOG.md b/CHANGELOG.md index 5686d7243..f4fd4e7d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - +- feat: Add option for using `tfenv` to switch terraform versions in terraform_docs, terraform_fmt, terraform_validate ## [v1.49.0] - 2021-04-20 @@ -145,7 +145,7 @@ All notable changes to this project will be documented in this file. - fix: Change terraform_validate hook functionality for subdirectories with terraform files ([#100](https://github.com/antonbabenko/pre-commit-terraform/issues/100)) -### +### configuration for the appropriate working directory. diff --git a/README.md b/README.md index a939111b3..54c205f83 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,14 @@ * [`TFSec`](https://github.com/liamg/tfsec) required for `terraform_tfsec` hook. * [`coreutils`](https://formulae.brew.sh/formula/coreutils) required for `terraform_validate` hook on macOS (due to use of `realpath`). * [`checkov`](https://github.com/bridgecrewio/checkov) required for `checkov` hook. +* [`tfenv`](https://github.com/tfutils/tfenv) if you wish to specify a desired version of terraform for the `terraform_fmt`, `terraform_validate`, or `terraform_docs` hooks. or build and use the Docker image locally as mentioned below in the `Run` section. ##### MacOS ```bash -brew install pre-commit gawk terraform-docs tflint tfsec coreutils checkov +brew install pre-commit gawk terraform-docs tflint tfsec coreutils checkov tfenv ``` ##### Ubuntu 18.04 @@ -35,6 +36,8 @@ curl -L "$(curl -s https://api.github.com/repos/tfsec/tfsec/releases/latest | gr python3.7 -m pip install -U checkov ``` +`tfenv` installation varies depending on distribution, see full install instructions here: https://github.com/tfutils/tfenv#manual + ### 2. Install the pre-commit hook globally Note: not needed if you use the Docker image @@ -72,9 +75,9 @@ or you can also build and use the provided Docker container, which wraps all dep ```bash # first building it docker build -t pre-commit . -# and then running it in the folder +# and then running it in the folder # with the terraform code you want to check by executing -docker run -v $(pwd):/lint -w /lint pre-commit run -a +docker run -v $(pwd):/lint -w /lint pre-commit run -a ``` ## Available Hooks @@ -96,6 +99,16 @@ There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform Check the [source file](https://github.com/antonbabenko/pre-commit-terraform/blob/master/.pre-commit-hooks.yaml) to know arguments used for each hook. +## Using a specific terraform version via `tfenv` + +Hooks which call terraform directly (`terraform_fmt`, `terraform_validate`, `terraform_docs`) support the `--tf-version` argument to optionally ensure that the correct version of terraform is used. + +```yaml +hooks: + - id: terraform_fmt + args: ['--tf-version=0.12.18'] +``` + ## Notes about terraform_docs hooks 1. `terraform_docs` and `terraform_docs_without_aggregate_type_defaults` will insert/update documentation generated by [terraform-docs](https://github.com/terraform-docs/terraform-docs) framed by markers: diff --git a/lib_tfenv b/lib_tfenv new file mode 100644 index 000000000..238e1f0d4 --- /dev/null +++ b/lib_tfenv @@ -0,0 +1,21 @@ +#!/bin/sh +function switchTfEnv() { + TARGET_VERSION="$1" + + # Read current terraform version + CURRENT_VERSION=$(tfenv list | sed -nr 's/^\* ([0-9\-\.]+) .*/\1/p') + echo "Current version is set to '$CURRENT_VERSION', switching to '$TARGET_VERSION'" + + # Install and switch to required version + tfenv install "$TARGET_VERSION" + tfenv use "$TARGET_VERSION" + + # Auto-cleanup on exit + trap restoreTfEnv EXIT +} + +function restoreTfEnv() { + # Restore + echo "Restoring version to '$CURRENT_VERSION'" + tfenv use "$CURRENT_VERSION" +} diff --git a/terraform_docs.sh b/terraform_docs.sh index 8c7076bcf..fdbdaeede 100755 --- a/terraform_docs.sh +++ b/terraform_docs.sh @@ -4,6 +4,10 @@ set -eo pipefail main() { initialize_ parse_cmdline_ "$@" + + # If a specific terraform version was specified, switch to it. The tfenv lib will auto-restore on exit + [ "$TFVER" != "" ] && . "$_SCRIPT_DIR/lib_tfenv" && switchTfEnv "$TFVER" + terraform_docs_ "${ARGS[*]}" "${FILES[@]}" } @@ -27,7 +31,7 @@ initialize_() { parse_cmdline_() { declare argv - argv=$(getopt -o a: --long args: -- "$@") || return + argv=$(getopt -o a:t: --long args:,tf-version: -- "$@") || return eval "set -- $argv" for argv; do @@ -37,6 +41,11 @@ parse_cmdline_() { ARGS+=("$1") shift ;; + -t | --tf-version) + shift + TFVER="$1" + shift + ;; --) shift FILES=("$@") @@ -311,7 +320,8 @@ EOF } -# global arrays +# global variables +declare TFVER="" declare -a ARGS=() declare -a FILES=() diff --git a/terraform_fmt.sh b/terraform_fmt.sh index d280574d1..c46a34e6c 100755 --- a/terraform_fmt.sh +++ b/terraform_fmt.sh @@ -1,34 +1,92 @@ #!/usr/bin/env bash -set -e +set -eo pipefail -declare -a paths -declare -a tfvars_files +main() { + initialize_ + parse_cmdline_ "$@" -index=0 + # If a specific terraform version was specified, switch to it. The tfenv lib will auto-restore on exit + [ "$TFVER" != "" ] && . "$_SCRIPT_DIR/lib_tfenv" && switchTfEnv "$TFVER" -for file_with_path in "$@"; do - file_with_path="${file_with_path// /__REPLACED__SPACE__}" + terraform_fmt_ "${FILES[@]}" +} - paths[index]=$(dirname "$file_with_path") +initialize_() { + # get directory containing this script + local dir + local source + source="${BASH_SOURCE[0]}" + while [[ -L $source ]]; do # resolve $source until the file is no longer a symlink + dir="$(cd -P "$(dirname "$source")" > /dev/null && pwd)" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the symlink file was located + [[ $source != /* ]] && source="$dir/$source" + done + _SCRIPT_DIR="$(dirname "$source")" - if [[ "$file_with_path" == *".tfvars" ]]; then - tfvars_files+=("$file_with_path") - fi + # source getopt function + # shellcheck source=lib_getopt + . "$_SCRIPT_DIR/lib_getopt" +} - let "index+=1" -done +parse_cmdline_() { + declare argv + argv=$(getopt -o t: --long tf-version: -- "$@") || return + eval "set -- $argv" -for path_uniq in $(echo "${paths[*]}" | tr ' ' '\n' | sort -u); do - path_uniq="${path_uniq//__REPLACED__SPACE__/ }" + for argv; do + case $argv in + -t | --tf-version) + shift + TFVER="$1" + shift + ;; + --) + shift + FILES=("$@") + break + ;; + esac + done +} - pushd "$path_uniq" > /dev/null - terraform fmt - popd > /dev/null -done +terraform_fmt_() { + local -a -r files=("$@") + declare -a paths + declare -a tfvars_files -# terraform.tfvars are excluded by `terraform fmt` -for tfvars_file in "${tfvars_files[@]}"; do - tfvars_file="${tfvars_file//__REPLACED__SPACE__/ }" + index=0 - terraform fmt "$tfvars_file" -done + for file_with_path in "${files[@]}"; do + file_with_path="${file_with_path// /__REPLACED__SPACE__}" + + paths[index]=$(dirname "$file_with_path") + + if [[ "$file_with_path" == *".tfvars" ]]; then + tfvars_files+=("$file_with_path") + fi + + let "index+=1" + done + + for path_uniq in $(echo "${paths[*]}" | tr ' ' '\n' | sort -u); do + path_uniq="${path_uniq//__REPLACED__SPACE__/ }" + + pushd "$path_uniq" > /dev/null + terraform fmt + popd > /dev/null + done + + # terraform.tfvars are excluded by `terraform fmt` + for tfvars_file in "${tfvars_files[@]}"; do + tfvars_file="${tfvars_file//__REPLACED__SPACE__/ }" + + terraform fmt "$tfvars_file" + done +} + +# global variables +declare TFVER="" +declare -a FILES=() + +[[ ${BASH_SOURCE[0]} != "$0" ]] || main "$@" diff --git a/terraform_validate.sh b/terraform_validate.sh index 236b35138..ca2dc4d52 100755 --- a/terraform_validate.sh +++ b/terraform_validate.sh @@ -7,6 +7,10 @@ export AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-us-east-1} main() { initialize_ parse_cmdline_ "$@" + + # If a specific terraform version was specified, switch to it. The tfenv lib will auto-restore on exit + [ "$TFVER" != "" ] && . "$_SCRIPT_DIR/lib_tfenv" && switchTfEnv "$TFVER" + terraform_validate_ } @@ -30,7 +34,7 @@ initialize_() { parse_cmdline_() { declare argv - argv=$(getopt -o e:a: --long envs:,args: -- "$@") || return + argv=$(getopt -o e:a:t: --long envs:,args:,tf-version: -- "$@") || return eval "set -- $argv" for argv; do @@ -45,6 +49,11 @@ parse_cmdline_() { ENVS+=("$1") shift ;; + -t | --tf-version) + shift + TFVER="$1" + shift + ;; --) shift FILES=("$@") @@ -121,7 +130,8 @@ terraform_validate_() { fi } -# global arrays +# global variables +declare TFVER="" declare -a ARGS declare -a ENVS declare -a FILES