Skip to content

Supabase Production Migration #2034

Supabase Production Migration

Supabase Production Migration #2034

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