Files
api/scripts/clone-live-db-and-test.sh

174 lines
6.1 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
TEST_COMMAND="${TEST_COMMAND:-composer test:unit}"
SOURCE_TARGET="${SOURCE_TARGET:-}"
SOURCE_HOST="${SOURCE_HOST:-${CONFIG_DB_HOST:-}}"
SOURCE_PORT="${SOURCE_PORT:-${CONFIG_DB_PORT:-3306}}"
SOURCE_DATABASE="${SOURCE_DATABASE:-${CONFIG_DB_DATABASE:-}}"
SOURCE_USER="${SOURCE_USER:-${CONFIG_DB_USER:-}}"
SOURCE_PASSWORD="${SOURCE_PASSWORD:-${CONFIG_DB_PASSWORD:-}}"
NETWORK_NAME="${NETWORK_NAME:-api-test-net}"
DB_CONTAINER_NAME="${DB_CONTAINER_NAME:-api-test-db}"
REDIS_CONTAINER_NAME="${REDIS_CONTAINER_NAME:-api-test-redis}"
PHP_IMAGE="${PHP_IMAGE:-api-php-test-runner}"
CLONE_DATABASE="${CLONE_DATABASE:-}"
CLONE_ROOT_PASSWORD="${CLONE_ROOT_PASSWORD:-test_root_password}"
SKIP_BUILD="${SKIP_BUILD:-0}"
FORCE_BUILD="${FORCE_BUILD:-0}"
SKIP_TESTS="${SKIP_TESTS:-0}"
KEEP_CONTAINERS="${KEEP_CONTAINERS:-0}"
FORCE="${FORCE:-0}"
if [[ -f ".env" ]]; then
# shellcheck disable=SC2046
export $(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' .env | sed 's/\r$//')
SOURCE_TARGET="${SOURCE_TARGET:-${CONFIG_DB_TARGET:-}}"
fi
SOURCE_TARGET="${SOURCE_TARGET:-live}"
SOURCE_TARGET="$(echo "${SOURCE_TARGET}" | tr '[:upper:]' '[:lower:]')"
if [[ "${SOURCE_TARGET}" != "live" && "${SOURCE_TARGET}" != "debug" ]]; then
SOURCE_TARGET="live"
fi
if [[ "${SOURCE_TARGET}" == "debug" ]]; then
SOURCE_HOST="${SOURCE_HOST:-${CONFIG_DB_DEBUG_HOST:-${CONFIG_DB_HOST:-}}}"
SOURCE_DATABASE="${SOURCE_DATABASE:-${CONFIG_DB_DEBUG_DATABASE:-${CONFIG_DB_DATABASE:-}}}"
SOURCE_USER="${SOURCE_USER:-${CONFIG_DB_DEBUG_USER:-${CONFIG_DB_USER:-}}}"
SOURCE_PASSWORD="${SOURCE_PASSWORD:-${CONFIG_DB_DEBUG_PASSWORD:-${CONFIG_DB_PASSWORD:-}}}"
else
SOURCE_HOST="${SOURCE_HOST:-${CONFIG_DB_HOST:-}}"
SOURCE_DATABASE="${SOURCE_DATABASE:-${CONFIG_DB_DATABASE:-}}"
SOURCE_USER="${SOURCE_USER:-${CONFIG_DB_USER:-}}"
SOURCE_PASSWORD="${SOURCE_PASSWORD:-${CONFIG_DB_PASSWORD:-}}"
fi
if [[ -z "${SOURCE_HOST}" || -z "${SOURCE_DATABASE}" || -z "${SOURCE_USER}" || -z "${SOURCE_PASSWORD}" ]]; then
echo "Missing source DB credentials for target '${SOURCE_TARGET}'. Set CONFIG_DB_TARGET and CONFIG_DB_* / CONFIG_DB_DEBUG_* in .env or env vars."
exit 1
fi
if [[ -z "${CLONE_DATABASE}" ]]; then
CLONE_DATABASE="${SOURCE_DATABASE}_test_clone"
fi
if ! command -v docker >/dev/null 2>&1; then
echo "Docker is required but was not found in PATH."
exit 1
fi
if [[ "${FORCE}" != "1" ]]; then
echo "This will clone data from the '${SOURCE_TARGET}' database target into a local Docker MySQL container."
read -r -p "Type CLONE to continue: " confirmation
if [[ "${confirmation}" != "CLONE" ]]; then
echo "Cancelled."
exit 1
fi
fi
remove_container_if_exists() {
local name="$1"
if docker ps -aq --filter "name=^${name}$" | grep -q .; then
docker rm -f "${name}" >/dev/null
fi
}
if [[ "${SKIP_BUILD}" != "1" ]]; then
if docker image inspect "${PHP_IMAGE}" >/dev/null 2>&1 && [[ "${FORCE_BUILD}" != "1" ]]; then
echo "Using cached PHP test image (${PHP_IMAGE}). Set FORCE_BUILD=1 to rebuild."
else
echo "Building PHP test image (${PHP_IMAGE})..."
docker build -f services/php/Dockerfile -t "${PHP_IMAGE}" .
fi
fi
if ! docker network ls --format '{{.Name}}' | grep -qx "${NETWORK_NAME}"; then
echo "Creating docker network ${NETWORK_NAME}..."
docker network create "${NETWORK_NAME}" >/dev/null
fi
echo "Resetting test containers..."
remove_container_if_exists "${DB_CONTAINER_NAME}"
remove_container_if_exists "${REDIS_CONTAINER_NAME}"
echo "Starting cloned MySQL container (${DB_CONTAINER_NAME})..."
docker run -d \
--name "${DB_CONTAINER_NAME}" \
--network "${NETWORK_NAME}" \
-e "MYSQL_ROOT_PASSWORD=${CLONE_ROOT_PASSWORD}" \
-e "MYSQL_DATABASE=${CLONE_DATABASE}" \
mysql:8.4 >/dev/null
for i in $(seq 1 60); do
if docker exec -e "MYSQL_PWD=${CLONE_ROOT_PASSWORD}" "${DB_CONTAINER_NAME}" sh -lc "mysqladmin -u root ping --silent" >/dev/null 2>&1; then
break
fi
if [[ "$i" -eq 60 ]]; then
echo "Timed out waiting for ${DB_CONTAINER_NAME} to be ready."
exit 1
fi
sleep 2
done
echo "Cloning '${SOURCE_TARGET}' database ${SOURCE_DATABASE} from ${SOURCE_HOST} into ${DB_CONTAINER_NAME}/${CLONE_DATABASE}..."
DUMP_DIR="${ROOT_DIR}/.tmp-db-clone"
DUMP_FILE="${DUMP_DIR}/dump.sql"
mkdir -p "${DUMP_DIR}"
rm -f "${DUMP_FILE}"
docker run --rm \
-e "MYSQL_PWD=${SOURCE_PASSWORD}" \
-v "${DUMP_DIR}:/dump" \
mysql:8.4 \
sh -lc "exec mysqldump --compress --single-transaction --quick --set-gtid-purged=OFF -h '${SOURCE_HOST}' -P '${SOURCE_PORT}' -u '${SOURCE_USER}' '${SOURCE_DATABASE}' > /dump/dump.sql"
if [[ ! -f "${DUMP_FILE}" ]]; then
echo "Database dump failed."
exit 1
fi
docker run --rm \
--network "${NETWORK_NAME}" \
-e "MYSQL_PWD=${CLONE_ROOT_PASSWORD}" \
-v "${DUMP_DIR}:/dump" \
mysql:8.4 \
sh -lc "exec mysql -h '${DB_CONTAINER_NAME}' -u root '${CLONE_DATABASE}' < /dump/dump.sql"
rm -f "${DUMP_FILE}"
echo "Starting isolated Redis container (${REDIS_CONTAINER_NAME})..."
docker run -d --name "${REDIS_CONTAINER_NAME}" --network "${NETWORK_NAME}" redis:7 >/dev/null
if [[ "${SKIP_TESTS}" != "1" ]]; then
echo "Running tests in a separate PHP container against cloned DB..."
docker run --rm \
--network "${NETWORK_NAME}" \
--env-file "${ROOT_DIR}/.env" \
-e "CONFIG_DB_HOST=${DB_CONTAINER_NAME}" \
-e "CONFIG_DB_DATABASE=${CLONE_DATABASE}" \
-e "CONFIG_DB_USER=root" \
-e "CONFIG_DB_PASSWORD=${CLONE_ROOT_PASSWORD}" \
-e "REDIS_CONFIG_HOST=${REDIS_CONTAINER_NAME}" \
-e "AUTO_COMPOSER_INSTALL=false" \
-v "${ROOT_DIR}/services/nginx/app:/var/www/html" \
-v "${ROOT_DIR}/services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro" \
"${PHP_IMAGE}" \
sh -lc "cd /var/www/html && if [ ! -f vendor/autoload.php ]; then composer install --no-interaction --prefer-dist; fi && ${TEST_COMMAND}"
fi
if [[ "${KEEP_CONTAINERS}" != "1" ]]; then
echo "Cleaning up test containers..."
remove_container_if_exists "${REDIS_CONTAINER_NAME}"
remove_container_if_exists "${DB_CONTAINER_NAME}"
else
echo "Keeping containers for inspection:"
echo " DB: ${DB_CONTAINER_NAME}"
echo " Redis: ${REDIS_CONTAINER_NAME}"
fi
echo "Done."