diff --git a/README.md b/README.md index 3320c5608..f73fe48f5 100644 --- a/README.md +++ b/README.md @@ -544,12 +544,73 @@ To replicate functionality in `terraform_docs` hook: ### terraform_providers_lock -1. The hook requires Terraform 0.14 or later. -2. The hook invokes two operations that can be really slow: - * `terraform init` (in case `.terraform` directory is not initialized) - * `terraform providers lock` +> **Note**: The hook requires Terraform 0.14 or later. + +> **Note**: The hook can invoke `terraform 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-lockfile` in `args:` +> * Before `terraform_providers_lock`, add `terraform_validate` hook with `--hook-config=--retry-once-with-cleanup=true` +> * Move `--tf-init-args=` to `terraform_validate` hook +> +> In the end, you should get config like this: +> +> ```yaml +> - id: terraform_validate +> args: +> - --hook-config=--retry-once-with-cleanup=true +> # - --tf-init-args=-upgrade +> +> - id: terraform_providers_lock +> args: +> - --hook-config=--mode=always-regenerate-lockfile +> ``` +> +> Why? When v2.x will be introduced - the default mode will be changed, probably, to `only-check-is-current-lockfile-cross-platform`. +> +> You can check available modes for hook below. +>
+ + +1. The hook can work in a few different modes: `only-check-is-current-lockfile-cross-platform` with and without [terraform_validate hook](#terraform_validate) and `always-regenerate-lockfile` - only with terraform_validate hook. + + * `only-check-is-current-lockfile-cross-platform` without terraform_validate - only checks that lockfile has all required SHAs for all providers already added to lockfile. + + ```yaml + - id: terraform_providers_lock + args: + - --hook-config=--mode=only-check-is-current-lockfile-cross-platform + ``` + + * `only-check-is-current-lockfile-cross-platform` with [terraform_validate hook](#terraform_validate) - make up-to-date lockfile by adding/removing providers and only then check that lockfile has all required SHAs. + + > **Note**: Next `terraform_validate` flag requires additional dependency to be installed: `jq`. Also, it could run another slow and time consuming command - `terraform init` + + ```yaml + - id: terraform_validate + args: + - --hook-config=--retry-once-with-cleanup=true + + - id: terraform_providers_lock + args: + - --hook-config=--mode=only-check-is-current-lockfile-cross-platform + ``` + + * `always-regenerate-lockfile` only with [terraform_validate hook](#terraform_validate) - regenerate lockfile from scratch. Can be useful for upgrading providers in lockfile to latest versions + + ```yaml + - id: terraform_validate + args: + - --hook-config=--retry-once-with-cleanup=true + - --tf-init-args=-upgrade + + - id: terraform_providers_lock + args: + - --hook-config=--mode=always-regenerate-lockfile + ``` - 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. 3. `terraform_providers_lock` supports custom arguments: @@ -576,6 +637,8 @@ To replicate functionality in `terraform_docs` hook: 5. `terraform_providers_lock` support passing custom arguments to its `terraform init`: + > **Warning** - DEPRECATION NOTICE: This is available only in `no-mode` mode, which will be removed in v2.0. Please provide this keys to [`terraform_validate`](#terraform_validate) hook, which, to take effect, should be called before `terraform_providers_lock` + ```yaml - id: terraform_providers_lock args: diff --git a/hooks/_common.sh b/hooks/_common.sh index 4f9c7f0ce..31be98100 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -312,6 +312,7 @@ function common::colorify { # Outputs: # If failed - print out terraform init output ####################################################################### +# TODO: v2.0: Move it inside terraform_validate.sh function common::terraform_init { local -r command_name=$1 local -r dir_path=$2 diff --git a/hooks/terraform_providers_lock.sh b/hooks/terraform_providers_lock.sh index 638e3f9cf..d85a794f8 100755 --- a/hooks/terraform_providers_lock.sh +++ b/hooks/terraform_providers_lock.sh @@ -19,6 +19,63 @@ function main { common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" } +####################################################################### +# Check that all needed `h1` and `zh` SHAs are included in lockfile for +# each provider. +# Arguments: +# platforms_count (number) How many `-platform` flags provided +# Outputs: +# Return 0 when lockfile has all needed SHAs +# Return 1-99 when lockfile is invalid +# Return 100+ when not all SHAs found +####################################################################### +function lockfile_contains_all_needed_sha { + local -r platforms_count="$1" + + local h1_counter="$platforms_count" + local zh_counter=0 + + # Reading each line + while read -r line; do + + if grep -Eq '^"h1:' <<< "$line"; then + h1_counter=$((h1_counter - 1)) + continue + fi + + if grep -Eq '^"zh:' <<< "$line"; then + zh_counter=0 + continue + fi + + if grep -Eq '^provider' <<< "$line"; then + h1_counter="$platforms_count" + zh_counter=$((zh_counter + 1)) + continue + fi + # Not all SHA inside provider lock definition block found + if grep -Eq '^}' <<< "$line"; then + if [ "$h1_counter" -ge 1 ] || [ "$zh_counter" -ge 1 ]; then + # h1_counter can be less than 0, in the case when lockfile + # contains more platforms than you currently specify + # That's why here extra +50 - for safety reasons, to be sure + # that error goes exactly from this part of the function + return $((150 + h1_counter + zh_counter)) + fi + fi + + # lockfile always exists, because the hook triggered only on + # `files: (\.terraform\.lock\.hcl)$` + done < ".terraform.lock.hcl" + + # When you specify `-platform``, but don't specify current platform - + # platforms_count will be less than `h1:` headers` + [ "$h1_counter" -lt 0 ] && h1_counter=0 + + # 0 if all OK, 2+ when invalid lockfile + return $((h1_counter + zh_counter)) +} + ####################################################################### # Unique part of `common::per_dir_hook`. The function is executed in loop # on each provided dir path. Run wrapped tool with specified arguments @@ -39,13 +96,60 @@ function per_dir_hook_unique_part { shift 2 local -a -r args=("$@") + local platforms_count=0 + for arg in "${args[@]}"; do + if grep -Eq '^-platform=' <<< "$arg"; then + platforms_count=$((platforms_count + 1)) + fi + done + local exit_code + # + # Get hook settings + # + local mode + + IFS=";" read -r -a configs <<< "${HOOK_CONFIG[*]}" + + for c in "${configs[@]}"; do + + IFS="=" read -r -a config <<< "$c" + key=${config[0]} + value=${config[1]} + + case $key in + --mode) + if [ "$mode" ]; then + common::colorify "yellow" 'Invalid hook config. Make sure that you specify not more than one "--mode" flag' + exit 1 + fi + mode=$value + ;; + esac + done + + # Available options: + # only-check-is-current-lockfile-cross-platform (will be default) + # always-regenerate-lockfile + # TODO: Remove in 2.0 + if [ ! "$mode" ]; then + common::colorify "yellow" "DEPRECATION NOTICE: We introduced '--mode' flag for this hook. +Check migration instructions at https://github.com/antonbabenko/pre-commit-terraform#terraform_providers_lock +" + common::terraform_init 'terraform providers lock' "$dir_path" || { + exit_code=$? + return $exit_code + } + fi + + if [ "$mode" == "only-check-is-current-lockfile-cross-platform" ] && + lockfile_contains_all_needed_sha "$platforms_count"; then - common::terraform_init 'terraform providers lock' "$dir_path" || { - exit_code=$? - return $exit_code - } + exit 0 + fi + #? Don't require `tf init` for providers, but required `tf init` for modules + #? Mitigated by `function match_validate_errors` from terraform_validate hook # pass the arguments to hook terraform providers lock "${args[@]}"