Conversation
There was a problem hiding this comment.
Pull request overview
Adds post-deploy logic to automatically seed the database when “critical” tables are empty, aiming to prevent a blank app state after deploy/reset.
Changes:
- Query row counts for
products,product_variants, andinventory_stockduring the CD deploy job. - If counts indicate emptiness, run
deploy/scripts/reset-db-and-seed.shand then bring services back up.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| sudo docker compose -f docker-compose.prod.yml exec -T mysql \ | ||
| mysql -N -s -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" -e "SELECT COUNT(*) FROM $table;" 2>/dev/null | tr -d '\r' | ||
| } | ||
| PRODUCTS_COUNT="$(get_table_count products || echo 0)" | ||
| VARIANTS_COUNT="$(get_table_count product_variants || echo 0)" | ||
| STOCK_COUNT="$(get_table_count inventory_stock || echo 0)" |
There was a problem hiding this comment.
get_table_count suppresses errors and the pipeline’s exit status will be from tr, so failures like MySQL not ready / auth errors / table missing will produce an empty string that later gets treated as 0. That can incorrectly trigger the reset+seed path. Suggest: wait for the mysql service to be healthy before counting, and make get_table_count fail loudly (e.g., add set -o pipefail, capture/validate output is a number, and retry/abort on error rather than treating it as zero).
| sudo docker compose -f docker-compose.prod.yml exec -T mysql \ | |
| mysql -N -s -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" -e "SELECT COUNT(*) FROM $table;" 2>/dev/null | tr -d '\r' | |
| } | |
| PRODUCTS_COUNT="$(get_table_count products || echo 0)" | |
| VARIANTS_COUNT="$(get_table_count product_variants || echo 0)" | |
| STOCK_COUNT="$(get_table_count inventory_stock || echo 0)" | |
| # Wait for MySQL to be ready and fail loudly on errors. | |
| local attempt=0 | |
| local max_attempts=5 | |
| local sleep_seconds=3 | |
| local count | |
| # Ensure pipeline failures propagate (mysql errors aren't hidden by `tr`). | |
| set -o pipefail | |
| while [ "$attempt" -lt "$max_attempts" ]; do | |
| if count="$( | |
| sudo docker compose -f docker-compose.prod.yml exec -T mysql \ | |
| mysql -N -s -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" \ | |
| -e "SELECT COUNT(*) FROM $table;" | tr -d '\r' | |
| )"; then | |
| if printf '%s\n' "$count" | grep -Eq '^[0-9]+$'; then | |
| echo "$count" | |
| return 0 | |
| else | |
| echo "Non-numeric row count '$count' returned for table '$table'" >&2 | |
| return 1 | |
| fi | |
| fi | |
| attempt=$((attempt + 1)) | |
| echo "Unable to query table '$table' (attempt $attempt/$max_attempts). Waiting for MySQL to become ready..." >&2 | |
| sleep "$sleep_seconds" | |
| done | |
| echo "Failed to query table '$table' after $max_attempts attempts; aborting." >&2 | |
| return 1 | |
| } | |
| if ! PRODUCTS_COUNT="$(get_table_count products)"; then | |
| echo "Error retrieving row count for 'products' table; aborting deployment." >&2 | |
| exit 1 | |
| fi | |
| if ! VARIANTS_COUNT="$(get_table_count product_variants)"; then | |
| echo "Error retrieving row count for 'product_variants' table; aborting deployment." >&2 | |
| exit 1 | |
| fi | |
| if ! STOCK_COUNT="$(get_table_count inventory_stock)"; then | |
| echo "Error retrieving row count for 'inventory_stock' table; aborting deployment." >&2 | |
| exit 1 | |
| fi |
| if [ "${PRODUCTS_COUNT:-0}" = "0" ] || [ "${VARIANTS_COUNT:-0}" = "0" ]; then | ||
| echo "Detected empty critical tables. Running reset-db-and-seed.sh..." | ||
| if [ -f "./deploy/scripts/reset-db-and-seed.sh" ]; then | ||
| sudo chmod +x ./deploy/scripts/reset-db-and-seed.sh | ||
| sudo DEPLOY_PATH="$DEPLOY_PATH" COMPOSE_FILE="$DEPLOY_PATH/docker-compose.prod.yml" ENV_FILE="$ENV_FILE" bash ./deploy/scripts/reset-db-and-seed.sh | ||
| # Ensure services are up after seed | ||
| sudo env REGISTRY="$REGISTRY" IMAGE_NAMESPACE="$IMAGE_NAMESPACE" IMAGE_TAG="$TARGET_TAG" DB_USERNAME="$DB_USERNAME" DB_PASSWORD="$DB_PASSWORD" MYSQL_DATABASE="$MYSQL_DATABASE" MYSQL_ROOT_PASSWORD="$MYSQL_ROOT_PASSWORD" docker compose -f docker-compose.prod.yml up -d --remove-orphans |
There was a problem hiding this comment.
The condition products == 0 || variants == 0 will run reset-db-and-seed.sh, which drops and recreates the database. This is risky because a legitimately empty table (or a transient/partial seed) would wipe an otherwise valid production DB. Consider tightening the guard to only bootstrap when the database is clearly fresh (e.g., all critical tables are 0 and/or schema just created), and/or gate behind an explicit flag/secret (e.g., AUTO_BOOTSTRAP_SEED=true) so normal deploys can’t trigger a destructive reset.
| sudo docker compose -f docker-compose.prod.yml exec -T mysql \ | ||
| mysql -N -s -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" -e "SELECT COUNT(*) FROM $table;" 2>/dev/null | tr -d '\r' |
There was a problem hiding this comment.
The MySQL root password is passed as a CLI argument (-p"$MYSQL_ROOT_PASSWORD"), which can be visible to other processes on the VM via process listings. Prefer using MYSQL_PWD (passed via docker compose exec -e MYSQL_PWD=...) or a defaults file inside the container so the password isn’t part of the command line.
| sudo docker compose -f docker-compose.prod.yml exec -T mysql \ | |
| mysql -N -s -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" -e "SELECT COUNT(*) FROM $table;" 2>/dev/null | tr -d '\r' | |
| sudo docker compose -f docker-compose.prod.yml exec -T -e MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mysql \ | |
| mysql -N -s -uroot "$MYSQL_DATABASE" -e "SELECT COUNT(*) FROM $table;" 2>/dev/null | tr -d '\r' |
… post-deploy