Supabase Production Migration #2034
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Supabase Production Migration | |
| on: | |
| workflow_run: | |
| workflows: | |
| - "Vercel Platform Production Deployment" | |
| - "Supabase Staging Migration" | |
| types: | |
| - completed | |
| branches: | |
| - production | |
| - main | |
| workflow_dispatch: | |
| jobs: | |
| check-ci: | |
| uses: ./.github/workflows/ci-check.yml | |
| with: | |
| workflow_name: supabase-production.yaml | |
| permissions: | |
| contents: read | |
| deployments: read | |
| evaluate-prerequisites: | |
| name: Evaluate production prerequisites | |
| runs-on: ubuntu-latest | |
| needs: [check-ci] | |
| if: needs.check-ci.outputs.should_run == 'true' | |
| permissions: | |
| actions: read | |
| contents: read | |
| outputs: | |
| should_deploy: ${{ steps.evaluate.outputs.should_deploy }} | |
| target_sha: ${{ steps.evaluate.outputs.target_sha }} | |
| steps: | |
| - name: Evaluate prerequisite status | |
| id: evaluate | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| CURRENT_SHA: ${{ github.sha }} | |
| CURRENT_REF_NAME: ${{ github.ref_name }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| TRIGGER_BRANCH: ${{ github.event.workflow_run.head_branch }} | |
| TRIGGER_CONCLUSION: ${{ github.event.workflow_run.conclusion }} | |
| TRIGGER_SHA: ${{ github.event.workflow_run.head_sha }} | |
| TRIGGER_WORKFLOW: ${{ github.event.workflow_run.name }} | |
| run: | | |
| set -euo pipefail | |
| SHOULD_DEPLOY=false | |
| TARGET_SHA="" | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| if [ "$CURRENT_REF_NAME" != "production" ]; then | |
| echo "::notice::Skipping production migration because manual dispatches must run from the production branch. Current ref is '$CURRENT_REF_NAME'." | |
| else | |
| TARGET_SHA="$CURRENT_SHA" | |
| echo "Manual dispatch detected on production. Checking production migration prerequisites for $TARGET_SHA." | |
| fi | |
| elif [ "$TRIGGER_WORKFLOW" = "Vercel Platform Production Deployment" ]; then | |
| if [ "$TRIGGER_BRANCH" != "production" ]; then | |
| echo "::notice::Skipping production migration because the production platform deployment trigger ran on '$TRIGGER_BRANCH' instead of production." | |
| elif [ "$TRIGGER_CONCLUSION" != "success" ]; then | |
| echo "::notice::Skipping production migration because the production platform deployment trigger concluded with '$TRIGGER_CONCLUSION'." | |
| else | |
| TARGET_SHA="$TRIGGER_SHA" | |
| echo "Production platform deployment completed. Checking prerequisites for production migration at $TARGET_SHA..." | |
| fi | |
| elif [ "$TRIGGER_WORKFLOW" = "Supabase Staging Migration" ]; then | |
| if [ "$TRIGGER_BRANCH" != "main" ]; then | |
| echo "::notice::Skipping production migration re-evaluation because the staging migration trigger ran on '$TRIGGER_BRANCH' instead of main." | |
| elif [ "$TRIGGER_CONCLUSION" != "success" ]; then | |
| echo "::notice::Skipping production migration re-evaluation because the staging migration trigger concluded with '$TRIGGER_CONCLUSION'." | |
| else | |
| TARGET_SHA="$TRIGGER_SHA" | |
| echo "Staging migration completed. Re-checking production migration prerequisites for $TARGET_SHA..." | |
| fi | |
| else | |
| echo "::notice::Skipping production migration because the triggering workflow was '$TRIGGER_WORKFLOW'." | |
| fi | |
| if [ -n "$TARGET_SHA" ]; then | |
| echo "Checking production platform deployment workflow status..." | |
| VERCEL_RUN=$(gh api \ | |
| "repos/${{ github.repository }}/actions/workflows/vercel-production-platform.yaml/runs?branch=production&head_sha=$TARGET_SHA&per_page=1" \ | |
| --jq '.workflow_runs[0] // {} | {conclusion: .conclusion, status: .status, created_at: .created_at, head_branch: .head_branch, head_sha: .head_sha}') | |
| VERCEL_CONCLUSION=$(echo "$VERCEL_RUN" | jq -r '.conclusion // "missing"') | |
| VERCEL_STATUS=$(echo "$VERCEL_RUN" | jq -r '.status // "missing"') | |
| VERCEL_BRANCH=$(echo "$VERCEL_RUN" | jq -r '.head_branch // "missing"') | |
| VERCEL_SHA=$(echo "$VERCEL_RUN" | jq -r '.head_sha // "missing"') | |
| echo "Production platform deployment status: $VERCEL_STATUS" | |
| echo "Production platform deployment conclusion: $VERCEL_CONCLUSION" | |
| echo "Production platform deployment branch: $VERCEL_BRANCH" | |
| echo "Production platform deployment SHA: $VERCEL_SHA" | |
| if [ "$VERCEL_SHA" != "$TARGET_SHA" ]; then | |
| echo "::notice::Skipping production migration because no production platform deployment workflow run was found for $TARGET_SHA." | |
| elif [ "$VERCEL_BRANCH" != "production" ]; then | |
| echo "::notice::Skipping production migration because the matching production platform deployment ran on '$VERCEL_BRANCH'." | |
| elif [ "$VERCEL_CONCLUSION" != "success" ]; then | |
| echo "::notice::Skipping production migration because the production platform deployment for $TARGET_SHA has conclusion '$VERCEL_CONCLUSION'." | |
| else | |
| echo "Checking successful production platform deployment marker..." | |
| DEPLOYMENT_MARKER_DEPLOYMENT=$(gh api \ | |
| "repos/${{ github.repository }}/deployments?environment=vercel-production-platform&per_page=100" \ | |
| | jq --arg sha "$TARGET_SHA" ' | |
| def payload_obj: | |
| (.payload // {}) as $payload | |
| | if ($payload | type) == "object" then $payload | |
| elif ($payload | type) == "string" then ($payload | fromjson? // {}) | |
| else {} | |
| end; | |
| [ | |
| .[] | |
| | . as $deployment | |
| | (payload_obj) as $payload | |
| | select( | |
| ($deployment.sha == $sha or $payload.sha == $sha) | |
| and (($payload.markerKind // "deployment") == "deployment") | |
| and (($payload.workflowName // "vercel-production-platform.yaml") == "vercel-production-platform.yaml") | |
| ) | |
| ] | |
| | sort_by(.created_at // "") | |
| | reverse | |
| | .[0] // {} | |
| | {id: .id, sha: .sha}') | |
| DEPLOYMENT_MARKER_DEPLOYMENT_ID=$(echo "$DEPLOYMENT_MARKER_DEPLOYMENT" | jq -r '.id // ""') | |
| DEPLOYMENT_MARKER_SHA=$(echo "$DEPLOYMENT_MARKER_DEPLOYMENT" | jq -r '.sha // "missing"') | |
| echo "Production platform deployment marker deployment ID: ${DEPLOYMENT_MARKER_DEPLOYMENT_ID:-missing}" | |
| echo "Production platform deployment marker SHA: $DEPLOYMENT_MARKER_SHA" | |
| if [ -z "$DEPLOYMENT_MARKER_DEPLOYMENT_ID" ]; then | |
| echo "::notice::Skipping production migration because no production platform deployment marker was found for $TARGET_SHA." | |
| else | |
| DEPLOYMENT_MARKER_STATUSES=$(gh api \ | |
| "repos/${{ github.repository }}/deployments/$DEPLOYMENT_MARKER_DEPLOYMENT_ID/statuses") | |
| DEPLOYMENT_MARKER_STATE=$(echo "$DEPLOYMENT_MARKER_STATUSES" | jq -r '.[0].state // "missing"') | |
| DEPLOYMENT_MARKER_HAS_SUCCESS=$(echo "$DEPLOYMENT_MARKER_STATUSES" | jq -r '[.[]? | select(.state == "success")] | length > 0') | |
| echo "Production platform deployment marker latest state: $DEPLOYMENT_MARKER_STATE" | |
| echo "Production platform deployment marker has success status: $DEPLOYMENT_MARKER_HAS_SUCCESS" | |
| if [ "$DEPLOYMENT_MARKER_HAS_SUCCESS" != "true" ]; then | |
| echo "::notice::Skipping production migration because the production platform deployment marker for $TARGET_SHA does not include a success status. Latest state is '$DEPLOYMENT_MARKER_STATE'." | |
| else | |
| echo "Checking staging migration status..." | |
| STAGING_RUN=$(gh api \ | |
| "repos/${{ github.repository }}/actions/workflows/supabase-staging.yaml/runs?branch=main&head_sha=$TARGET_SHA&per_page=1" \ | |
| --jq '.workflow_runs[0] // {} | {conclusion: .conclusion, status: .status, created_at: .created_at, head_branch: .head_branch, head_sha: .head_sha}') | |
| STAGING_CONCLUSION=$(echo "$STAGING_RUN" | jq -r '.conclusion // "missing"') | |
| STAGING_STATUS=$(echo "$STAGING_RUN" | jq -r '.status // "missing"') | |
| STAGING_BRANCH=$(echo "$STAGING_RUN" | jq -r '.head_branch // "missing"') | |
| STAGING_SHA=$(echo "$STAGING_RUN" | jq -r '.head_sha // "missing"') | |
| echo "Staging migration status: $STAGING_STATUS" | |
| echo "Staging migration conclusion: $STAGING_CONCLUSION" | |
| echo "Staging migration branch: $STAGING_BRANCH" | |
| echo "Staging migration SHA: $STAGING_SHA" | |
| if [ "$STAGING_SHA" != "$TARGET_SHA" ]; then | |
| echo "::notice::Skipping production migration because no main staging migration was found for $TARGET_SHA." | |
| elif [ "$STAGING_BRANCH" != "main" ]; then | |
| echo "::notice::Skipping production migration because the matching staging migration ran on '$STAGING_BRANCH'." | |
| elif [ "$STAGING_STATUS" != "completed" ]; then | |
| echo "::notice::Skipping production migration because the staging migration for $TARGET_SHA is '$STAGING_STATUS', not completed." | |
| elif [ "$STAGING_CONCLUSION" != "success" ]; then | |
| echo "::notice::Skipping production migration because the staging migration for $TARGET_SHA has conclusion '$STAGING_CONCLUSION'." | |
| else | |
| echo "Production platform deployment marker and staging migration are bound to $TARGET_SHA. Proceeding with migration." | |
| SHOULD_DEPLOY=true | |
| fi | |
| fi | |
| fi | |
| fi | |
| fi | |
| echo "should_deploy=$SHOULD_DEPLOY" >> "$GITHUB_OUTPUT" | |
| echo "target_sha=$TARGET_SHA" >> "$GITHUB_OUTPUT" | |
| deploy: | |
| name: Migrate production database | |
| timeout-minutes: 15 | |
| runs-on: ubuntu-latest | |
| needs: [check-ci, evaluate-prerequisites] | |
| if: needs.check-ci.outputs.should_run == 'true' && needs.evaluate-prerequisites.outputs.should_deploy == 'true' | |
| permissions: | |
| contents: read | |
| env: | |
| SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} | |
| SUPABASE_DB_PASSWORD: ${{ secrets.PRODUCTION_DB_PASSWORD }} | |
| PRODUCTION_PROJECT_ID: ${{ secrets.PRODUCTION_PROJECT_ID }} | |
| PRODUCTION_DB_URL: ${{ secrets.PRODUCTION_DB_URL }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.evaluate-prerequisites.outputs.target_sha }} | |
| - name: Setup Supabase CLI | |
| uses: ./.github/actions/setup-supabase-cli-with-retry | |
| with: | |
| github-token: ${{ github.token }} | |
| - name: Deploy migrations to production | |
| run: | | |
| cd apps/database | |
| supabase link --project-ref ${{ env.PRODUCTION_PROJECT_ID }} | |
| supabase db push --include-all |