Files
coder/provisioner/terraform/testdata/generate.sh
T
Mathias Fredriksson 7fb93dbf0e build: lock provider version in provisioner/terraform/testdata (#23776)
The terraform testdata fixtures silently drift when the coder provider
releases a new version. The .terraform.lock.hcl files are gitignored,
.tf files use loose constraints (>= 2.0.0), and generate.sh always
runs terraform init -upgrade. The Makefile only re-runs generate.sh
when the terraform CLI version changes, not the provider version.

Track a canonical lockfile and provider-version.txt in git. Change
generate.sh to respect the lockfile by default (terraform init without
-upgrade). Add --upgrade flag for intentional provider bumps, --check
for cheap staleness detection in the Makefile, and a new
update-terraform-testdata make target.
2026-03-30 16:37:25 +03:00

205 lines
5.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Resolve paths before cd so they're absolute.
scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$scriptdir/resources"
canonical_lock="$(pwd)/.terraform.lock.hcl"
# These environment variables influence the coder provider.
for v in $(env | grep -E '^CODER_' | cut -d= -f1); do
unset "$v"
done
generate() {
local name="$1"
echo "=== BEGIN: $name"
if ((upgrade)); then
terraform init -upgrade
else
terraform init
fi &&
terraform plan -out terraform.tfplan &&
terraform show -json ./terraform.tfplan | jq >"$name".tfplan.json &&
terraform graph -type=plan >"$name".tfplan.dot &&
rm terraform.tfplan &&
terraform apply -auto-approve &&
terraform show -json ./terraform.tfstate | jq >"$name".tfstate.json &&
rm terraform.tfstate &&
terraform graph -type=plan >"$name".tfstate.dot
ret=$?
echo "=== END: $name"
if [[ $ret -ne 0 ]]; then
return $ret
fi
}
minimize_diff() {
for f in *.tf*.json; do
declare -A deleted=()
declare -a sed_args=()
while read -r line; do
# Deleted line (previous value).
if [[ $line = -\ * ]]; then
key="${line#*\"}"
key="${key%%\"*}"
value="${line#*: }"
value="${value#*\"}"
value="\"${value%\"*}\""
declare deleted["$key"]="$value"
# Added line (new value).
elif [[ $line = +\ * ]]; then
key="${line#*\"}"
key="${key%%\"*}"
value="${line#*: }"
value="${value#*\"}"
value="\"${value%\"*}\""
# Matched key, restore the value.
if [[ -v deleted["$key"] ]]; then
sed_args+=(-e "s|${value}|${deleted["$key"]}|")
unset "deleted[$key]"
fi
fi
if [[ ${#sed_args[@]} -gt 0 ]]; then
# Handle macOS compat.
if grep -q -- "\[-i extension\]" < <(sed -h 2>&1); then
sed -i '' "${sed_args[@]}" "$f"
else
sed -i'' "${sed_args[@]}" "$f"
fi
fi
done < <(
# Filter out known keys with autogenerated values.
git diff -- "$f" |
grep -E "\"(terraform_version|id|agent_id|subagent_id|resource_id|token|random|timestamp)\":"
)
done
}
run() {
d="$1"
cd "$d"
name=$(basename "$(pwd)")
toskip=(
# This needs care to update correctly.
"kubernetes-metadata"
# Multiple resources with duplicate JSON key names (id, agent_id)
# cause minimize_diff() to scramble UUIDs. Hand-crafted fixture.
"duplicate-env-keys"
)
for skip in "${toskip[@]}"; do
if [[ $name == "$skip" ]]; then
echo "== Skipping: $name"
touch "$name.tfplan.json" "$name.tfplan.dot" "$name.tfstate.json" "$name.tfstate.dot"
return 0
fi
done
echo "== Generating test data for: $name"
if ! out="$(generate "$name" 2>&1)"; then
echo "$out"
echo "== Error generating test data for: $name"
return 1
fi
if ((minimize)); then
echo "== Minimizing diffs for: $name"
minimize_diff
fi
echo "== Done generating test data for: $name"
exit 0
}
if [[ " $* " == *" --help "* || " $* " == *" -h "* ]]; then
echo "Usage: $0 [--upgrade] [--check] [--no-minimize] [module1 module2 ...]"
exit 0
fi
minimize=1
if [[ " $* " == *" --no-minimize "* ]]; then
minimize=0
fi
upgrade=0
if [[ " $* " == *" --upgrade "* ]]; then
upgrade=1
fi
# Verify that the canonical lockfile matches provider-version.txt.
if [[ " $* " == *" --check "* ]]; then
expected="$(<"$scriptdir/provider-version.txt")"
actual="$(sed -n '/coder\/coder/,/^}/{ /version[[:space:]]*=/{ s/.*"\(.*\)"/\1/; p; q; } }' "$canonical_lock")"
if [[ "$expected" == "$actual" ]]; then
exit 0
else
echo "ERROR: provider-version.txt ($expected) does not match lockfile ($actual)"
exit 1
fi
fi
# Filter flags from positional args to get directory names.
declare -a dirs=()
for arg in "$@"; do
case "$arg" in
--upgrade | --no-minimize | --check | --help | -h) ;;
*) dirs+=("$arg") ;;
esac
done
# Seed each resource subdirectory with the canonical lockfile.
for d in */; do
cp "$canonical_lock" "$d/.terraform.lock.hcl"
done
declare -a jobs=()
if [[ ${#dirs[@]} -gt 0 ]]; then
for d in "${dirs[@]}"; do
run "$d" &
jobs+=($!)
done
else
for d in */; do
run "$d" &
jobs+=($!)
done
fi
err=0
for job in "${jobs[@]}"; do
if ! wait "$job"; then
err=$((err + 1))
fi
done
if [[ $err -ne 0 ]]; then
echo "ERROR: Failed to generate test data for $err modules"
exit 1
fi
# After upgrade, promote the lockfile from a representative directory
# back to the canonical location and record the provider version.
if ((upgrade)); then
# Prefer rich-parameters since it uses all providers (coder, null, docker).
src=""
if [[ -f "rich-parameters/.terraform.lock.hcl" ]]; then
src="rich-parameters/.terraform.lock.hcl"
else
for d in */; do
if [[ -f "$d/.terraform.lock.hcl" ]]; then
src="$d/.terraform.lock.hcl"
break
fi
done
fi
if [[ -n "$src" ]]; then
cp "$src" "$canonical_lock"
version="$(sed -n '/coder\/coder/,/^}/{ /version[[:space:]]*=/{ s/.*"\(.*\)"/\1/; p; q; } }' "$canonical_lock")"
echo "$version" >"$scriptdir/provider-version.txt"
echo "== Updated canonical lockfile and provider-version.txt (coder provider $version)"
fi
fi
terraform version -json | jq -r '.terraform_version' >../version.txt