ci(deploy): make dev content deploy workflow_dispatch only

Remove the push-to-dev trigger and the org cd-cdk.yaml caller so CI no
longer runs cdk deploy during the adoption. The workflow assumes the
pinned dev role and runs the simple scripts/deploy-web.sh against a
pinned bucket and distribution, which keeps content deploys working
after CloudFormation relinquishes the stack outputs. Staging is
untouched.
This commit is contained in:
Adam Moussa 2026-09-10 19:15:12 -04:00
parent 82361e14b5
commit 87e79072ad
No known key found for this signature in database
2 changed files with 108 additions and 48 deletions

View file

@ -1,22 +1,22 @@
name: Deploy
name: Deploy dev content
# Continuous deployment to AWS (S3 + CloudFront) on push to `dev`.
# Manual dev content deployment during the Terraform adoption (SH-300).
#
# This is a thin caller of the org's reusable CD workflow. `cd-cdk.yaml` runs
# `cdk deploy` (provisioning the infra in infra/cdk) and then the
# post-deploy-script, which builds the SPA and syncs it to S3 + invalidates
# CloudFront. Both run as the OIDC deploy role created by the stack.
# The push-to-`dev` trigger and the org reusable `cd-cdk.yaml` caller are
# retired: `cdk deploy` no longer runs from CI. Infrastructure changes are
# administrator-run (`infra/cdk/README.md`) while CloudFormation still owns the
# resources, and move to HCP Terraform (`terraform/README.md`) as adoption
# completes. Automatic push-to-`dev` releases return with the Terraform
# content-CD change, gated on a repository variable.
#
# When staging/prod accounts exist, add jobs keyed to their branches and their
# own AWS_DEPLOY_ROLE_ARN, reusing this same reusable workflow.
# This workflow publishes only content: verify, build, `aws s3 sync`, and a
# CloudFront invalidation through `scripts/deploy-web.sh`, as the pinned OIDC
# deploy role. The bucket and distribution are pinned here so a content deploy
# keeps working after CloudFormation relinquishes the stack outputs.
on:
push:
branches: [dev]
workflow_dispatch: {}
# OIDC needs id-token: write — it is never in the default token set and cannot
# be granted to the reusable workflow unless the caller has it.
permissions:
id-token: write
contents: read
@ -27,22 +27,13 @@ concurrency:
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@af0f002e14a08cdbfd879c1183bfe7eb2604bce9 # v1.0.8
with:
node-version: "24"
region: us-east-1
cdk-dir: infra/cdk
stack-name: shoc-frontend-dev
post-deploy-script: scripts/deploy-web.sh
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
upload-sourcemaps:
name: Upload private source maps
needs: deploy
name: Publish content to dev
# Deploy only the exact dev branch ref: workflow_dispatch can be invoked
# from arbitrary refs, and the deploy role trusts only refs/heads/dev.
if: github.ref == 'refs/heads/dev'
runs-on: ubuntu-latest
env:
AWS_REGION: us-east-1
VITE_APP_COMMIT_SHA: ${{ github.sha }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@ -52,9 +43,66 @@ jobs:
with:
node-version: "24"
cache: npm
- name: Build exact deployed release
run: npm ci && npm run build
- name: Upload source maps to Sentry
- name: Set up Terraform
# Required by `npm run verify` (governance runs terraform fmt/validate).
uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
with:
terraform_version: "1.16.0"
terraform_wrapper: false
- name: Quality gates (full verify before any deploy)
run: npm ci && npm run verify
env:
GOVERNANCE_BASE: origin/dev
- name: Assume dev deploy role (OIDC)
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
role-to-assume: arn:aws:iam::396287094661:role/githubdeploy-shoc-frontend-new-dev
aws-region: us-east-1
# Builds with the dev values committed in .env.production (VITE_API_URL,
# Sentry DSN), syncs to the pinned bucket, and invalidates CloudFront.
- name: Build and publish SPA
run: bash scripts/deploy-web.sh
env:
SITE_BUCKET: seahaven-shoc-frontend-dev
CLOUDFRONT_DISTRIBUTION_ID: E2CWLM1AFB964P
WAIT_FOR_INVALIDATION: "true"
- name: Upload private source maps
run: bash scripts/upload-sourcemaps.sh
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: Verify deployment
run: |
set -euo pipefail
SITE_URL="https://dev.seahaven.com"
if grep -Rq "api.staging.seahaven.com" dist/; then
echo "::error::Built assets contain the staging API URL." >&2
exit 1
fi
grep -Rq "api.dev.seahaven.com" dist/
echo "Built assets reference the dev API URL."
# The invalidation has completed, but give edges a short window to
# converge before calling the served index.html wrong.
remote_dir="$(mktemp -d)"
trap 'rm -rf "${remote_dir}"' EXIT
matched=false
for i in 1 2 3 4 5 6; do
if curl -fsS --max-time 30 "${SITE_URL}" -o "${remote_dir}/index.html" \
&& cmp -s dist/index.html "${remote_dir}/index.html"; then
matched=true
break
fi
echo "Served index.html does not yet match the published build (attempt ${i}); retrying in 20s..."
sleep 20
done
if [[ "${matched}" != "true" ]]; then
echo "::error::Served index.html does not match the build just published." >&2
exit 1
fi
echo "Served index.html matches the published build."
curl -fsS --max-time 30 -o /dev/null "${SITE_URL}/login"
echo "Extensionless SPA route serves."

View file

@ -1,14 +1,16 @@
#!/usr/bin/env bash
#
# Post-deploy step for the org reusable workflow `cd-cdk.yaml`
# (wired in via `.github/workflows/deploy.yml` -> `post-deploy-script`).
# Content publish step for the environment deploy workflows
# (`.github/workflows/deploy.yml`, `.github/workflows/deploy-staging.yml`).
#
# Runs AFTER `cdk deploy` has provisioned/updated the infra, as the GitHub
# OIDC deploy role. Builds the SPA, uploads it to the stack's S3 bucket with
# the right cache headers, and invalidates CloudFront.
# Runs as the GitHub OIDC deploy role. Builds the SPA, uploads it to the
# environment's S3 bucket with the right cache headers, and invalidates
# CloudFront. It never touches infrastructure.
#
# Runs from the repo root. Reads the bucket + distribution from stack outputs,
# so it has no hardcoded resource IDs.
# Runs from the repo root. The target is resolved from, in order:
# 1. SITE_BUCKET + CLOUDFRONT_DISTRIBUTION_ID (pinned by the workflow; used by
# dev, whose CloudFormation outputs disappear during Terraform adoption)
# 2. the BucketName/DistributionId outputs of STACK_NAME (staging)
set -euo pipefail
STACK_NAME="${STACK_NAME:-shoc-frontend-dev}"
@ -20,21 +22,31 @@ export VITE_APP_COMMIT_SHA="${VITE_APP_COMMIT_SHA:-${GITHUB_SHA:-}}"
npm ci
npm run build
echo "Reading stack outputs from ${STACK_NAME}..."
stack_output() {
aws cloudformation describe-stacks \
--stack-name "${STACK_NAME}" \
--region "${REGION}" \
--query "Stacks[0].Outputs[?OutputKey=='$1'].OutputValue" \
--output text
}
BUCKET="${SITE_BUCKET:-}"
DIST_ID="${CLOUDFRONT_DISTRIBUTION_ID:-}"
BUCKET="$(stack_output BucketName)"
DIST_ID="$(stack_output DistributionId)"
if [[ -z "${BUCKET}" || "${BUCKET}" == "None" || -z "${DIST_ID}" || "${DIST_ID}" == "None" ]]; then
echo "::error::Could not resolve BucketName/DistributionId from stack ${STACK_NAME}." >&2
if [[ -n "${BUCKET}" && -n "${DIST_ID}" ]]; then
echo "Using pinned target: bucket ${BUCKET}, distribution ${DIST_ID}."
elif [[ -n "${BUCKET}" || -n "${DIST_ID}" ]]; then
echo "::error::Set both SITE_BUCKET and CLOUDFRONT_DISTRIBUTION_ID, or neither." >&2
exit 1
else
echo "Reading stack outputs from ${STACK_NAME}..."
stack_output() {
aws cloudformation describe-stacks \
--stack-name "${STACK_NAME}" \
--region "${REGION}" \
--query "Stacks[0].Outputs[?OutputKey=='$1'].OutputValue" \
--output text
}
BUCKET="$(stack_output BucketName)"
DIST_ID="$(stack_output DistributionId)"
if [[ -z "${BUCKET}" || "${BUCKET}" == "None" || -z "${DIST_ID}" || "${DIST_ID}" == "None" ]]; then
echo "::error::Could not resolve BucketName/DistributionId from stack ${STACK_NAME}." >&2
exit 1
fi
fi
echo "Uploading hashed assets (immutable) to s3://${BUCKET}..."