Initial public release
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CONTAINER="geointel"
|
||||
BACKUP_DIR=""
|
||||
OUTPUT=""
|
||||
CONFIRM="false"
|
||||
RESTORE_RESULT=""
|
||||
TARGET_DB=""
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: bash scripts/verify_release_upgrade_smoke.sh \
|
||||
--backup-dir PATH --confirm-isolated-upgrade [options]
|
||||
|
||||
Restores a verified release backup into a generated temporary database, runs
|
||||
the currently deployed image's Alembic upgrade against that database, verifies
|
||||
PostGIS and the single expected head, then removes the temporary database.
|
||||
|
||||
Options:
|
||||
--container NAME
|
||||
--output PATH
|
||||
EOF
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--backup-dir) BACKUP_DIR="$2"; shift 2 ;;
|
||||
--container) CONTAINER="$2"; shift 2 ;;
|
||||
--output) OUTPUT="$2"; shift 2 ;;
|
||||
--confirm-isolated-upgrade) CONFIRM="true"; shift ;;
|
||||
--help|-h) usage; exit 0 ;;
|
||||
*) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$CONFIRM" != "true" ] || [ -z "$BACKUP_DIR" ]; then
|
||||
echo "Explicit --confirm-isolated-upgrade and --backup-dir are required." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
for required in docker python3; do
|
||||
command -v "$required" >/dev/null 2>&1 || {
|
||||
echo "Missing required command: $required" >&2
|
||||
exit 2
|
||||
}
|
||||
done
|
||||
|
||||
BACKUP_DIR="$(python3 -c 'import pathlib,sys; print(pathlib.Path(sys.argv[1]).expanduser().resolve())' "$BACKUP_DIR")"
|
||||
RESTORE_RESULT="$(mktemp "${TMPDIR:-/tmp}/geointel-upgrade-restore.XXXXXX.json")"
|
||||
|
||||
cleanup() {
|
||||
if [ -n "$TARGET_DB" ] && [[ "$TARGET_DB" =~ ^geointel_restore_verify_[0-9_]+$ ]]; then
|
||||
db_user="$(docker exec "$CONTAINER" sh -c 'printf %s "${POSTGRES_USER:-${GEOINTEL_POSTGRES_USER:-geointel}}"' 2>/dev/null || true)"
|
||||
if [ -n "$db_user" ]; then
|
||||
docker exec "$CONTAINER" dropdb --if-exists -U "$db_user" "$TARGET_DB" >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
rm -f -- "$RESTORE_RESULT"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
bash "$(dirname "$0")/restore_release_backup_smoke.sh" \
|
||||
--backup-dir "$BACKUP_DIR" \
|
||||
--container "$CONTAINER" \
|
||||
--confirm-isolated-restore \
|
||||
--keep-database \
|
||||
--output "$RESTORE_RESULT"
|
||||
|
||||
TARGET_DB="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["temporary_database"])' "$RESTORE_RESULT")"
|
||||
if ! [[ "$TARGET_DB" =~ ^geointel_restore_verify_[0-9_]+$ ]]; then
|
||||
echo "Unsafe temporary database returned by restore smoke: ${TARGET_DB}" >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
docker exec -e TARGET_DB="$TARGET_DB" "$CONTAINER" sh -c '
|
||||
target_url="$(python -c '"'"'
|
||||
import os
|
||||
from sqlalchemy import URL
|
||||
|
||||
print(
|
||||
URL.create(
|
||||
"postgresql+psycopg",
|
||||
username=os.environ["GEOINTEL_POSTGRES_USER"],
|
||||
password=os.environ["GEOINTEL_POSTGRES_PASSWORD"],
|
||||
host="127.0.0.1",
|
||||
port=5432,
|
||||
database=os.environ["TARGET_DB"],
|
||||
)
|
||||
.render_as_string(hide_password=False)
|
||||
)
|
||||
'"'"')"
|
||||
export DATABASE_URL="$target_url"
|
||||
python -m alembic upgrade head
|
||||
'
|
||||
|
||||
DB_USER="$(docker exec "$CONTAINER" sh -c 'printf %s "${POSTGRES_USER:-${GEOINTEL_POSTGRES_USER:-geointel}}"' )"
|
||||
ALEMBIC_HEAD="$(docker exec "$CONTAINER" psql -X -v ON_ERROR_STOP=1 -U "$DB_USER" -d "$TARGET_DB" -Atqc \
|
||||
"SELECT version_num FROM alembic_version;")"
|
||||
EXPECTED_HEAD="$(docker exec "$CONTAINER" sh -c 'python -m alembic heads | awk "{print \$1}"')"
|
||||
POSTGIS_VERSION="$(docker exec "$CONTAINER" psql -X -v ON_ERROR_STOP=1 -U "$DB_USER" -d "$TARGET_DB" -Atqc \
|
||||
"SELECT postgis_version();")"
|
||||
RELEASE_REVISION="$(docker inspect --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' "$CONTAINER")"
|
||||
|
||||
if [ "$ALEMBIC_HEAD" != "$EXPECTED_HEAD" ]; then
|
||||
echo "Upgraded temporary database head '${ALEMBIC_HEAD}' differs from '${EXPECTED_HEAD}'." >&2
|
||||
exit 4
|
||||
fi
|
||||
|
||||
if [ -z "$OUTPUT" ]; then
|
||||
OUTPUT="${BACKUP_DIR%/}-upgrade-smoke.json"
|
||||
fi
|
||||
python3 - "$OUTPUT" <<PY
|
||||
import json
|
||||
import pathlib
|
||||
|
||||
payload = {
|
||||
"schema_version": 1,
|
||||
"verified_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"backup_dir": ${BACKUP_DIR@Q},
|
||||
"release_revision": ${RELEASE_REVISION@Q},
|
||||
"temporary_database": ${TARGET_DB@Q},
|
||||
"production_database_untouched": True,
|
||||
"temporary_database_retained": False,
|
||||
"alembic_head": ${ALEMBIC_HEAD@Q},
|
||||
"postgis_version": ${POSTGIS_VERSION@Q},
|
||||
"upgrade_status": "passed",
|
||||
}
|
||||
path = pathlib.Path(__import__("sys").argv[1]).expanduser().resolve()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
PY
|
||||
|
||||
cleanup
|
||||
trap - EXIT
|
||||
echo "Isolated release upgrade passed at ${ALEMBIC_HEAD}; temporary database removed."
|
||||
Reference in New Issue
Block a user