mirror of
https://github.com/Sea-Haven-Industries/seahaven-ap.git
synced 2026-09-30 10:23:17 +00:00
199 lines
6.9 KiB
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}"
|