Preserve JSON values during restore drills
Some checks failed
Quality / full-local-gates (push) Has been cancelled

This commit is contained in:
SimpleTest 2026-08-28 21:04:21 +03:00
parent 319cdb970b
commit 00083d17c1
3 changed files with 122 additions and 35 deletions

View File

@ -131,6 +131,7 @@ docker run --rm \
echo "Checking production restore PostgreSQL readiness" echo "Checking production restore PostgreSQL readiness"
bash test/scripts/production_offsite_backup_readiness_test.sh bash test/scripts/production_offsite_backup_readiness_test.sh
./scripts/restore-drill-env-regression.sh
echo "Checking the production read-only load safety boundary" echo "Checking the production read-only load safety boundary"
./scripts/production-readonly-load-drill.sh ./scripts/production-readonly-load-drill.sh

View File

@ -22,24 +22,39 @@ if [ ! -f "$checksum" ]; then
exit 1 exit 1
fi fi
if [ ! -f "$ROOT/.env" ]; then env_file="$ROOT/.env"
echo "Missing $ROOT/.env." >&2
if [ ! -f "$env_file" ]; then
echo "Missing $env_file." >&2
exit 1 exit 1
fi fi
set -a read_env_value() {
# shellcheck source=/dev/null key=$1
. "$ROOT/.env"
set +a
if [ "${DATABASE_MODE:-container}" != container ]; then awk -v key="$key" '
index($0, key "=") == 1 {
print substr($0, length(key) + 2)
found = 1
exit
}
END { if (!found) exit 1 }
' "$env_file"
}
database_mode=$(read_env_value DATABASE_MODE 2>/dev/null || printf container)
postgres_db=$(read_env_value POSTGRES_DB 2>/dev/null || true)
postgres_user=$(read_env_value POSTGRES_USER 2>/dev/null || true)
database_url=$(read_env_value DATABASE_URL 2>/dev/null || true)
if [ "$database_mode" != container ]; then
echo "restore-drill-compose.sh only operates on DATABASE_MODE=container." >&2 echo "restore-drill-compose.sh only operates on DATABASE_MODE=container." >&2
exit 1 exit 1
fi fi
: "${POSTGRES_DB:?Set POSTGRES_DB in .env}" : "${postgres_db:?Set POSTGRES_DB in .env}"
: "${POSTGRES_USER:?Set POSTGRES_USER in .env}" : "${postgres_user:?Set POSTGRES_USER in .env}"
: "${DATABASE_URL:?Set DATABASE_URL in .env}" : "${database_url:?Set DATABASE_URL in .env}"
dump_dir=$(CDPATH='' cd -- "$(dirname -- "$dump")" && pwd) dump_dir=$(CDPATH='' cd -- "$(dirname -- "$dump")" && pwd)
dump_name=$(basename -- "$dump") dump_name=$(basename -- "$dump")
@ -49,37 +64,41 @@ dump_name=$(basename -- "$dump")
sha256sum --check --status "$(basename -- "$checksum")" sha256sum --check --status "$(basename -- "$checksum")"
) )
docker compose exec -T db pg_restore --list <"$dump" >/dev/null compose() {
"$ROOT/scripts/compose.sh" "$env_file" "$@"
}
compose exec -T db pg_restore --list <"$dump" >/dev/null
drill_db="who_need_help_restore_drill_$(date -u +%Y%m%d%H%M%S)_$$" drill_db="who_need_help_restore_drill_$(date -u +%Y%m%d%H%M%S)_$$"
created=false created=false
cleanup() { cleanup() {
if [ "$created" = true ]; then if [ "$created" = true ]; then
docker compose exec -T db \ compose exec -T db \
dropdb --username "$POSTGRES_USER" --if-exists "$drill_db" >/dev/null dropdb --username "$postgres_user" --if-exists "$drill_db" >/dev/null
fi fi
} }
trap cleanup EXIT HUP INT TERM trap cleanup EXIT HUP INT TERM
docker compose exec -T db \ compose exec -T db \
createdb \ createdb \
--username "$POSTGRES_USER" \ --username "$postgres_user" \
--template template0 \ --template template0 \
"$drill_db" "$drill_db"
created=true created=true
docker compose exec -T db \ compose exec -T db \
pg_restore \ pg_restore \
--username "$POSTGRES_USER" \ --username "$postgres_user" \
--dbname "$drill_db" \ --dbname "$drill_db" \
--exit-on-error \ --exit-on-error \
--no-owner \ --no-owner \
--no-privileges <"$dump" --no-privileges <"$dump"
tables=$( tables=$(
docker compose exec -T db \ compose exec -T db \
psql --username "$POSTGRES_USER" --dbname "$drill_db" --tuples-only --no-align \ psql --username "$postgres_user" --dbname "$drill_db" --tuples-only --no-align \
--command " --command "
SELECT tablename SELECT tablename
FROM pg_tables FROM pg_tables
@ -94,8 +113,8 @@ total_rows=0
for table in $tables; do for table in $tables; do
restored_count=$( restored_count=$(
docker compose exec -T db \ compose exec -T db \
psql --username "$POSTGRES_USER" --dbname "$drill_db" --tuples-only --no-align \ psql --username "$postgres_user" --dbname "$drill_db" --tuples-only --no-align \
--command "SELECT count(*) FROM \"$table\";" --command "SELECT count(*) FROM \"$table\";"
) )
@ -109,19 +128,19 @@ if [ "$table_count" -eq 0 ]; then
fi fi
postgis_version=$( postgis_version=$(
docker compose exec -T db \ compose exec -T db \
psql --username "$POSTGRES_USER" --dbname "$drill_db" --tuples-only --no-align \ psql --username "$postgres_user" --dbname "$drill_db" --tuples-only --no-align \
--command "SELECT PostGIS_Lib_Version();" --command "SELECT PostGIS_Lib_Version();"
) )
case "$DATABASE_URL" in case "$database_url" in
*\?*) *\?*)
database_base=${DATABASE_URL%%\?*} database_base=${database_url%%\?*}
database_query="?${DATABASE_URL#*\?}" database_query="?${database_url#*\?}"
;; ;;
*) *)
database_base=$DATABASE_URL database_base=$database_url
database_query= database_query=
;; ;;
esac esac
@ -135,30 +154,30 @@ fi
drill_database_url="$database_prefix/$drill_db$database_query" drill_database_url="$database_prefix/$drill_db$database_query"
docker compose run --rm --no-deps \ compose run --rm --no-deps \
--env APP_ROLE=migrate \ --env APP_ROLE=migrate \
--env "DATABASE_URL=$drill_database_url" \ --env "DATABASE_URL=$drill_database_url" \
migrate /app/bin/migrate migrate /app/bin/migrate
docker compose run --rm --no-deps \ compose run --rm --no-deps \
--env APP_ROLE=migrate \ --env APP_ROLE=migrate \
--env "DATABASE_URL=$drill_database_url" \ --env "DATABASE_URL=$drill_database_url" \
migrate /app/bin/who_need_help eval 'WhoNeedHelp.Release.await_migrations()' migrate /app/bin/who_need_help eval 'WhoNeedHelp.Release.await_migrations()'
migration_count=$( migration_count=$(
docker compose exec -T db \ compose exec -T db \
psql --username "$POSTGRES_USER" --dbname "$drill_db" --tuples-only --no-align \ psql --username "$postgres_user" --dbname "$drill_db" --tuples-only --no-align \
--command "SELECT count(*) FROM schema_migrations;" --command "SELECT count(*) FROM schema_migrations;"
) )
docker compose exec -T db \ compose exec -T db \
dropdb --username "$POSTGRES_USER" "$drill_db" dropdb --username "$postgres_user" "$drill_db"
created=false created=false
trap - EXIT HUP INT TERM trap - EXIT HUP INT TERM
remaining=$( remaining=$(
docker compose exec -T db \ compose exec -T db \
psql --username "$POSTGRES_USER" --dbname postgres --tuples-only --no-align \ psql --username "$postgres_user" --dbname postgres --tuples-only --no-align \
--command "SELECT count(*) FROM pg_database WHERE datname = '$drill_db';" --command "SELECT count(*) FROM pg_database WHERE datname = '$drill_db';"
) )

