Merge pull request #332 from bounswe/feat/web/playwright-acceptance-t… #67
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: Deploy Production (Azure ACA) | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - "backend/**" | |
| - "frontend/**" | |
| - ".github/workflows/deploy.yml" | |
| workflow_dispatch: | |
| inputs: | |
| target: | |
| description: "Which app to deploy" | |
| required: true | |
| default: "both" | |
| type: choice | |
| options: [both, backend, frontend] | |
| env: | |
| RESOURCE_GROUP: sem-backend-rg | |
| ACA_ENV: sem-backend-env | |
| ACR_NAME: semgroup9acr | |
| ACR_SERVER: semgroup9acr.azurecr.io | |
| BACKEND_APP: sem-backend | |
| FRONTEND_APP: sem-frontend | |
| # Frontend calls backend via the stable custom domain | |
| FRONTEND_API_BASE_URL: https://api.thesocialeventmapper.social | |
| jobs: | |
| # ── Detect which parts changed ────────────────────────────────────────────── | |
| detect-changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| backend: ${{ steps.decide.outputs.backend }} | |
| frontend: ${{ steps.decide.outputs.frontend }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Detect paths changed | |
| id: filter | |
| uses: dorny/paths-filter@v3 | |
| with: | |
| filters: | | |
| backend: | |
| - 'backend/**' | |
| frontend: | |
| - 'frontend/**' | |
| workflow: | |
| - '.github/workflows/deploy.yml' | |
| - name: Decide which apps to deploy | |
| id: decide | |
| run: | | |
| # Manual dispatch honors the "target" input | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| case "${{ github.event.inputs.target }}" in | |
| both) echo "backend=true" >> "$GITHUB_OUTPUT"; echo "frontend=true" >> "$GITHUB_OUTPUT" ;; | |
| backend) echo "backend=true" >> "$GITHUB_OUTPUT"; echo "frontend=false" >> "$GITHUB_OUTPUT" ;; | |
| frontend) echo "backend=false" >> "$GITHUB_OUTPUT"; echo "frontend=true" >> "$GITHUB_OUTPUT" ;; | |
| esac | |
| exit 0 | |
| fi | |
| # If the workflow itself changed, re-deploy both to validate new pipeline | |
| if [ "${{ steps.filter.outputs.workflow }}" = "true" ]; then | |
| echo "backend=true" >> "$GITHUB_OUTPUT" | |
| echo "frontend=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "backend=${{ steps.filter.outputs.backend }}" >> "$GITHUB_OUTPUT" | |
| echo "frontend=${{ steps.filter.outputs.frontend }}" >> "$GITHUB_OUTPUT" | |
| # ── Backend: build + push + deploy ────────────────────────────────────────── | |
| deploy-backend: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.backend == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Azure Login | |
| uses: azure/login@v2 | |
| with: | |
| creds: ${{ secrets.AZURE_CREDENTIALS }} | |
| - name: Install containerapp extension | |
| run: az extension add --name containerapp --upgrade --only-show-errors | |
| - uses: docker/setup-buildx-action@v3 | |
| - name: ACR Docker login | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.ACR_SERVER }} | |
| username: ${{ secrets.ACR_USERNAME }} | |
| password: ${{ secrets.ACR_PASSWORD }} | |
| - name: Build & push backend image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./backend | |
| file: ./backend/Dockerfile | |
| push: true | |
| tags: | | |
| ${{ env.ACR_SERVER }}/sem-backend:${{ github.sha }} | |
| ${{ env.ACR_SERVER }}/sem-backend:latest | |
| - name: Deploy backend to ACA | |
| id: deploy | |
| run: | | |
| set -e | |
| IMAGE="${{ env.ACR_SERVER }}/sem-backend:${{ github.sha }}" | |
| if az containerapp show -n ${{ env.BACKEND_APP }} -g ${{ env.RESOURCE_GROUP }} >/dev/null 2>&1; then | |
| echo "Backend app exists — syncing secrets from GitHub Secrets..." | |
| az containerapp secret set \ | |
| -n ${{ env.BACKEND_APP }} \ | |
| -g ${{ env.RESOURCE_GROUP }} \ | |
| --secrets \ | |
| supabase-url="${{ secrets.SUPABASE_URL }}" \ | |
| supabase-key="${{ secrets.SUPABASE_KEY }}" \ | |
| jwt-secret="${{ secrets.JWT_SECRET }}" \ | |
| google-client-id="${{ secrets.GOOGLE_CLIENT_ID }}" \ | |
| google-client-secret="${{ secrets.GOOGLE_CLIENT_SECRET }}" \ | |
| google-redirect-uri="${{ secrets.GOOGLE_REDIRECT_URI }}" \ | |
| smtp-host="${{ secrets.SMTP_HOST }}" \ | |
| smtp-port="${{ secrets.SMTP_PORT }}" \ | |
| smtp-user="${{ secrets.SMTP_USER }}" \ | |
| smtp-password="${{ secrets.SMTP_PASSWORD }}" \ | |
| smtp-from-email="${{ secrets.SMTP_FROM_EMAIL }}" \ | |
| smtp-from-name="${{ secrets.SMTP_FROM_NAME }}" \ | |
| --output none | |
| echo "Updating image..." | |
| az containerapp update \ | |
| -n ${{ env.BACKEND_APP }} \ | |
| -g ${{ env.RESOURCE_GROUP }} \ | |
| --image "$IMAGE" \ | |
| --output none | |
| else | |
| echo "Creating backend container app..." | |
| az containerapp create \ | |
| -n ${{ env.BACKEND_APP }} \ | |
| -g ${{ env.RESOURCE_GROUP }} \ | |
| --environment ${{ env.ACA_ENV }} \ | |
| --image "$IMAGE" \ | |
| --registry-server ${{ env.ACR_SERVER }} \ | |
| --registry-username "${{ secrets.ACR_USERNAME }}" \ | |
| --registry-password "${{ secrets.ACR_PASSWORD }}" \ | |
| --target-port 8000 \ | |
| --ingress external \ | |
| --min-replicas 0 \ | |
| --max-replicas 3 \ | |
| --cpu 0.5 \ | |
| --memory 1.0Gi \ | |
| --secrets \ | |
| supabase-url="${{ secrets.SUPABASE_URL }}" \ | |
| supabase-key="${{ secrets.SUPABASE_KEY }}" \ | |
| jwt-secret="${{ secrets.JWT_SECRET }}" \ | |
| google-client-id="${{ secrets.GOOGLE_CLIENT_ID }}" \ | |
| google-client-secret="${{ secrets.GOOGLE_CLIENT_SECRET }}" \ | |
| google-redirect-uri="${{ secrets.GOOGLE_REDIRECT_URI }}" \ | |
| smtp-host="${{ secrets.SMTP_HOST }}" \ | |
| smtp-port="${{ secrets.SMTP_PORT }}" \ | |
| smtp-user="${{ secrets.SMTP_USER }}" \ | |
| smtp-password="${{ secrets.SMTP_PASSWORD }}" \ | |
| smtp-from-email="${{ secrets.SMTP_FROM_EMAIL }}" \ | |
| smtp-from-name="${{ secrets.SMTP_FROM_NAME }}" \ | |
| --env-vars \ | |
| "SUPABASE_URL=secretref:supabase-url" \ | |
| "SUPABASE_KEY=secretref:supabase-key" \ | |
| "JWT_SECRET=secretref:jwt-secret" \ | |
| "ENVIRONMENT=production" \ | |
| "FRONTEND_URL=${{ secrets.FRONTEND_URL }}" \ | |
| "BACKEND_URL=${{ secrets.BACKEND_URL }}" \ | |
| "CORS_ORIGINS=${{ secrets.CORS_ORIGINS }}" \ | |
| "COOKIE_DOMAIN=.thesocialeventmapper.social" \ | |
| "GOOGLE_CLIENT_ID=secretref:google-client-id" \ | |
| "GOOGLE_CLIENT_SECRET=secretref:google-client-secret" \ | |
| "GOOGLE_REDIRECT_URI=secretref:google-redirect-uri" \ | |
| "SMTP_HOST=secretref:smtp-host" \ | |
| "SMTP_PORT=secretref:smtp-port" \ | |
| "SMTP_USER=secretref:smtp-user" \ | |
| "SMTP_PASSWORD=secretref:smtp-password" \ | |
| "SMTP_FROM_EMAIL=secretref:smtp-from-email" \ | |
| "SMTP_FROM_NAME=secretref:smtp-from-name" \ | |
| --output none | |
| fi | |
| BACKEND_FQDN=$(az containerapp show -n ${{ env.BACKEND_APP }} -g ${{ env.RESOURCE_GROUP }} --query properties.configuration.ingress.fqdn -o tsv) | |
| echo "backend_fqdn=$BACKEND_FQDN" >> $GITHUB_OUTPUT | |
| - name: Backend health check | |
| run: | | |
| # Prefer custom domain when bound, fall back to ACA FQDN | |
| for URL in "https://api.thesocialeventmapper.social/health" "https://${{ steps.deploy.outputs.backend_fqdn }}/health"; do | |
| for i in $(seq 1 20); do | |
| if curl --fail --silent "$URL" >/dev/null; then | |
| echo "Backend healthy at $URL" | |
| exit 0 | |
| fi | |
| sleep 10 | |
| done | |
| done | |
| echo "Backend health check failed" | |
| exit 1 | |
| # ── Frontend: build + push + deploy ───────────────────────────────────────── | |
| deploy-frontend: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.frontend == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Azure Login | |
| uses: azure/login@v2 | |
| with: | |
| creds: ${{ secrets.AZURE_CREDENTIALS }} | |
| - name: Install containerapp extension | |
| run: az extension add --name containerapp --upgrade --only-show-errors | |
| - uses: docker/setup-buildx-action@v3 | |
| - name: ACR Docker login | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.ACR_SERVER }} | |
| username: ${{ secrets.ACR_USERNAME }} | |
| password: ${{ secrets.ACR_PASSWORD }} | |
| - name: Build & push frontend image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./frontend | |
| file: ./frontend/Dockerfile | |
| push: true | |
| build-args: | | |
| NEXT_PUBLIC_API_BASE_URL=${{ env.FRONTEND_API_BASE_URL }} | |
| tags: | | |
| ${{ env.ACR_SERVER }}/sem-frontend:${{ github.sha }} | |
| ${{ env.ACR_SERVER }}/sem-frontend:latest | |
| - name: Deploy frontend to ACA | |
| id: deploy | |
| run: | | |
| set -e | |
| IMAGE="${{ env.ACR_SERVER }}/sem-frontend:${{ github.sha }}" | |
| if az containerapp show -n ${{ env.FRONTEND_APP }} -g ${{ env.RESOURCE_GROUP }} >/dev/null 2>&1; then | |
| echo "Frontend app exists — updating image..." | |
| az containerapp update \ | |
| -n ${{ env.FRONTEND_APP }} \ | |
| -g ${{ env.RESOURCE_GROUP }} \ | |
| --image "$IMAGE" \ | |
| --output none | |
| else | |
| echo "Creating frontend container app..." | |
| az containerapp create \ | |
| -n ${{ env.FRONTEND_APP }} \ | |
| -g ${{ env.RESOURCE_GROUP }} \ | |
| --environment ${{ env.ACA_ENV }} \ | |
| --image "$IMAGE" \ | |
| --registry-server ${{ env.ACR_SERVER }} \ | |
| --registry-username "${{ secrets.ACR_USERNAME }}" \ | |
| --registry-password "${{ secrets.ACR_PASSWORD }}" \ | |
| --target-port 3000 \ | |
| --ingress external \ | |
| --min-replicas 0 \ | |
| --max-replicas 3 \ | |
| --cpu 0.5 \ | |
| --memory 1.0Gi \ | |
| --output none | |
| fi | |
| FRONTEND_FQDN=$(az containerapp show -n ${{ env.FRONTEND_APP }} -g ${{ env.RESOURCE_GROUP }} --query properties.configuration.ingress.fqdn -o tsv) | |
| echo "frontend_fqdn=$FRONTEND_FQDN" >> $GITHUB_OUTPUT | |
| - name: Frontend health check | |
| run: | | |
| for URL in "https://thesocialeventmapper.social/login" "https://${{ steps.deploy.outputs.frontend_fqdn }}/login"; do | |
| for i in $(seq 1 20); do | |
| if curl --fail --silent "$URL" >/dev/null; then | |
| echo "Frontend healthy at $URL" | |
| exit 0 | |
| fi | |
| sleep 10 | |
| done | |
| done | |
| echo "Frontend health check failed" | |
| exit 1 |