Skip to content

Commit

Permalink
feat: add hook for provider version checking
Browse files Browse the repository at this point in the history
  • Loading branch information
yohanb committed Dec 6, 2024
1 parent 0f8a096 commit e20d967
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@

# Crash log files
crash.log

.vscode/
8 changes: 8 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,11 @@
language: script
files: \.tf$
exclude: ^outputs\..*\.tf$

- id: provider-pinned-versions
name: "Ensures provider versions are pinned.`"
description: "Ensures Terraform providers are pinned and not in a constrained format."
entry: hooks/provider-pinned-versions/check
language: script
files: \.tf$
exclude: ^(variables|outputs)\.{0,1}.*\.tf$
19 changes: 19 additions & 0 deletions hooks/provider-pinned-versions/check
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AWK_FILE="${SCRIPT_DIR}/required_providers.awk"

check_files() {
has_error=0
for file in "$@"; do
awk -f "$AWK_FILE" "$file" || has_error=1
done
return $has_error
}

if ! check_files "$@"; then
echo "Providers defined in the 'required_providers' block are not pinned to a specific version."
echo "See: https://developer.hashicorp.com/terraform/language/expressions/version-constraints"
fi

exit $has_error
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.0.0, < 2.0.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~>5.78"
}
newrelic = {
source = "newrelic/newrelic"
version = ">=2, <3"
}
}
}
14 changes: 14 additions & 0 deletions hooks/provider-pinned-versions/fixtures/fail_when_mixed.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.0.0, < 2.0.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "5.78.0"
}
newrelic = {
source = "newrelic/newrelic"
version = ">=2, <3"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.0.0, < 2.0.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "5.78"
}
newrelic = {
source = "newrelic/newrelic"
version = "3.52.1"
}
}
}
26 changes: 26 additions & 0 deletions hooks/provider-pinned-versions/fixtures/pass_when_all_pinned.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
terraform {
required_version = ">= 1.0.0, < 2.0.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "5.78.0"
}
cyral = {
source = "cyralinc/cyral"
version = "4.14.1"
}
mysql = {
source = "petoju/mysql"
version = "3.0.67"
}
newrelic = {
source = "newrelic/newrelic"
version = "3.52.1"
}
turo = {
source = "app.terraform.io/turo/turo"
version = "8.76.0"
}
}
}
55 changes: 55 additions & 0 deletions hooks/provider-pinned-versions/required_providers.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#! /bin/awk

BEGIN {
# Initialize variables
in_required_providers = 0;
brace_count = 0;
version_prefix_regex = "[[:space:]]*version[[:space:]]+=[[:space:]]+";
}

{
# Check if the line contains "required_providers"
if ($0 ~ /required_providers/) {
in_required_providers = 1;
}

# If inside "required_providers" block, count braces
if (in_required_providers) {
brace_count += gsub(/{/, "{");
brace_count -= gsub(/}/, "}");

# If brace count returns to 0, exit the block
if (brace_count == 0) {
in_required_providers = 0;
next;
}

# If the line contains a provider, parse the version
if ($0 ~ /[a-z_-]+[[:space:]]+=[[:space:]]+\{/) {
provider = $1;
# TODO: This doesn't support the following cases:
# - provider version is inlined: provider = { source = "blah" version = "1.2.3" }
# - provider version isn't the second attribute
getline;
getline;
if ($0 ~ version_prefix_regex) {
gsub(version_prefix_regex, "", $0);
if (!($0 ~ /[0-9]+\.[0-9]+\.[0-9]+/)) {
error[++i] = "'" provider "' version not in pinned format: " $0 " in file " FILENAME;
}
}
else {
error[++i] = "No version attribute specified for provider: '" provider "' in file " FILENAME;
}
}
}
}

END {
if (length(error) > 0) {
for (j = 1; j <= i; j++) {
print error[j]
}
exit 1;
}
}
35 changes: 35 additions & 0 deletions hooks/provider-pinned-versions/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash -e

# get the directory of the script
script_directory="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"

echo "testing: $script_directory"

# create an array with expected passing files
passing_files=(
"$script_directory/fixtures/pass_when_all_pinned.tf"
)

# loop over files and check them
for file in "${passing_files[@]}"; do
echo "testing: check $file"
"$script_directory/check" "$file"
done

failing_files=(
"$script_directory/fixtures/fail_when_all_constraints.tf"
"$script_directory/fixtures/fail_when_mixed.tf"
"$script_directory/fixtures/fail_when_not_full_semver.tf"
)

# loop over files and check them
for file in "${failing_files[@]}"; do
echo "testing: check $file"
echo " expecting error"
if "$script_directory/check" "$file"; then
echo "ERROR: should have failed"
exit 1
fi
done

echo "testing: PASS"
1 change: 1 addition & 0 deletions script/test
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ REPO_DIR="$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pw

"$REPO_DIR/hooks/outputs-in-outputs-files/test"
"$REPO_DIR/hooks/vars-in-variables-files/test"
"$REPO_DIR/hooks/vars-in-variables-files/test"

0 comments on commit e20d967

Please sign in to comment.