Skip to content

Manual Deploy [boxel] #1274

Manual Deploy [boxel]

Manual Deploy [boxel] #1274

Workflow file for this run

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',
});