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 7, 2024
1 parent 0f8a096 commit e447bd1
Show file tree
Hide file tree
Showing 12 changed files with 192 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$
15 changes: 15 additions & 0 deletions hooks/provider-pinned-versions/check
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/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
}

check_files "$@"
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"
}
}
}
22 changes: 22 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,22 @@
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"
}
}
}
8 changes: 8 additions & 0 deletions hooks/provider-pinned-versions/fixtures/pass_when_inlined.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
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" }
}
}
14 changes: 14 additions & 0 deletions hooks/provider-pinned-versions/fixtures/pass_when_pre_release.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-beta-some-branch-name"
}
newrelic = {
source = "newrelic/newrelic"
version = "3.52.1"
}
}
}
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 = {
version = "5.78.0"
source = "hashicorp/aws"
}
cyral = {
source = "cyralinc/cyral"
version = "4.14.1"
}
}
}
54 changes: 54 additions & 0 deletions hooks/provider-pinned-versions/required_providers.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#! /bin/awk

BEGIN {
in_required_providers = 0;
brace_count = 0;
version_prefix_regex = ".*version[[:space:]]+=[[:space:]]+";
provider_prefix_regex = "[a-z_-]+[[:space:]]+=[[:space:]]+\{";
pinned_version_regex = "[0-9]+\.[0-9]+\.[0-9]+";
version_constraints_regex = "[!~><]+";
}

{
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 ($0 ~ provider_prefix_regex) {
provider = $1;
}

# If the line doesn't contain a version, skip
if (!($0 ~ version_prefix_regex)) {
next;
} else {
# If the line contains a version, remove the prefix
gsub(version_prefix_regex, "", $0);
if ($0 ~ version_constraints_regex) {
# remove trailing curly brace if any (happens when version is inlined)
gsub(/}/, "", $0);
error[++i] = "ERROR: '" provider "' version not in pinned format: " $0 " in file " FILENAME;
}
}
}
}

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

# shellcheck disable=SC2207
files=($(find $script_directory/fixtures -type f -name "pass_*.tf"))
for file in "${files[@]}"; do
echo "testing: check $file"
"$script_directory/check" "$file"
done

# shellcheck disable=SC2207
files=($(find $script_directory/fixtures -type f -name "fail_*.tf"))
for file in "${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/provider-pinned-versions/test"

0 comments on commit e447bd1

Please sign in to comment.