Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .github/actions/finalize-deployment/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Finalize GitHub deployment
description: >-
Set the final status on a deployment record from the three component
deploy results. With lights-parameter set, a successful deploy while the
environment is scaled to zero is recorded as inactive instead of success.

inputs:
deployment-id:
description: Deployment id returned by open-deployment
required: true
environment:
description: GitHub environment name (e.g. dev3, prod)
required: true
environment-url:
description: Public URL shown on the deployment record
required: true
backend-result:
description: Result of the backend deploy job
required: true
frontend-result:
description: Result of the frontend deploy job
required: true
site-result:
description: Result of the site deploy job
required: true
lights-parameter:
description: >-
SSM parameter holding the environment's lights state, for environments
that scale to zero. Requires AWS credentials to already be configured;
any read failure is treated as lights on. Empty disables the check.
required: false
default: ""

runs:
using: composite
steps:
- name: Set final deployment status
shell: bash
env:
GH_TOKEN: ${{ github.token }}
DEPLOYMENT_ID: ${{ inputs.deployment-id }}
ENVIRONMENT: ${{ inputs.environment }}
ENVIRONMENT_URL: ${{ inputs.environment-url }}
LIGHTS_PARAMETER: ${{ inputs.lights-parameter }}
B: ${{ inputs.backend-result }}
F: ${{ inputs.frontend-result }}
S: ${{ inputs.site-result }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
if [[ "$B" == "success" && "$F" == "success" && "$S" == "success" ]]; then
lights=unknown
if [[ -n "$LIGHTS_PARAMETER" ]]; then
lights=$(aws ssm get-parameter --name "$LIGHTS_PARAMETER" \
--query Parameter.Value --output text 2>/dev/null || echo unknown)
fi
if [[ "$lights" == "off" ]]; then
state=inactive
desc="$ENVIRONMENT is asleep (lights off); this ref serves on next wake"
else
state=success
desc="deployed to $ENVIRONMENT"
fi
else
state=failure
desc="$ENVIRONMENT deploy failed (backend:$B frontend:$F site:$S)"
fi
# On a success status GitHub auto-inactivates the previous deployment
# for this environment, so the Environments tab reflects what is live.
gh api -X POST "repos/${GITHUB_REPOSITORY}/deployments/${DEPLOYMENT_ID}/statuses" \
-f state="$state" \
-f description="$desc" \
-f environment="$ENVIRONMENT" \
-f environment_url="$ENVIRONMENT_URL" \
-f log_url="$RUN_URL" >/dev/null
58 changes: 58 additions & 0 deletions .github/actions/open-deployment/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Open GitHub deployment
description: >-
Open a GitHub deployment record for an environment and mark it
in_progress. Best-effort: never fails the job — an empty deployment_id
output means no record was created, and callers should skip finalizing.

inputs:
environment:
description: GitHub environment name (e.g. dev3, prod)
required: true
environment-url:
description: Public URL shown on the deployment record
required: true
ref:
description: Git ref or SHA the deployment is attributed to
required: true
description:
description: Short description shown on the deployment record
required: true
production-environment:
description: Whether GitHub badges this environment as production (JSON boolean)
required: false
default: "false"

outputs:
deployment_id:
description: The created deployment id, or empty if creation failed
value: ${{ steps.open.outputs.deployment_id }}

runs:
using: composite
steps:
- name: Open deployment and mark in_progress
id: open
shell: bash
env:
GH_TOKEN: ${{ github.token }}
ENVIRONMENT: ${{ inputs.environment }}
ENVIRONMENT_URL: ${{ inputs.environment-url }}
REF: ${{ inputs.ref }}
DESCRIPTION: ${{ inputs.description }}
PRODUCTION_ENVIRONMENT: ${{ inputs.production-environment }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
payload=$(jq -n --arg ref "$REF" --arg env "$ENVIRONMENT" \
--arg desc "$DESCRIPTION" --argjson prod "$PRODUCTION_ENVIRONMENT" \
'{ref: $ref, environment: $env, required_contexts: [], auto_merge: false, production_environment: $prod, description: $desc}')
id=$(gh api -X POST "repos/${GITHUB_REPOSITORY}/deployments" --input - -q .id <<<"$payload" 2>/dev/null || true)
if [[ -n "$id" ]]; then
echo "deployment_id=$id" >> "$GITHUB_OUTPUT"
gh api -X POST "repos/${GITHUB_REPOSITORY}/deployments/${id}/statuses" \
-f state=in_progress \
-f environment="$ENVIRONMENT" \
-f environment_url="$ENVIRONMENT_URL" \
-f log_url="$RUN_URL" >/dev/null 2>&1 || true
else
echo "::warning::could not create $ENVIRONMENT deployment record; deploy continues"
fi
135 changes: 42 additions & 93 deletions .github/workflows/dev-deploy-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,85 +144,58 @@ jobs:
echo "blocked=true" >> "$GITHUB_OUTPUT"
fi

- name: Open GitHub deployment
id: deployment
- name: Resolve PR head commit
id: head
if: steps.cmd.outputs.run == 'true' && steps.check.outputs.authorized == 'true' && steps.merge.outputs.blocked != 'true'
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.issue.number }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
# Attribute the deployment to the PR head commit so GitHub links it to
# the PR (the built code may be refs/pull/N/merge, but the head SHA is
# the real commit that shows up on the PR timeline).
# Best-effort: deployment bookkeeping must never block an actual deploy.
head_sha=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR}" -q .head.sha 2>/dev/null || true)
if [[ -n "$head_sha" ]]; then
payload=$(jq -n --arg ref "$head_sha" \
'{ref: $ref, environment: "dev3", required_contexts: [], auto_merge: false, description: "dev3 deploy (PR command)"}')
id=$(gh api -X POST "repos/${GITHUB_REPOSITORY}/deployments" --input - -q .id <<<"$payload" 2>/dev/null || true)
fi
if [[ -n "${id:-}" ]]; then
echo "deployment_id=$id" >> "$GITHUB_OUTPUT"
gh api -X POST "repos/${GITHUB_REPOSITORY}/deployments/${id}/statuses" \
-f state=in_progress \
-f environment=dev3 \
-f environment_url=https://dev3.doenet.org \
-f log_url="$RUN_URL" >/dev/null 2>&1 || true
else
echo "::warning::could not create dev3 deployment record; deploy continues"
if [[ -z "$head_sha" ]]; then
echo "::warning::could not resolve PR head sha; deploying without a dev3 deployment record"
fi
echo "sha=$head_sha" >> "$GITHUB_OUTPUT"

