#!/bin/sh # Rehearses a logical MySQL backup into a separate disposable database. # It never points at an existing database and does not modify a production target. set -eu TASK_DOCKER=${DOCKER:-docker} TASK_MIGRATE=${ORBIT_MIGRATE_BIN:-bin/orbit-migrate} TASK_ADMIN=${ORBIT_ADMIN_BIN:-bin/orbit-admin} TASK_SOURCE_NAME="orbit-backup-source-$$" TASK_RESTORE_NAME="orbit-backup-restore-$$" TASK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/orbit-backup.XXXXXX") TASK_SOURCE_PID= TASK_RESTORE_PID= cleanup() { "$TASK_DOCKER" rm -f "$TASK_SOURCE_NAME" "$TASK_RESTORE_NAME" >/dev/null 2>&1 || true for task_artifact in "$TASK_DIR"/*; do if [ -f "$task_artifact" ]; then unlink "$task_artifact"; fi done rmdir "$TASK_DIR" 2>/dev/null || true } trap cleanup EXIT HUP INT TERM if [ ! -x "$TASK_MIGRATE" ] || [ ! -x "$TASK_ADMIN" ]; then printf '%s\n' 'orbit-migrate and orbit-admin binaries are required; build them before this rehearsal.' >&2 exit 2 fi umask 077 TASK_SOURCE_PASSWORD=$(openssl rand -hex 24) TASK_RESTORE_PASSWORD=$(openssl rand -hex 24) start_mysql() { container=$1 database=$2 password=$3 "$TASK_DOCKER" run -d --name "$container" \ -e MYSQL_ROOT_PASSWORD="$password" \ -e MYSQL_DATABASE="$database" \ -p 127.0.0.1::3306 "$ORBIT_TEST_MYSQL_IMAGE" >/dev/null ready=0 for attempt in $(seq 1 60); do if "$TASK_DOCKER" exec -e MYSQL_PWD="$password" "$container" \ mysql -uroot --protocol=tcp -e 'SELECT 1' >/dev/null 2>&1; then ready=1 break fi sleep 1 done if [ "$ready" -ne 1 ]; then printf 'MySQL container did not become ready: %s\n' "$container" >&2 "$TASK_DOCKER" logs --tail 30 "$container" >&2 || true exit 1 fi } query() { container=$1 password=$2 database=$3 sql=$4 "$TASK_DOCKER" exec -e MYSQL_PWD="$password" "$container" \ mysql -uroot --protocol=tcp --batch --skip-column-names "$database" -e "$sql" } ORBIT_TEST_MYSQL_IMAGE=${ORBIT_TEST_MYSQL_IMAGE:-mysql:8.4} start_mysql "$TASK_SOURCE_NAME" orbit_source "$TASK_SOURCE_PASSWORD" start_mysql "$TASK_RESTORE_NAME" orbit_restore "$TASK_RESTORE_PASSWORD" TASK_SOURCE_PORT=$("$TASK_DOCKER" port "$TASK_SOURCE_NAME" 3306/tcp | sed 's/.*://') TASK_RESTORE_PORT=$("$TASK_DOCKER" port "$TASK_RESTORE_NAME" 3306/tcp | sed 's/.*://') TASK_SOURCE_DSN="root:$TASK_SOURCE_PASSWORD@tcp(127.0.0.1:$TASK_SOURCE_PORT)/orbit_source" TASK_RESTORE_DSN="root:$TASK_RESTORE_PASSWORD@tcp(127.0.0.1:$TASK_RESTORE_PORT)/orbit_restore" ORBIT_MYSQL_DSN="$TASK_SOURCE_DSN" "$TASK_MIGRATE" up >/dev/null ORBIT_MYSQL_DSN="$TASK_SOURCE_DSN" "$TASK_ADMIN" bootstrap \ --application backup-rehearsal --namespace default --environment test \ --queue restore-check --output "$TASK_DIR/credentials.json" >/dev/null TASK_SOURCE_COUNTS=$(query "$TASK_SOURCE_NAME" "$TASK_SOURCE_PASSWORD" orbit_source \ "SELECT CONCAT((SELECT COUNT(*) FROM orbit_applications),',',(SELECT COUNT(*) FROM orbit_namespaces),',',(SELECT COUNT(*) FROM orbit_credentials),',',(SELECT COUNT(*) FROM pulse_queues),',',(SELECT COUNT(*) FROM orbit_schema_migrations));") "$TASK_DOCKER" exec -e MYSQL_PWD="$TASK_SOURCE_PASSWORD" "$TASK_SOURCE_NAME" \ mysqldump -uroot --protocol=tcp --single-transaction --routines --triggers --events \ --hex-blob --no-tablespaces orbit_source > "$TASK_DIR/orbit.sql" "$TASK_DOCKER" exec -i -e MYSQL_PWD="$TASK_RESTORE_PASSWORD" "$TASK_RESTORE_NAME" \ mysql -uroot --protocol=tcp orbit_restore < "$TASK_DIR/orbit.sql" TASK_VERIFY=$(ORBIT_MYSQL_DSN="$TASK_RESTORE_DSN" "$TASK_ADMIN" verify) TASK_RESTORE_COUNTS=$(query "$TASK_RESTORE_NAME" "$TASK_RESTORE_PASSWORD" orbit_restore \ "SELECT CONCAT((SELECT COUNT(*) FROM orbit_applications),',',(SELECT COUNT(*) FROM orbit_namespaces),',',(SELECT COUNT(*) FROM orbit_credentials),',',(SELECT COUNT(*) FROM pulse_queues),',',(SELECT COUNT(*) FROM orbit_schema_migrations));") TASK_FOREIGN_KEYS=$(query "$TASK_RESTORE_NAME" "$TASK_RESTORE_PASSWORD" information_schema \ "SELECT COUNT(*) FROM REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA='orbit_restore' AND LEFT(TABLE_NAME,6) IN ('orbit_','pulse_','weave_');") if [ "$TASK_SOURCE_COUNTS" != "$TASK_RESTORE_COUNTS" ]; then printf 'restored core counts differ: source=%s restore=%s\n' "$TASK_SOURCE_COUNTS" "$TASK_RESTORE_COUNTS" >&2 exit 1 fi if [ "$TASK_FOREIGN_KEYS" != 0 ]; then printf 'restored schema has %s Orbit foreign keys\n' "$TASK_FOREIGN_KEYS" >&2 exit 1 fi printf '{"source_core_counts":"%s","restore_core_counts":"%s","restore_foreign_key_count":%s,"verify":%s}\n' \ "$TASK_SOURCE_COUNTS" "$TASK_RESTORE_COUNTS" "$TASK_FOREIGN_KEYS" "$TASK_VERIFY"