Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(terraform_providers_lock): Add --mode option and deprecate previous workflow #528

Merged
merged 15 commits into from
May 30, 2023
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
MaxymVlasov marked this conversation as resolved.
Show resolved Hide resolved
#######################################################################
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))
MaxymVlasov marked this conversation as resolved.
Show resolved Hide resolved
fi
fi

# lockfile always exists, because the hook triggered only on
# `files: (\.terraform\.lock\.hcl)$`
done < ".terraform.lock.hcl"
MaxymVlasov marked this conversation as resolved.
Show resolved Hide resolved

# 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
MaxymVlasov marked this conversation as resolved.
Show resolved Hide resolved
--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
MaxymVlasov marked this conversation as resolved.
Show resolved Hide resolved

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