diff --git a/.github/scripts/check-migrations-and-backup.sh b/.github/scripts/check-migrations-and-backup.sh new file mode 100644 index 0000000000..81f2b49b40 --- /dev/null +++ b/.github/scripts/check-migrations-and-backup.sh @@ -0,0 +1,66 @@ +#!/bin/bash +set -euxo pipefail + +# Arguments +WORKDIR=$1 +SUPABASE_DB_URL=$2 + +if [ -z "$WORKDIR" ]; then + echo "Error: WORKDIR is required." + exit 1 +fi + +pushd "$WORKDIR" + +if [ -z "$SUPABASE_DB_URL" ]; then + echo "Error: SUPABASE_DB_URL is required." + exit 1 +fi + +set +u +if [ -z "$GITHUB_OUTPUT" ]; then + echo "GITHUB_OUTPUT is not set. Using /dev/null for local testing." + GITHUB_OUTPUT="/dev/null" +fi +set -u + +# Allow dry run mode for testing +DRY_RUN=${DRY_RUN:-false} + +echo "Checking for pending migrations..." + +# Capture output from dry-run +# pnpm supabase needs to be run where supabase config/migrations are (apps/frontend) +# usage of 2>&1 to capture stderr as well +OUTPUT=$(pnpm supabase db push --dry-run --db-url "$SUPABASE_DB_URL" 2>&1) || true +echo "$OUTPUT" + +if echo "$OUTPUT" | grep -q "Remote database is up to date"; then + echo "No migrations needed." + echo "needs_migration=false" >> $GITHUB_OUTPUT +elif echo "$OUTPUT" | grep -q "Would push these migrations"; then + echo "Migrations needed. Proceeding with backup." + echo "needs_migration=true" >> $GITHUB_OUTPUT + + # Perform backup + TIMESTAMP=$(date -u +"%Y%m%d%H%M%S") + BACKUP_FILE="${TIMESTAMP}Z_backup.sql.gz" + + if [[ "$DRY_RUN" == "true" ]]; then + echo "Dry run enabled, skipping actual backup creation." + echo "backup_file=$BACKUP_FILE" >> $GITHUB_OUTPUT + else + echo "Running pg_dump..." + pg_dump "$SUPABASE_DB_URL" | gzip > "$BACKUP_FILE" + echo "Creating backup file: $BACKUP_FILE" + fi + + echo "Backup created at $BACKUP_FILE" + echo "backup_file=$BACKUP_FILE" >> $GITHUB_OUTPUT +else + echo "Error: Unexpected output from dry run." + echo "$OUTPUT" + exit 1 +fi + +popd \ No newline at end of file diff --git a/.github/workflows/db-migration.yml b/.github/workflows/db-migration.yml new file mode 100644 index 0000000000..5e491668b2 --- /dev/null +++ b/.github/workflows/db-migration.yml @@ -0,0 +1,87 @@ +name: Supabase DB Migration +permissions: + contents: read + id-token: write + +# We need to match both pull_request and merge_group events to ensure that +# we stop broken migrations from being merged into main. This ensures that +# we can add this workflow as a required check. Without the pull_request event, +# we, oddly, would not be able to add this as a required check on and the +# merge_group. +on: + pull_request: + branches: + - main + merge_group: + branches: + - main + +jobs: + migrate-db: + runs-on: ubuntu-latest + environment: deploy + steps: + - uses: actions/checkout@v3 + if: ${{ github.event_name == 'merge_group' }} + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + credentials_json: '${{ secrets.GOOGLE_CREDENTIALS_JSON }}' + if: ${{ github.event_name == 'merge_group' }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + with: + version: '>= 363.0.0' + if: ${{ github.event_name == 'merge_group' }} + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: | + - recursive: true + args: [--frozen-lockfile, --strict-peer-dependencies] + if: ${{ github.event_name == 'merge_group' }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.x" + cache: "pnpm" + if: ${{ github.event_name == 'merge_group' }} + + - name: Install postgresql-client + run: | + sudo apt-get update + sudo apt-get install -y postgresql-client + if: ${{ github.event_name == 'merge_group' }} + + - name: Check migrations and backup + id: check_migrations + run: | + bash .github/scripts/check-migrations-and-backup.sh "apps/frontend" "$SUPABASE_DB_URL" + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + SUPABASE_DB_URL: ${{ secrets.SUPABASE_PRODUCTION_DB_URL }} + if: ${{ github.event_name == 'merge_group' }} + + - name: Upload backup to GCS + working-directory: apps/frontend + run: | + BACKUP_FILE="${{ steps.check_migrations.outputs.backup_file }}" + echo "Uploading backup ${BACKUP_FILE} to gs://${DB_BACKUP_BUCKET}/${BACKUP_FILE}" + gcloud storage cp "${BACKUP_FILE}" "gs://${DB_BACKUP_BUCKET}/${BACKUP_FILE}" + env: + DB_BACKUP_BUCKET: ${{ secrets.DB_BACKUP_BUCKET }} + if: ${{ github.event_name == 'merge_group' && steps.check_migrations.outputs.needs_migration == 'true' }} + + - name: Push Supabase migrations + working-directory: apps/frontend + run: | + pnpm supabase db push --db-url "$SUPABASE_DB_URL" + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + SUPABASE_DB_URL: ${{ secrets.SUPABASE_PRODUCTION_DB_URL }} + if: ${{ github.event_name == 'merge_group' && steps.check_migrations.outputs.needs_migration == 'true' }}