deploy-backend:
needs: gate
if: needs.gate.outputs.go == 'true'
uses: ./.github/workflows/reusable-deploy-backend.yml
with:
deploy_ref: ${{ needs.gate.outputs.deploy_ref }}
aws_region: us-east-2
ecr_repository: dev3/doenet
ecs_cluster: dev3
ecs_service: doenet-FARGATE
gha_role: arn:aws:iam::568298704244:role/github-actions-dev3
sns_topic: arn:aws:sns:us-east-2:568298704244:dev3-critical-cloudwatch-notifications
tag_name: latest
secrets: inherit
# Local composite actions need the repo on disk. issue_comment events
# check out the default branch, so this is trusted code, not the PR's.
- name: Checkout repository
if: steps.head.outputs.sha != ''
uses: actions/checkout@v6

deploy-frontend:
needs: gate
if: needs.gate.outputs.go == 'true'
uses: ./.github/workflows/reusable-deploy-frontend.yml
with:
deploy_ref: ${{ needs.gate.outputs.deploy_ref }}
aws_region: us-east-1
gha_role: arn:aws:iam::568298704244:role/github-actions-dev3
aws_account_id: "568298704244"
env_name: dev3
build_mode: dev3
secrets: inherit
- name: Open GitHub deployment
id: deployment
if: steps.head.outputs.sha != ''
uses: ./.github/actions/open-deployment
with:
environment: dev3
environment-url: https://dev3.doenet.org
ref: ${{ steps.head.outputs.sha }}
description: dev3 deploy (PR command)

