Skip to content

Commit

Permalink
feat(terraform_providers_lock): Add --mode option and deprecate p…
Browse files Browse the repository at this point in the history
…revious workflow (#528)

Co-authored-by: George L. Yermulnik <yz@yz.kiev.ua>
  • Loading branch information
MaxymVlasov and yermulnik authored May 30, 2023
1 parent 37202d0 commit 2426b52
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 9 deletions.
73 changes: 68 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

> <details><summary><b>Note</b>: Read this if you used this hook before v1.80.0 | Planned breaking changes in v2.0</summary>
> 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.
> </details>


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:

Expand All @@ -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:
Expand Down
1 change: 1 addition & 0 deletions hooks/_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
112 changes: 108 additions & 4 deletions hooks/terraform_providers_lock.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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[@]}"

Expand Down

0 comments on commit 2426b52

Please sign in to comment.