From 70519b6b7920d7b8e70687118c6eaa40c2142cd0 Mon Sep 17 00:00:00 2001 From: MaxymVlasov Date: Thu, 22 Jan 2026 16:37:32 +0200 Subject: [PATCH 1/7] fix(`terraform_validate`): Add better workaround for deal with parallelism race conditions in Terraform setups during `t init`: allow use of `tofu` binary just for `t init`, as OpenTofu 1.10+ have native lock mechanism --- README.md | 10 +++++++++- hooks/_common.sh | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 59f01426e..f4bd0bf57 100644 --- a/README.md +++ b/README.md @@ -970,7 +970,15 @@ To replicate functionality in `terraform_docs` hook: ### terraform_validate > [!IMPORTANT] -> If you use [`TF_PLUGIN_CACHE_DIR`](https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache), we recommend enabling `--hook-config=--retry-once-with-cleanup=true` or disabling parallelism (`--hook-config=--parallelism-limit=1`) to avoid [race conditions when `terraform init` writes to it](https://github.com/hashicorp/terraform/issues/31964). +> If you use both: +> +> 1. Global Provider Cache. [Terraform docs](https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache), [OpenTofu docs](https://opentofu.org/docs/cli/config/config-file/#provider-plugin-cache) +> 2.`tofu` < v1.10 or any `terraform` version +> +> We recommend to do one of: +> +> * Disable parallelism (`--hook-config=--parallelism-limit=1`) as it can cause [race conditions when `terraform init` writes to it](https://github.com/hashicorp/terraform/issues/31964). +> * Install [OpenTofu v1.10+](https://opentofu.org/blog/help-us-test-opentofu-1-10-0-alpha2/#global-provider-cache-locking) even if you use Terraform - it will be used just for `t init` operations. 1. `terraform_validate` supports custom arguments so you can pass supported `-no-color` or `-json` flags: diff --git a/hooks/_common.sh b/hooks/_common.sh index fd26dfce1..e56d191c3 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -555,6 +555,15 @@ function common::terraform_init { init_output=$("$tf_path" init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) exit_code=$? else + # The global provider cache is safe for concurrent use by multiple processes for OpenTofu v1.10+ + # More details - https://github.com/opentofu/opentofu/pull/1878 + # For that reason, we switch to `tofu init` when possible + if [[ "$($tf_path -version | head -1 | grep '^Terraform')" == "Terraform" ]] && + common::tofu_version_ge_1.10; then + tf_path=$(command -v tofu) + common::colorify "green" "Using OpenTofu binary ($tf_path) for Terraform init operations, as it supports concurrent provider initialization." + fi + # Locking just doesn't work, and the below works quicker instead. Details: # https://github.com/hashicorp/terraform/issues/31964#issuecomment-1939869453 for i in {1..10}; do @@ -609,6 +618,37 @@ function common::export_provided_env_vars { done } +####################################################################### +# Check if the installed Tofu version is >=1.10.0 or not +# +# This function helps to determine if tofu version allow to use +# parallelism based on Tofu version +# +# Returns: +# - 0 if version >= 1.10.0 +# - 1 if version < 1.10.0 +# Defaults to 1 if version cannot be determined +####################################################################### +# TODO: Drop after Jun 2027. Two years to upgrade is more than enough. +function common::tofu_version_ge_1.10 { + local tofu_version + + # Extract version number (e.g., "tofu version v1.10.4" -> "1.10") + tofu_version=$(tofu --version 2> /dev/null | grep -oE '[0-9]+\.[0-9]+') + # If we can't parse version, default to older command + [[ ! $tofu_version ]] && return 1 + + local major minor + IFS='.' read -r major minor <<< "$tofu_version" + + # New functional added in v1.10.0 (June 2025) + if [[ $major -gt 1 || ($major -eq 1 && $minor -ge 10) ]]; then + return 0 + else + return 1 + fi +} + ####################################################################### # Check if the installed Terragrunt version is >=0.78.0 or not # From 2645d40850e9a44ef8297d1eec07dcc9837cc8b8 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 22 Jan 2026 17:02:52 +0200 Subject: [PATCH 2/7] Apply suggestions from code review --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f4bd0bf57..f09ad21b2 100644 --- a/README.md +++ b/README.md @@ -973,7 +973,7 @@ To replicate functionality in `terraform_docs` hook: > If you use both: > > 1. Global Provider Cache. [Terraform docs](https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache), [OpenTofu docs](https://opentofu.org/docs/cli/config/config-file/#provider-plugin-cache) -> 2.`tofu` < v1.10 or any `terraform` version +> 2. `tofu` < v1.10 or any `terraform` version > > We recommend to do one of: > From f5af729c6b44d396a8553b5a923070aa4557699f Mon Sep 17 00:00:00 2001 From: MaxymVlasov Date: Thu, 22 Jan 2026 17:16:07 +0200 Subject: [PATCH 3/7] Debug --- hooks/_common.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hooks/_common.sh b/hooks/_common.sh index e56d191c3..79af41dbc 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -558,11 +558,13 @@ function common::terraform_init { # The global provider cache is safe for concurrent use by multiple processes for OpenTofu v1.10+ # More details - https://github.com/opentofu/opentofu/pull/1878 # For that reason, we switch to `tofu init` when possible + echo "Using Terraform binary path: $tf_path" if [[ "$($tf_path -version | head -1 | grep '^Terraform')" == "Terraform" ]] && common::tofu_version_ge_1.10; then tf_path=$(command -v tofu) common::colorify "green" "Using OpenTofu binary ($tf_path) for Terraform init operations, as it supports concurrent provider initialization." fi + echo "Running 'terraform init' with plugin cache dir: $TF_PLUGIN_CACHE_DIR" # Locking just doesn't work, and the below works quicker instead. Details: # https://github.com/hashicorp/terraform/issues/31964#issuecomment-1939869453 @@ -635,6 +637,7 @@ function common::tofu_version_ge_1.10 { # Extract version number (e.g., "tofu version v1.10.4" -> "1.10") tofu_version=$(tofu --version 2> /dev/null | grep -oE '[0-9]+\.[0-9]+') + echo "Detected OpenTofu version: $tofu_version" # If we can't parse version, default to older command [[ ! $tofu_version ]] && return 1 From 6fad8c8f4404cc46da0a41cee3c1170b9dec35af Mon Sep 17 00:00:00 2001 From: MaxymVlasov Date: Thu, 22 Jan 2026 18:23:39 +0200 Subject: [PATCH 4/7] f --- hooks/_common.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index 79af41dbc..b9d9daa22 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -558,13 +558,11 @@ function common::terraform_init { # The global provider cache is safe for concurrent use by multiple processes for OpenTofu v1.10+ # More details - https://github.com/opentofu/opentofu/pull/1878 # For that reason, we switch to `tofu init` when possible - echo "Using Terraform binary path: $tf_path" - if [[ "$($tf_path -version | head -1 | grep '^Terraform')" == "Terraform" ]] && + if [[ "$($tf_path -version | head -1 | grep -o '^Terraform')" == "Terraform" ]] && common::tofu_version_ge_1.10; then tf_path=$(command -v tofu) common::colorify "green" "Using OpenTofu binary ($tf_path) for Terraform init operations, as it supports concurrent provider initialization." fi - echo "Running 'terraform init' with plugin cache dir: $TF_PLUGIN_CACHE_DIR" # Locking just doesn't work, and the below works quicker instead. Details: # https://github.com/hashicorp/terraform/issues/31964#issuecomment-1939869453 @@ -637,7 +635,6 @@ function common::tofu_version_ge_1.10 { # Extract version number (e.g., "tofu version v1.10.4" -> "1.10") tofu_version=$(tofu --version 2> /dev/null | grep -oE '[0-9]+\.[0-9]+') - echo "Detected OpenTofu version: $tofu_version" # If we can't parse version, default to older command [[ ! $tofu_version ]] && return 1 From 5362ca01c4a4d9091426bc55bac0bf6fa9b51711 Mon Sep 17 00:00:00 2001 From: MaxymVlasov Date: Thu, 22 Jan 2026 18:27:48 +0200 Subject: [PATCH 5/7] deal with RO var --- hooks/_common.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index b9d9daa22..69eeb6f7b 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -558,16 +558,17 @@ function common::terraform_init { # The global provider cache is safe for concurrent use by multiple processes for OpenTofu v1.10+ # More details - https://github.com/opentofu/opentofu/pull/1878 # For that reason, we switch to `tofu init` when possible + local tf_init_path=$tf_path if [[ "$($tf_path -version | head -1 | grep -o '^Terraform')" == "Terraform" ]] && common::tofu_version_ge_1.10; then - tf_path=$(command -v tofu) - common::colorify "green" "Using OpenTofu binary ($tf_path) for Terraform init operations, as it supports concurrent provider initialization." + tf_init_path=$(command -v tofu) + common::colorify "green" "Using OpenTofu binary ($tf_init_path) for Terraform init operations, as it supports concurrent provider initialization." fi # Locking just doesn't work, and the below works quicker instead. Details: # https://github.com/hashicorp/terraform/issues/31964#issuecomment-1939869453 for i in {1..10}; do - init_output=$("$tf_path" init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) + init_output=$("$tf_init_path" init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) exit_code=$? if [ $exit_code -eq 0 ]; then From c615e24b293e080fe7c320fa273cd86a2f050d45 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 22 Jan 2026 18:33:36 +0200 Subject: [PATCH 6/7] Apply suggestions from code review Co-authored-by: George Yermulnik (Georgii Iermulnik) --- hooks/_common.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index 69eeb6f7b..3f69b5687 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -645,9 +645,9 @@ function common::tofu_version_ge_1.10 { # New functional added in v1.10.0 (June 2025) if [[ $major -gt 1 || ($major -eq 1 && $minor -ge 10) ]]; then return 0 - else - return 1 fi + + return 1 } ####################################################################### From a6689a8aa55875e415f791317123120f6fb5b2a8 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 22 Jan 2026 19:07:28 +0200 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: George Yermulnik (Georgii Iermulnik) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- hooks/_common.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index 3f69b5687..c2bfce1a1 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -559,7 +559,7 @@ function common::terraform_init { # More details - https://github.com/opentofu/opentofu/pull/1878 # For that reason, we switch to `tofu init` when possible local tf_init_path=$tf_path - if [[ "$($tf_path -version | head -1 | grep -o '^Terraform')" == "Terraform" ]] && + if [[ "$($tf_path --version | head -1 | grep -o '^Terraform')" == "Terraform" ]] && common::tofu_version_ge_1.10; then tf_init_path=$(command -v tofu) common::colorify "green" "Using OpenTofu binary ($tf_init_path) for Terraform init operations, as it supports concurrent provider initialization." @@ -622,8 +622,8 @@ function common::export_provided_env_vars { ####################################################################### # Check if the installed Tofu version is >=1.10.0 or not # -# This function helps to determine if tofu version allow to use -# parallelism based on Tofu version +# This function determines whether the tofu version allows using +# parallelism. # # Returns: # - 0 if version >= 1.10.0 @@ -641,8 +641,7 @@ function common::tofu_version_ge_1.10 { local major minor IFS='.' read -r major minor <<< "$tofu_version" - - # New functional added in v1.10.0 (June 2025) + # New functionality was added in v1.10.0 (June 2025) if [[ $major -gt 1 || ($major -eq 1 && $minor -ge 10) ]]; then return 0 fi