deploy-site:
deploy:
needs: gate
if: needs.gate.outputs.go == 'true'
uses: ./.github/workflows/reusable-deploy-site.yml
uses: ./.github/workflows/reusable-deploy-env.yml
with:
deploy_ref: ${{ needs.gate.outputs.deploy_ref }}
aws_region: us-east-1
gha_role: arn:aws:iam::568298704244:role/github-actions-dev3
aws_account_id: "568298704244"
env_name: dev3
build_mode: dev3
secrets: inherit

notify:
needs: [gate, deploy-backend, deploy-frontend, deploy-site]
needs: [gate, deploy]
if: always() && needs.gate.outputs.go == 'true'
runs-on: ubuntu-latest
steps:
# Read-only creds so we can ask whether dev3 is actually awake before
# claiming a deployment is live. Best-effort: never fails the job.
# Read-only creds so finalize-deployment can ask whether dev3 is
# actually awake before claiming a deployment is live. Best-effort:
# never fails the job; without creds the lights read falls back to
# treating dev3 as awake.
- name: Configure AWS credentials
id: aws
if: needs.gate.outputs.deployment_id != ''
continue-on-error: true
uses: aws-actions/configure-aws-credentials@v6
Expand All @@ -232,55 +205,31 @@ jobs:
role-session-name: dev3-deploy-status
aws-region: us-east-2

- name: Checkout repository
if: needs.gate.outputs.deployment_id != ''
uses: actions/checkout@v6

- name: Finalize GitHub deployment status
if: needs.gate.outputs.deployment_id != ''
env:
GH_TOKEN: ${{ github.token }}
ID: ${{ needs.gate.outputs.deployment_id }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
HAVE_AWS: ${{ steps.aws.outcome }}
B: ${{ needs.deploy-backend.result }}
F: ${{ needs.deploy-frontend.result }}
S: ${{ needs.deploy-site.result }}
run: |
if [[ "$B" == "success" && "$F" == "success" && "$S" == "success" ]]; then
# dev3 scales to zero on the lights-out schedule (and the backend
# deploy is a no-op while it's asleep), so reflect whether it's
# actually serving rather than always claiming success.
lights=unknown
if [[ "$HAVE_AWS" == "success" ]]; then
lights=$(aws ssm get-parameter --name /dev3/doenet/lights \
--query Parameter.Value --output text 2>/dev/null || echo unknown)
fi
if [[ "$lights" == "off" ]]; then
state=inactive
desc="dev3 is asleep (lights off); this ref serves on next wake"
else
state=success
desc="deployed to dev3"
fi
else
state=failure
desc="dev3 deploy failed (backend:$B frontend:$F site:$S)"
fi
# On a success status GitHub auto-inactivates the previous dev3
# deployment, so the Environments tab reflects what is currently live.
gh api -X POST "repos/${GITHUB_REPOSITORY}/deployments/${ID}/statuses" \
-f state="$state" \
-f description="$desc" \
-f environment=dev3 \
-f environment_url=https://dev3.doenet.org \
-f log_url="$RUN_URL" >/dev/null
uses: ./.github/actions/finalize-deployment
with:
deployment-id: ${{ needs.gate.outputs.deployment_id }}
environment: dev3
environment-url: https://dev3.doenet.org
backend-result: ${{ needs.deploy.outputs.backend_result }}
frontend-result: ${{ needs.deploy.outputs.frontend_result }}
site-result: ${{ needs.deploy.outputs.site_result }}
lights-parameter: /dev3/doenet/lights

- name: Comment result on the PR
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.issue.number }}
REF: ${{ needs.gate.outputs.deploy_ref }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
B: ${{ needs.deploy-backend.result }}
F: ${{ needs.deploy-frontend.result }}
S: ${{ needs.deploy-site.result }}
B: ${{ needs.deploy.outputs.backend_result }}
F: ${{ needs.deploy.outputs.frontend_result }}
S: ${{ needs.deploy.outputs.site_result }}
run: |
if [[ "$B" == "success" && "$F" == "success" && "$S" == "success" ]]; then
printf '%s\n' \
Expand Down
Loading