repositories / bugabinga.net
bugabinga.net
personal infrastructure for bugabinga!
owned by admin
scripts/local/tofu.sh
Raw#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "${repo_root}"
usage() {
cat >&2 <<'USAGE'
usage: tofu.sh ACTION [ARGS...]
Actions:
init
migrate BACKUP
migrate-auth
state [local]
unlock LOCK_ID
test lock
test restore BACKUP
test auth-migration
test backend-failure
plan
apply
output NAME
USAGE
exit 2
}
require_args() {
local expected="$1"
shift
[[ "$#" -eq "${expected}" ]] || usage
}
state_list_file() {
(cd / && tofu state list -state="$1")
}
migrate_state() (
require_args 1 "$@"
local backup="$1" before after
before="$(mktemp)"
after="$(mktemp)"
trap 'rm -f -- "${before}" "${after}"' EXIT
state_list_file "${repo_root}/terraform.tfstate" >"${before}"
[[ ! -e "${backup}" ]] || {
echo "backup already exists: ${backup}" >&2
exit 1
}
install -m 600 "${repo_root}/terraform.tfstate" "${backup}"
cmp -s "${repo_root}/terraform.tfstate" "${backup}"
tofu init -migrate-state
tofu state list >"${after}"
diff -u "${before}" "${after}"
)
migrate_auth() (
require_args 0 "$@"
local target lock temporary
target="${repo_root}/auth.auto.tfvars.json"
lock="${repo_root}/.auth.auto.tfvars.lock"
[[ ! -e "${target}" ]] || {
echo "target already exists: ${target}" >&2
exit 1
}
exec 9>"${lock}"
flock -x 9
temporary="$(mktemp "${repo_root}/.auth-migration.XXXXXX")"
trap 'rm -f -- "${temporary}"' EXIT
printf '%s\n' 'nonsensitive(jsonencode({zot_htpasswd_content=var.zot_htpasswd_content,hallucygenie_basic_auth_users=var.hallucygenie_basic_auth_users}))' |
tofu console -no-color |
jq -er . >"${temporary}"
jq -e 'type == "object" and has("zot_htpasswd_content") and has("hallucygenie_basic_auth_users")' "${temporary}" >/dev/null
chmod 600 "${temporary}"
mv "${temporary}" "${target}"
echo "migrated auth variables to ${target}"
echo "remove zot_htpasswd_content and hallucygenie_basic_auth_users from secrets.auto.tfvars, then run just plan"
)
delete_test_objects() {
local key="$1"
wrangler r2 object delete "bugabinga-net-tofu-state/${key}.tflock" --remote >/dev/null 2>&1 || true
wrangler r2 object delete "bugabinga-net-tofu-state/${key}" --remote >/dev/null 2>&1 || true
}
test_lock() (
local id key directory lock output
id="$(cat /proc/sys/kernel/random/uuid)"
key="lock-tests/${id}/terraform.tfstate"
directory="$(mktemp -d)"
lock="$(mktemp)"
output="$(mktemp)"
trap 'delete_test_objects "${key}"; rm -rf -- "${directory}"; rm -f -- "${lock}" "${output}"' EXIT
cp backend.tf "${directory}/backend.tf"
printf 'resource "terraform_data" "lock_probe" { input = "probe" }\n' >"${directory}/main.tf"
(cd "${directory}" && tofu init -reconfigure -backend-config="key=${key}") >/dev/null
printf '{"ID":"%s","Operation":"OperationTypeApply","Info":"lock contention test","Who":"just tofu test lock","Version":"%s","Created":"%s","Path":"%s"}\n' \
"${id}" "$(tofu version -json | jq -r .terraform_version)" "$(date --utc +%Y-%m-%dT%H:%M:%S.%NZ)" "${key}" >"${lock}"
wrangler r2 object put "bugabinga-net-tofu-state/${key}.tflock" --remote --file "${lock}" >/dev/null
if (cd "${directory}" && tofu apply --auto-approve --input=false --refresh=false --lock-timeout=0s) >"${output}" 2>&1; then
cat "${output}" >&2
echo "lock contention was not rejected" >&2
exit 1
fi
grep -F "${id}" "${output}" >/dev/null
echo "lock contention rejected: ${id}"
)
test_restore() (
require_args 1 "$@"
local backup="$1" id key directory before after
[[ -f "${backup}" ]] || {
echo "backup not found: ${backup}" >&2
exit 1
}
id="$(cat /proc/sys/kernel/random/uuid)"
key="recovery-tests/${id}/terraform.tfstate"
directory="$(mktemp -d)"
before="$(mktemp)"
after="$(mktemp)"
trap 'delete_test_objects "${key}"; rm -rf -- "${directory}"; rm -f -- "${before}" "${after}"' EXIT
cp backend.tf "${directory}/backend.tf"
state_list_file "${backup}" >"${before}"
wrangler r2 object put "bugabinga-net-tofu-state/${key}" --remote --file "${backup}" >/dev/null
(cd "${directory}" && tofu init -reconfigure -backend-config="key=${key}") >/dev/null
(cd "${directory}" && tofu state list) >"${after}"
diff -u "${before}" "${after}"
echo "backup restored and verified: ${id}"
)
test_auth_migration() (
local directory output
directory="$(mktemp -d)"
output="$(mktemp)"
trap 'rm -rf -- "${directory}"; rm -f -- "${output}"' EXIT
cat >"${directory}/main.tf" <<'HCL'
variable "zot_htpasswd_content" {
type = string
sensitive = true
}
variable "hallucygenie_basic_auth_users" {
type = string
sensitive = true
}
HCL
cat >"${directory}/fixture.auto.tfvars.json" <<'JSON'
{
"zot_htpasswd_content": "alice:$2a$12$fixture\n",
"hallucygenie_basic_auth_users": "alice $2a$14$fixture\n"
}
JSON
(
cd "${directory}"
printf '%s\n' 'nonsensitive(jsonencode({zot_htpasswd_content=var.zot_htpasswd_content,hallucygenie_basic_auth_users=var.hallucygenie_basic_auth_users}))' |
tofu console -no-color |
jq -er .
) >"${output}"
jq -e '
.zot_htpasswd_content == "alice:$2a$12$fixture\n"
and .hallucygenie_basic_auth_users == "alice $2a$14$fixture\n"
' "${output}" >/dev/null
echo "auth migration conversion valid"
)
test_backend_failure() (
local directory output
directory="$(mktemp -d)"
output="$(mktemp)"
trap 'rm -rf -- "${directory}"; rm -f -- "${output}"' EXIT
sed 's#https://c9526445475a4d62fd6e330810573ef0.r2.cloudflarestorage.com#http://127.0.0.1:1#' backend.tf >"${directory}/backend.tf"
if (cd "${directory}" && AWS_ACCESS_KEY_ID=invalid AWS_SECRET_ACCESS_KEY=invalid AWS_MAX_ATTEMPTS=1 tofu init -reconfigure) >"${output}" 2>&1; then
cat "${output}" >&2
echo "unavailable backend unexpectedly initialized" >&2
exit 1
fi
grep -F '127.0.0.1:1' "${output}" >/dev/null
[[ ! -e "${directory}/terraform.tfstate" ]]
echo "unavailable backend failed closed"
)
action="${1:-}"
[[ -n "${action}" ]] || usage
shift
case "${action}" in
init)
require_args 0 "$@"
tofu init
;;
migrate)
migrate_state "$@"
;;
migrate-auth)
migrate_auth "$@"
;;
state)
if [[ "${1:-}" == "local" ]]; then
require_args 1 "$@"
state_list_file "${repo_root}/terraform.tfstate"
else
require_args 0 "$@"
tofu state list
fi
;;
unlock)
require_args 1 "$@"
tofu force-unlock "$1"
;;
test)
test_name="${1:-}"
[[ -n "${test_name}" ]] || usage
shift
case "${test_name}" in
lock)
require_args 0 "$@"
test_lock
;;
restore) test_restore "$@" ;;
auth-migration)
require_args 0 "$@"
test_auth_migration
;;
backend-failure)
require_args 0 "$@"
test_backend_failure
;;
*) usage ;;
esac
;;
plan)
require_args 0 "$@"
tofu plan --input=false --out=tfplan
;;
apply)
require_args 0 "$@"
tofu apply --auto-approve -parallelism=1 tfplan
;;
output)
require_args 1 "$@"
tofu output -raw "$1"
;;
*)
usage
;;
esac