Manual Deploy [boxel] #1274
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: Manual Deploy [boxel] | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| environment: | |
| description: Deployment environment | |
| required: false | |
| default: staging | |
| workflow_call: | |
| inputs: | |
| environment: | |
| required: true | |
| type: string | |
| permissions: | |
| contents: read | |
| deployments: write | |
| id-token: write | |
| jobs: | |
| create-deployment: | |
| name: Create GitHub deployment | |
| if: github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| deployment-id: ${{ steps.create.outputs.deployment_id }} | |
| environment-url: ${{ steps.env.outputs.environment_url }} | |
| steps: | |
| - id: env | |
| run: | | |
| if [ "${{ inputs.environment }}" = "production" ]; then | |
| echo "environment_url=https://app.boxel.ai" >> "$GITHUB_OUTPUT" | |
| elif [ "${{ inputs.environment }}" = "staging" ]; then | |
| echo "environment_url=https://realms-staging.stack.cards" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "environment_url=" >> "$GITHUB_OUTPUT" | |
| fi | |
| - id: create | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const environment = '${{ inputs.environment }}'; | |
| const response = await github.rest.repos.createDeployment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: context.sha, | |
| required_contexts: [], | |
| auto_merge: false, | |
| environment, | |
| description: `Manual deploy to ${environment}`, | |
| transient_environment: false, | |
| production_environment: environment === 'production', | |
| }); | |
| core.setOutput('deployment_id', response.data.id.toString()); | |
| - name: Mark deployment in progress | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const deployment_id = Number('${{ steps.create.outputs.deployment_id }}'); | |
| const environmentUrlValue = '${{ steps.env.outputs.environment_url }}'; | |
| const environment_url = | |
| environmentUrlValue && environmentUrlValue.length > 0 | |
| ? environmentUrlValue | |
| : undefined; | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id, | |
| state: 'in_progress', | |
| environment_url, | |
| log_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, | |
| description: 'Deployment started', | |
| }); | |
| build-ai-bot: | |
| name: Build ai-bot Docker image | |
| uses: cardstack/gh-actions/.github/workflows/docker-ecr.yml@main | |
| secrets: inherit | |
| with: | |
| repository: "boxel-ai-bot-${{ inputs.environment }}" | |
| environment: ${{ inputs.environment }} | |
| dockerfile: "packages/ai-bot/Dockerfile" | |
| deploy-ai-bot: | |
| needs: [build-ai-bot, post-migrate-db] | |
| name: Deploy ai-bot to AWS ECS | |
| uses: cardstack/gh-actions/.github/workflows/ecs-deploy.yml@main | |
| secrets: inherit | |
| with: | |
| container-name: "boxel-ai-bot" | |
| environment: ${{ inputs.environment }} | |
| cluster: ${{ inputs.environment }} | |
| service-name: "boxel-ai-bot-${{ inputs.environment }}" | |
| image: ${{ needs.build-ai-bot.outputs.image }} | |
| wait-for-service-stability: false | |
| build-bot-runner: | |
| name: Build bot-runner Docker image | |
| uses: cardstack/gh-actions/.github/workflows/docker-ecr.yml@main | |
| secrets: inherit | |
| with: | |
| repository: "boxel-bot-runner-${{ inputs.environment }}" | |
| environment: ${{ inputs.environment }} | |
| dockerfile: "packages/bot-runner/Dockerfile" | |
| deploy-bot-runner: | |
| needs: [build-bot-runner, post-migrate-db] | |
| name: Deploy bot-runner to AWS ECS | |
| uses: cardstack/gh-actions/.github/workflows/ecs-deploy.yml@main | |
| secrets: inherit | |
| with: | |
| container-name: "boxel-bot-runner" | |
| environment: ${{ inputs.environment }} | |
| cluster: ${{ inputs.environment }} | |
| service-name: "boxel-bot-runner-${{ inputs.environment }}" | |
| image: ${{ needs.build-bot-runner.outputs.image }} | |
| wait-for-service-stability: false | |
| build-host: | |
| name: Build host | |
| uses: ./.github/workflows/build-host.yml | |
| secrets: inherit | |
| with: | |
| environment: ${{ inputs.environment }} | |
| deploy-host: | |
| name: Deploy host | |
| needs: [build-host] | |
| uses: ./.github/workflows/deploy-host.yml | |
| secrets: inherit | |
| with: | |
| environment: ${{ inputs.environment }} | |
| deploy-ui: | |
| name: Deploy boxel-ui | |
| if: inputs.environment == 'staging' | |
| uses: ./.github/workflows/deploy-ui.yml | |
| secrets: inherit | |
| with: | |
| environment: staging | |
| build-realm-server: | |
| name: Build realm-server Docker image | |
| uses: cardstack/gh-actions/.github/workflows/docker-ecr.yml@main | |
| secrets: inherit | |
| with: | |
| repository: "boxel-realm-server-${{ inputs.environment }}" | |
| environment: ${{ inputs.environment }} | |
| dockerfile: "packages/realm-server/realm-server.Dockerfile" | |
| build-args: | | |
| "realm_server_script=start:${{ inputs.environment }}" | |
| build-prerender-manager: | |
| name: Build prerender manager Docker image | |
| uses: cardstack/gh-actions/.github/workflows/docker-ecr.yml@main | |
| secrets: inherit | |
| with: | |
| repository: "boxel-prerender-manager-${{ inputs.environment }}" | |
| environment: ${{ inputs.environment }} | |
| dockerfile: "packages/realm-server/prerender-manager.Dockerfile" | |
| build-args: | | |
| "prerender_manager_script=start:prerender-manager" | |
| build-prerender: | |
| name: Build prerender Docker image | |
| uses: cardstack/gh-actions/.github/workflows/docker-ecr.yml@main | |
| secrets: inherit | |
| with: | |
| repository: "boxel-prerender-server-${{ inputs.environment }}" | |
| environment: ${{ inputs.environment }} | |
| dockerfile: "packages/realm-server/prerender.Dockerfile" | |
| build-args: | | |
| "prerender_script=start:prerender-${{ inputs.environment }}" | |
| build-worker: | |
| name: Build worker Docker image | |
| uses: cardstack/gh-actions/.github/workflows/docker-ecr.yml@main | |
| secrets: inherit | |
| with: | |
| repository: "boxel-worker-${{ inputs.environment }}" | |
| environment: ${{ inputs.environment }} | |
| dockerfile: "packages/realm-server/worker.Dockerfile" | |
| build-args: | | |
| "worker_script=start:worker-${{ inputs.environment }}" | |
| build-pg-migration: | |
| name: Build pg-migration Docker image | |
| uses: cardstack/gh-actions/.github/workflows/docker-ecr.yml@main | |
| secrets: inherit | |
| with: | |
| repository: "boxel-pg-migration-${{ inputs.environment }}" | |
| environment: ${{ inputs.environment }} | |
| dockerfile: "packages/postgres/Dockerfile" | |
| migrate-db: | |
| # use "deploy-host" and "build-realm-server" as deps so we can run | |
| # migrations at last possible moment in order to reduce the amount of time | |
| # that old code is pointing to new schema | |
| needs: [build-pg-migration, build-realm-server, deploy-host] | |
| name: Deploy and run DB migrations | |
| uses: cardstack/gh-actions/.github/workflows/ecs-deploy.yml@main | |
| secrets: inherit | |
| with: | |
| container-name: "boxel-pg-migration" | |
| environment: ${{ inputs.environment }} | |
| cluster: ${{ inputs.environment }} | |
| service-name: "boxel-pg-migration-${{ inputs.environment }}" | |
| image: ${{ needs.build-pg-migration.outputs.image }} | |
| wait-for-service-stability: false | |
| # the wait-for-service-stability flag doesn't seem to work in | |
| # aws-actions/amazon-ecs-deploy-task-definition@v2. we keep getting timeouts | |
| # waiting for service stability. So we are manually waiting here. | |
| post-migrate-db: | |
| name: Wait for db-migration | |
| needs: [migrate-db] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - run: sleep 180 | |
| deploy-prerender: | |
| name: Deploy prerender | |
| needs: [build-prerender] | |
| uses: cardstack/gh-actions/.github/workflows/ecs-deploy.yml@main | |
| secrets: inherit | |
| with: | |
| container-name: "boxel-prerender-server" | |
| environment: ${{ inputs.environment }} | |
| cluster: ${{ inputs.environment }} | |
| service-name: "boxel-prerender-server-${{ inputs.environment }}" | |
| image: ${{ needs.build-prerender.outputs.image }} | |
| timeout-minutes: 10 | |
| wait-for-service-stability: true | |
| deploy-prerender-manager: | |
| name: Deploy prerender manager | |
| needs: [build-prerender-manager, deploy-prerender] | |
| uses: cardstack/gh-actions/.github/workflows/ecs-deploy.yml@main | |
| secrets: inherit | |
| with: | |
| container-name: "boxel-prerender-manager" | |
| environment: ${{ inputs.environment }} | |
| cluster: ${{ inputs.environment }} | |
| service-name: "boxel-prerender-manager-${{ inputs.environment }}" | |
| image: ${{ needs.build-prerender-manager.outputs.image }} | |
| timeout-minutes: 10 | |
| wait-for-service-stability: true | |
| deploy-worker: | |
| name: Deploy worker | |
| needs: | |
| [build-worker, deploy-host, post-migrate-db, deploy-prerender-manager] | |
| uses: cardstack/gh-actions/.github/workflows/ecs-deploy.yml@main | |
| secrets: inherit | |
| with: | |
| container-name: "boxel-worker" | |
| environment: ${{ inputs.environment }} | |
| cluster: ${{ inputs.environment }} | |
| service-name: "boxel-worker-${{ inputs.environment }}" | |
| image: ${{ needs.build-worker.outputs.image }} | |
| wait-for-service-stability: false | |
| # the wait-for-service-stability flag doesn't seem to work in | |
| # aws-actions/amazon-ecs-deploy-task-definition@v2. we keep getting timeouts | |
| # waiting for service stability. So we are manually waiting here. | |
| post-deploy-worker: | |
| name: Wait for worker | |
| needs: [deploy-worker] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - run: sleep 180 | |
| deploy-realm-server: | |
| name: Deploy realm server | |
| needs: | |
| [post-deploy-worker, build-realm-server, deploy-host, post-migrate-db] | |
| uses: cardstack/gh-actions/.github/workflows/ecs-deploy.yml@main | |
| secrets: inherit | |
| with: | |
| container-name: "boxel-realm-server" | |
| environment: ${{ inputs.environment }} | |
| cluster: ${{ inputs.environment }} | |
| service-name: "boxel-realm-server-${{ inputs.environment }}" | |
| image: ${{ needs.build-realm-server.outputs.image }} | |
| timeout-minutes: 10 | |
| wait-for-service-stability: true | |
| post-deploy-realm-server: | |
| name: After realm server stable deployment | |
| needs: [deploy-realm-server] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Call post-deployment endpoint on realm server | |
| run: | | |
| if [ "${{ inputs.environment }}" = "production" ]; then | |
| URL="https://app.boxel.ai/_post-deployment?authHeader=${{ secrets.PRODUCTION_REALM_SERVER_SECRET }}" | |
| elif [ "${{ inputs.environment }}" = "staging" ]; then | |
| URL="https://realms-staging.stack.cards/_post-deployment?authHeader=${{ secrets.STAGING_REALM_SERVER_SECRET }}" | |
| else | |
| echo "Unknown environment: ${{ inputs.environment }}" | |
| exit 1 | |
| fi | |
| response=$(curl -s -w "\n%{http_code}" -X POST "$URL") | |
| response_body=$(echo "$response" | head -n -1) | |
| response_code=$(echo "$response" | tail -n 1) | |
| echo "Response body: $response_body" | |
| if [ "$response_code" != "200" ]; then | |
| echo "Post-deployment endpoint returned $response_code, expected 200" | |
| echo "Response body: $response_body" | |
| exit 1 | |
| fi | |
| apply-observability: | |
| # Push the observability/ package's dashboards/folders/data sources/alerts | |
| # into the production self-host Grafana as part of the deploy. The | |
| # called workflow's `environment: observability-production` gives the run | |
| # a production badge and a Grafana URL link, but doesn't gate (no required | |
| # reviewers — dispatching the manual deploy IS the approval). | |
| # | |
| # Production-only. Staging applies happen automatically on merge to main | |
| # via observability-apply-staging.yml — no need to re-run during a | |
| # manual staging deploy. | |
| name: Apply observability to production | |
| if: inputs.environment == 'production' | |
| needs: [post-deploy-realm-server] | |
| uses: ./.github/workflows/observability-apply-production.yml | |
| secrets: inherit | |
| finalize-deployment: | |
| name: Update GitHub deployment status | |
| needs: | |
| [ | |
| create-deployment, | |
| build-ai-bot, | |
| deploy-ai-bot, | |
| build-bot-runner, | |
| deploy-bot-runner, | |
| build-host, | |
| deploy-host, | |
| build-realm-server, | |
| build-prerender-manager, | |
| build-prerender, | |
| build-worker, | |
| build-pg-migration, | |
| migrate-db, | |
| post-migrate-db, | |
| deploy-prerender, | |
| deploy-prerender-manager, | |
| deploy-worker, | |
| post-deploy-worker, | |
| deploy-realm-server, | |
| post-deploy-realm-server, | |
| apply-observability, | |
| ] | |
| if: github.event_name == 'workflow_dispatch' && always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Set deployment status | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| NEEDS: ${{ toJson(needs) }} | |
| DEPLOYMENT_ID: ${{ needs.create-deployment.outputs.deployment-id }} | |
| ENVIRONMENT_URL: ${{ needs.create-deployment.outputs.environment-url }} | |
| with: | |
| script: | | |
| const deploymentIdRaw = process.env.DEPLOYMENT_ID; | |
| if (!deploymentIdRaw) { | |
| core.info('No deployment id found; skipping status update.'); | |
| return; | |
| } | |
| const needs = JSON.parse(process.env.NEEDS); | |
| const results = Object.values(needs).map((job) => job.result); | |
| let state = 'success'; | |
| if (results.includes('failure')) { | |
| state = 'failure'; | |
| } else if (results.includes('cancelled')) { | |
| state = 'inactive'; | |
| } | |
| const environment_url = | |
| process.env.ENVIRONMENT_URL && | |
| process.env.ENVIRONMENT_URL.length > 0 | |
| ? process.env.ENVIRONMENT_URL | |
| : undefined; | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: Number(deploymentIdRaw), | |
| state, | |
| environment_url, | |
| log_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, | |
| description: | |
| state === 'success' | |
| ? 'Deployment finished' | |
| : 'Deployment finished with errors', | |
| }); |