seahaven-ap/.github/workflows/deploy-web.yaml

199 lines
6.9 KiB
YAML

name: Deploy Web
# SPA CD. GitHub Actions syncs the committed placeholder to the S3 origin
# bucket root, then invalidates CloudFront. Terraform owns the bucket and the
# distribution and never touches content. Do not run a SPA production build.
#
# push to main -> GitHub Environment dev, at github.sha
# workflow_dispatch -> GitHub Environment dev. The workflow file must be main.
# inputs.ref selects the placeholder tree to publish.
# Job steps are the workflow file, not scripts from that ref.
#
# Nothing here creates an HCP run. Prod is AP-12.
on:
push:
branches: [main]
paths-ignore:
- "terraform/**"
- "packages/api/**"
- "packages/shared/**"
- "docs/**"
- "**/*.md"
- "Dockerfile"
- ".dockerignore"
- "scripts/verify-api-health.sh"
- "scripts/test-verify-api-health.sh"
- "scripts/test-terraform-dev-only.py"
- "scripts/patch-ecs-task-def.py"
- ".github/workflows/deploy-api.yaml"
- ".github/workflows/ci.yaml"
workflow_dispatch:
inputs:
environment:
description: "Target Environment"
required: true
type: choice
options: [dev]
ref:
description: "Git ref to deploy (branch or SHA). Empty means the workflow ref."
required: false
type: string
default: ""
permissions:
contents: read
jobs:
target:
name: Resolve target
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
environment: ${{ steps.resolve.outputs.environment }}
ref: ${{ steps.resolve.outputs.ref }}
steps:
- id: resolve
env:
EVENT_NAME: ${{ github.event_name }}
GITHUB_REF_NAME_IN: ${{ github.ref }}
GITHUB_SHA_IN: ${{ github.sha }}
INPUT_ENVIRONMENT: ${{ inputs.environment }}
INPUT_REF: ${{ inputs.ref }}
run: |
set -euo pipefail
case "${EVENT_NAME}" in
push)
if [ "${GITHUB_REF_NAME_IN}" != "refs/heads/main" ]; then
echo "push deploys only run from main" >&2
exit 1
fi
environment=dev
ref="${GITHUB_SHA_IN}"
;;
workflow_dispatch)
if [ "${GITHUB_REF_NAME_IN}" != "refs/heads/main" ]; then
echo "workflow_dispatch deploys only run from main" >&2
exit 1
fi
environment="${INPUT_ENVIRONMENT:-dev}"
if [ "${environment}" != "dev" ]; then
echo "only GitHub Environment dev is allowed" >&2
exit 1
fi
ref="${INPUT_REF:-${GITHUB_SHA_IN}}"
;;
*)
echo "unsupported event ${EVENT_NAME}" >&2
exit 1
;;
esac
{
echo "environment=${environment}"
echo "ref=${ref}"
} >> "${GITHUB_OUTPUT}"
echo "Deploying ${ref} to ${environment}"
deploy:
name: Deploy SPA to ${{ needs.target.outputs.environment }}
needs: target
runs-on: ubuntu-latest
timeout-minutes: 30
environment: ${{ needs.target.outputs.environment }}
concurrency:
group: deploy-web-${{ needs.target.outputs.environment }}
cancel-in-progress: false
permissions:
contents: read
id-token: write
env:
AWS_REGION: us-east-1
DEPLOY_ROLE_ARN: ${{ vars.DEPLOY_ROLE_ARN }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.target.outputs.ref }}
persist-credentials: false
- name: Resolve commit
id: commit
run: |
set -euo pipefail
sha="$(git rev-parse HEAD)"
echo "sha=${sha}" >> "${GITHUB_OUTPUT}"
echo "Deploying ${sha}"
test -f placeholder/index.html
- name: Require deploy role
run: |
set -euo pipefail
if [ -z "${DEPLOY_ROLE_ARN}" ]; then
echo "DEPLOY_ROLE_ARN is empty. Create GitHub Environment dev and set it after the seahaven-ap-dev apply, then re-run." >&2
exit 1
fi
- name: Configure AWS credentials using OIDC
uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
with:
role-to-assume: ${{ env.DEPLOY_ROLE_ARN }}
aws-region: us-east-1
audience: sts.amazonaws.com
- name: Get deploy parameters
id: deploy
run: |
set -euo pipefail
get_param() {
local name="$1" err value
err="$(mktemp)"
if ! value="$(aws ssm get-parameter --name "${name}" --query Parameter.Value --output text 2>"${err}")"; then
if grep -q ParameterNotFound "${err}"; then
echo "SSM parameter ${name} does not exist yet. Apply the seahaven-ap-dev workspace, then re-run this workflow." >&2
else
cat "${err}" >&2
fi
rm -f "${err}"
exit 1
fi
rm -f "${err}"
printf '%s\n' "${value}"
}
BUCKET=$(get_param /seahaven-ap/deploy/bucket)
DIST_ID=$(get_param /seahaven-ap/deploy/distribution-id)
DOMAIN=$(aws cloudfront get-distribution --id "${DIST_ID}" --query Distribution.DomainName --output text)
{
echo "bucket=${BUCKET}"
echo "distribution_id=${DIST_ID}"
echo "site_url=https://${DOMAIN}"
} >> "${GITHUB_OUTPUT}"
- name: Sync placeholder/ to the bucket root
env:
SITE_BUCKET: ${{ steps.deploy.outputs.bucket }}
run: |
set -euo pipefail
aws s3 sync placeholder/ "s3://${SITE_BUCKET}/" \
--exclude "index.html" \
--cache-control "public,max-age=31536000,immutable"
aws s3 cp placeholder/index.html "s3://${SITE_BUCKET}/index.html" \
--cache-control "no-cache,no-store,must-revalidate" \
--content-type "text/html"
aws s3 sync placeholder/ "s3://${SITE_BUCKET}/" \
--delete \
--exclude "index.html" \
--cache-control "public,max-age=31536000,immutable"
aws s3api head-object --bucket "${SITE_BUCKET}" --key index.html
- name: Invalidate CloudFront
env:
DISTRIBUTION_ID: ${{ steps.deploy.outputs.distribution_id }}
run: |
set -euo pipefail
invalidation_id="$(aws cloudfront create-invalidation \
--distribution-id "${DISTRIBUTION_ID}" \
--paths "/*" \
--query Invalidation.Id --output text)"
echo "Invalidation ${invalidation_id} created; waiting"
aws cloudfront wait invalidation-completed \
--distribution-id "${DISTRIBUTION_ID}" \
--id "${invalidation_id}"