View File

@ -0,0 +1,67 @@
#!/usr/bin/env bash
set -euo pipefail
umask 077
ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
source_script=${WNH_RESTORE_DRILL_SCRIPT:-"$ROOT/scripts/restore-drill-compose.sh"}
run_dir=$(mktemp -d "$ROOT/output/restore-drill-env-regression.XXXXXX")
fixture="$run_dir/project"
mock_bin="$run_dir/mock-bin"
cleanup() {
status=$?
trap - EXIT HUP INT TERM
find "$run_dir" -xdev -depth -delete 2>/dev/null || true
exit "$status"
}
trap cleanup EXIT HUP INT TERM
install -d -m 700 "$fixture/scripts" "$mock_bin"
install -m 755 "$source_script" "$fixture/scripts/restore-drill-compose.sh"
install -m 755 "$ROOT/scripts/compose.sh" "$fixture/scripts/compose.sh"
install -m 600 /dev/null "$fixture/.env"
printf '%s\n' \
'DEPLOYMENT_TARGET=compose' \
'DEPLOYMENT_ENV=test' \
'COMPOSE_PROJECT_NAME=who_need_help_restore_drill_env_regression' \
'DATABASE_MODE=container' \
'APP_TOPOLOGY=compact' \
'PUBLIC_EDGE_ENABLED=false' \
'EMAIL_DELIVERY_PROVIDER=smtp' \
'SMTP_RELAY=smtp.example.invalid' \
'POSTGRES_DB=who_need_help_test' \
'POSTGRES_USER=who_need_help_test' \
'POSTGRES_PASSWORD=restore-drill-regression' \
'DATABASE_URL=ecto://who_need_help_test:restore-drill-regression@db/who_need_help_test' \
'RATE_LIMIT_POLICIES_JSON={"registration_email":{"limit":4,"window_seconds":3600}}' \
>"$fixture/.env"
dump="$run_dir/test.dump"
printf 'restore drill regression fixture\n' >"$dump"
printf '%s %s\n' "$(sha256sum "$dump" | awk '{print $1}')" \
"$(basename -- "$dump")" >"$dump.sha256"
cat >"$mock_bin/docker" <<'EOF'
#!/bin/sh
set -eu
if [ "${RATE_LIMIT_POLICIES_JSON+x}" = x ]; then
echo "RATE_LIMIT_POLICIES_JSON leaked through shell evaluation of .env." >&2
exit 97
fi
case "$*" in
*"SELECT tablename"*) printf 'example_table\n' ;;
*'SELECT count(*) FROM "example_table";'*) printf '1\n' ;;
*'SELECT PostGIS_Lib_Version();'*) printf '3.6.4\n' ;;
*'SELECT count(*) FROM schema_migrations;'*) printf '29\n' ;;
*'SELECT count(*) FROM pg_database'*) printf '0\n' ;;
esac
EOF
chmod 755 "$mock_bin/docker"
PATH="$mock_bin:$PATH" \
"$fixture/scripts/restore-drill-compose.sh" "$dump" >/dev/null
echo "Restore drill treats .env as data and preserves JSON values for Compose."