Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d237a9e
add reusable E2E workflow for CodeBuild-based testing
darwinboersma Mar 18, 2026
574bb14
rename tdp-backend-e2e to tdp-e2e
darwinboersma Mar 18, 2026
ab457b8
address security and review comments
darwinboersma Mar 18, 2026
80429b0
remove unnecessary +5 buffer from job timeout
darwinboersma Mar 18, 2026
ee39290
make JFrog secrets required, simplify env var assembly
darwinboersma Mar 18, 2026
c5af998
add optional deploy-before-e2e: push to env branch and wait for CI
darwinboersma Mar 18, 2026
b290ef1
remove infra_config_ref input — only needed during dev
darwinboersma Mar 18, 2026
f6c5828
accept env_vars_json as flat object, convert to CodeBuild format inte…
darwinboersma Mar 18, 2026
a3ced6f
address review: force-with-lease, set -euo pipefail, error handling, …
darwinboersma Mar 18, 2026
844e7eb
re-add infra_config_ref as optional input for pre-merge testing
darwinboersma Mar 18, 2026
850f08d
embed default buildspec inline, make test_command configurable
darwinboersma Mar 18, 2026
be1d152
use yarn config set instead of raw echo for JFrog registry config
darwinboersma Mar 18, 2026
9946b06
move default buildspec to a file, copy into source zip before upload
darwinboersma Mar 18, 2026
1693c32
document e2e-codebuild reusable workflow in README
darwinboersma Mar 18, 2026
b728a23
stream CodeBuild logs into GHA output and job summary
darwinboersma Mar 18, 2026
e69caf1
fix README: contents: read in examples, clarify PAT description
darwinboersma Mar 18, 2026
643674f
add deploy_paths input to conditionally skip deploy for test-only cha…
darwinboersma Mar 18, 2026
c345a42
make deploy_paths required — callers must be explicit about what trig…
darwinboersma Mar 18, 2026
9b95684
remove deploy_before_e2e — deploy_paths controls everything
darwinboersma Mar 18, 2026
2454d4c
allow empty deploy_paths to skip deploy entirely
darwinboersma Mar 18, 2026
d69ac81
make environment required — no silent defaults
darwinboersma Mar 18, 2026
bf094dc
remove test_command input and E2E_TEST_COMMAND — just use yarn test:e2e
darwinboersma Mar 18, 2026
ba51236
remove test_command input and E2E_TEST_COMMAND env var
darwinboersma Mar 18, 2026
5eeb845
rename ci_workflow to deploy_workflow for clarity
darwinboersma Mar 18, 2026
12ee721
update README to reflect latest API: required inputs, deploy_paths, d…
darwinboersma Mar 18, 2026
e4bb9a0
expand infrastructure prerequisites with environment setup steps
darwinboersma Mar 18, 2026
9ccf9db
add working_directory input for publishing subdirectory packages
darwinboersma Mar 19, 2026
3cc6d55
enable corepack in publish workflow for Yarn 4+ repos
darwinboersma Mar 19, 2026
3eb0935
remove SSM auth — pass E2E_AUTH_TOKEN from GitHub secrets instead
darwinboersma Mar 19, 2026
cdbfbb6
revert to SSM for E2E auth token — per-account secrets required
darwinboersma Mar 19, 2026
a2eda1a
use service-scoped SSM path for E2E auth token
darwinboersma Mar 19, 2026
299a5fc
fix: fetch SSM token in install phase (parameter-store can't interpol…
darwinboersma Mar 19, 2026
2bc020b
pass E2E_SERVICE_NAME to CodeBuild for service-scoped SSM path
darwinboersma Mar 19, 2026
e13a3bc
make buildspec required, remove default buildspec pattern
darwinboersma Mar 19, 2026
7797e65
rewrite e2e-codebuild README section — concise, matches current API
darwinboersma Mar 19, 2026
d485808
remove default buildspec — each service provides its own
darwinboersma Mar 19, 2026
11cbc9c
feat: pass E2E_ENVIRONMENT to CodeBuild
darwinboersma Mar 19, 2026
57d3e7c
refactor: remove env_vars_json input
darwinboersma Mar 19, 2026
98ca3b2
docs: update README for environments.json pattern
darwinboersma Mar 19, 2026
67b8e15
fix: add 5 min buffer to e2e job timeout
darwinboersma Mar 19, 2026
dcc54ae
fix: simplify timeout expression to fix workflow parse error
darwinboersma Mar 19, 2026
66626e4
fix: revert timeout to simple input, bump default to 25
darwinboersma Mar 19, 2026
a29a842
refactor: hardcode infra_config_ref, remove caller input
darwinboersma Mar 19, 2026
b0bebeb
fix: improve change detection and timeout handling
darwinboersma Mar 19, 2026
570d8d5
docs: move e2e-codebuild section to bottom of README
darwinboersma Mar 19, 2026
6a56def
docs: restore full publish-npm-package and check-links documentation
darwinboersma Mar 19, 2026
9c47027
refactor: inline environment config, remove external config dependency
darwinboersma Mar 26, 2026
b115fc7
feat: add ECS stabilization wait after deploy
darwinboersma Mar 27, 2026
4ff606f
docs: fix e2e-codebuild README to match actual implementation
darwinboersma Mar 27, 2026
8fccbfe
refactor: remove ecs_cluster/ecs_service inputs
darwinboersma Mar 27, 2026
211d3f2
Move permissions from top-level to job-level
darwinboersma Mar 30, 2026
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
363 changes: 363 additions & 0 deletions .github/workflows/e2e-codebuild.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
name: E2E Tests (CodeBuild)

on:
workflow_call:
inputs:
environment:
description: "Target environment (e.g. predev3, dev)"
type: string
required: true
deploy_paths:
description: "Space-separated glob patterns — deploy only if changed files match. Empty string = never deploy."
type: string
required: true
buildspec:
description: "Path to the buildspec file in the caller repo"
type: string
required: true
Comment thread
darwinboersma marked this conversation as resolved.
deploy_workflow:
description: "Workflow file that builds and deploys the service (waited on after pushing to env branch)"
type: string
default: "ci.yml"
required: false
image_override:
description: "Override the CodeBuild base image (e.g. mcr.microsoft.com/playwright:v1.52.0-noble)"
type: string
required: false
compute_type_override:
description: "Override the CodeBuild compute type (e.g. BUILD_GENERAL1_MEDIUM for 7GB RAM)"
type: string
required: false
timeout_minutes:
description: "Max minutes for the E2E job — includes buffer for setup steps"
type: number
default: 25
Comment thread
darwinboersma marked this conversation as resolved.
required: false
secrets:
JFROG_ARTIFACTORY_NPM_VIRTUAL_URL:
required: true
JFROG_ARTIFACTORY_READ_NPM_AUTH:
required: true
GITHUB_PAT:
description: "PAT with cross-repo access and contents:write for deploy push"
required: true

jobs:
check-changes:
if: ${{ inputs.deploy_paths != '' }}
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
should_deploy: ${{ steps.check.outputs.should_deploy }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0

- name: Check if service code changed
id: check
env:
DEPLOY_PATHS: ${{ inputs.deploy_paths }}
BEFORE_SHA: ${{ github.event.before }}
run: |
set -euo pipefail
if [[ -n "${GITHUB_BASE_REF:-}" ]]; then
BASE="origin/$GITHUB_BASE_REF"
elif [[ -n "$BEFORE_SHA" && "$BEFORE_SHA" != "0000000000000000000000000000000000000000" ]]; then
BASE="$BEFORE_SHA"
else
BASE="HEAD~1"
fi

CHANGED_FILES=$(git diff --name-only "$BASE" HEAD 2>/dev/null || echo "")
Comment thread
darwinboersma marked this conversation as resolved.
if [[ -z "$CHANGED_FILES" ]]; then
echo "No changed files detected, will deploy"
echo "should_deploy=true" >> "$GITHUB_OUTPUT"
exit 0
fi

echo "Changed files:"
echo "$CHANGED_FILES"

for pattern in $DEPLOY_PATHS; do
while IFS= read -r file; do
if [[ -n "$file" ]] && [[ "$file" == $pattern ]]; then
echo "Match: $file matches $pattern"
echo "should_deploy=true" >> "$GITHUB_OUTPUT"
exit 0
fi
done <<< "$CHANGED_FILES"
done

echo "No service code changes — skipping deploy"
echo "should_deploy=false" >> "$GITHUB_OUTPUT"

deploy:
needs: check-changes
if: ${{ needs.check-changes.outputs.should_deploy == 'true' }}
runs-on: ubuntu-latest
Comment thread
darwinboersma marked this conversation as resolved.
timeout-minutes: 20
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_PAT }}

- name: Push to environment branch
env:
TARGET_ENV: ${{ inputs.environment }}
run: |
set -euo pipefail
git push origin HEAD:refs/heads/$TARGET_ENV --force-with-lease
echo "Pushed $(git rev-parse HEAD) to $TARGET_ENV"

- name: Wait for deploy
env:
GH_TOKEN: ${{ secrets.GITHUB_PAT }}
Comment thread
darwinboersma marked this conversation as resolved.
TARGET_ENV: ${{ inputs.environment }}
DEPLOY_WORKFLOW: ${{ inputs.deploy_workflow }}
run: |
set -euo pipefail
COMMIT_SHA=$(git rev-parse HEAD)
echo "Waiting for $DEPLOY_WORKFLOW on $TARGET_ENV branch (commit $COMMIT_SHA)..."

for i in $(seq 1 20); do
sleep 15
RUN_ID=$(gh run list --branch "$TARGET_ENV" --workflow "$DEPLOY_WORKFLOW" --limit 20 --json databaseId,headSha,status \
| jq -r --arg sha "$COMMIT_SHA" '.[] | select(.headSha == $sha) | .databaseId' | head -1)
if [[ -n "$RUN_ID" ]]; then
echo "Found deploy run: $RUN_ID"
break
fi
Comment thread
darwinboersma marked this conversation as resolved.
done

if [[ -z "$RUN_ID" ]]; then
echo "::error::Deploy workflow run not found after 5 minutes"
exit 1
fi

gh run watch "$RUN_ID" --exit-status
echo "Deploy completed successfully"


e2e:
needs: [check-changes, deploy]
if: |
always() &&
(needs.deploy.result == 'success' || needs.deploy.result == 'skipped') &&
(needs.check-changes.result == 'success' || needs.check-changes.result == 'skipped')
runs-on: ubuntu-latest
timeout-minutes: ${{ inputs.timeout_minutes }}
permissions:
id-token: write
contents: read

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Resolve environment
id: env
env:
TARGET_ENV: ${{ inputs.environment }}
run: |
set -euo pipefail

# Environment → AWS account mapping.
# The E2E infra (CodeBuild, OIDC role, S3 bucket) is deployed as a
# substack of the TDP service stack via EnableE2E=true.
# Account IDs and regions are stable infrastructure facts.
declare -A ACCOUNTS=(
[predev]=611255221147
[predev2]=383434869204
[predev3]=866984931759
[predev4]=456932089055
[predev5]=734018046548
[predev6]=326392129485
[predev7]=901926888715
[predev8]=581141123528
[dev]=706717599419
[preuat]=555180171822
)

declare -A REGIONS=(
[preuat]=us-west-2
)

# CloudFormation Environment value used in IAM role names.
# All predev/dev environments use "development"; preuat uses "preuat".
declare -A CF_ENVIRONMENTS=(
[preuat]=preuat
)

ACCOUNT="${ACCOUNTS[$TARGET_ENV]:-}"
REGION="${REGIONS[$TARGET_ENV]:-us-east-2}"
CF_ENV="${CF_ENVIRONMENTS[$TARGET_ENV]:-development}"

if [[ -z "$ACCOUNT" ]]; then
echo "::error::Unknown environment: $TARGET_ENV"
exit 1
fi

echo "env=$TARGET_ENV" >> "$GITHUB_OUTPUT"
echo "account=$ACCOUNT" >> "$GITHUB_OUTPUT"
echo "region=$REGION" >> "$GITHUB_OUTPUT"
echo "cf_env=$CF_ENV" >> "$GITHUB_OUTPUT"
echo "source_bucket=tdp-e2e-source-${ACCOUNT}" >> "$GITHUB_OUTPUT"

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
aws-region: ${{ steps.env.outputs.region }}
role-to-assume: arn:aws:iam::${{ steps.env.outputs.account }}:role/gha-tdp-e2e-${{ steps.env.outputs.cf_env }}
role-session-name: e2e-codebuild
role-skip-session-tagging: true
Comment thread
darwinboersma marked this conversation as resolved.

- name: Upload source to S3
id: source
env:
COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
REPO_NAME: ${{ github.repository }}
SOURCE_BUCKET: ${{ steps.env.outputs.source_bucket }}
run: |
set -euo pipefail
S3_KEY="${REPO_NAME}/${COMMIT_SHA}.zip"
zip -r -q /tmp/source.zip . -x '.git/*'
aws s3 cp /tmp/source.zip "s3://${SOURCE_BUCKET}/${S3_KEY}"
echo "s3_location=${SOURCE_BUCKET}/${S3_KEY}" >> "$GITHUB_OUTPUT"

- name: Start CodeBuild
id: codebuild
env:
TARGET_ENV: ${{ inputs.environment }}
JFROG_URL: ${{ secrets.JFROG_ARTIFACTORY_NPM_VIRTUAL_URL }}
JFROG_AUTH: ${{ secrets.JFROG_ARTIFACTORY_READ_NPM_AUTH }}
S3_LOCATION: ${{ steps.source.outputs.s3_location }}
BUILDSPEC_PATH: ${{ inputs.buildspec }}
IMAGE_OVERRIDE: ${{ inputs.image_override }}
COMPUTE_TYPE_OVERRIDE: ${{ inputs.compute_type_override }}
run: |
set -euo pipefail

ENV_VARS=$(jq -nc '[
{"name":"E2E_ENVIRONMENT","value":env.TARGET_ENV},
{"name":"JFROG_ARTIFACTORY_URL","value":env.JFROG_URL},
{"name":"JFROG_ARTIFACTORY_AUTH","value":env.JFROG_AUTH}
Comment thread
darwinboersma marked this conversation as resolved.
]')
Comment thread
darwinboersma marked this conversation as resolved.

CMD=(aws codebuild start-build
--project-name tdp-e2e
Comment thread
darwinboersma marked this conversation as resolved.
--source-type-override S3
Comment thread
darwinboersma marked this conversation as resolved.
--source-location-override "$S3_LOCATION"
--buildspec-override "$BUILDSPEC_PATH"
--environment-variables-override "$ENV_VARS")

if [[ -n "$IMAGE_OVERRIDE" ]]; then
CMD+=(--image-override "$IMAGE_OVERRIDE")
fi
if [[ -n "$COMPUTE_TYPE_OVERRIDE" ]]; then
CMD+=(--compute-type-override "$COMPUTE_TYPE_OVERRIDE")
fi

startBuild=$("${CMD[@]}")
buildId=$(echo "$startBuild" | jq -r '.build.id')
if [[ -z "$buildId" || "$buildId" == "null" ]]; then
echo "::error::Failed to start CodeBuild — no build ID returned"
exit 1
fi
echo "build_id=$buildId" >> "$GITHUB_OUTPUT"
echo "Started CodeBuild (tdp-e2e): $buildId"

- name: Wait for CodeBuild
id: wait
env:
BUILD_ID: ${{ steps.codebuild.outputs.build_id }}
TIMEOUT: ${{ inputs.timeout_minutes }}
run: |
set -euo pipefail
MAX_SECONDS=$((TIMEOUT * 60))
buildStatus="IN_PROGRESS"
while [[ $SECONDS -le $MAX_SECONDS ]]; do
sleep 15
buildInfo=$(aws codebuild batch-get-builds --ids "$BUILD_ID")
buildStatus=$(echo "$buildInfo" | jq -r '.builds[0].buildStatus')
if [[ -z "$buildStatus" || "$buildStatus" == "null" ]]; then
echo "::warning::Failed to get build status, retrying..."
continue
fi
if [[ "$buildStatus" != "IN_PROGRESS" ]]; then
break
fi
done

LOG_GROUP=$(echo "$buildInfo" | jq -r '.builds[0].logs.groupName')
LOG_STREAM=$(echo "$buildInfo" | jq -r '.builds[0].logs.streamName')
LOG_LINK=$(echo "$buildInfo" | jq -r '.builds[0].logs.deepLink')

echo "log_group=$LOG_GROUP" >> "$GITHUB_OUTPUT"
echo "log_stream=$LOG_STREAM" >> "$GITHUB_OUTPUT"
echo "log_link=$LOG_LINK" >> "$GITHUB_OUTPUT"
echo "build_status=$buildStatus" >> "$GITHUB_OUTPUT"

if [[ $SECONDS -gt $MAX_SECONDS ]]; then
echo "::error::Timeout exceeded ($TIMEOUT min); stopping CodeBuild build $BUILD_ID"
aws codebuild stop-build --id "$BUILD_ID" 2>/dev/null || true
exit 1
fi

- name: Fetch CodeBuild logs
if: always() && steps.wait.outputs.log_stream != ''
env:
LOG_GROUP: ${{ steps.wait.outputs.log_group }}
LOG_STREAM: ${{ steps.wait.outputs.log_stream }}
run: |
set -euo pipefail
echo "::group::CodeBuild output"
aws logs get-log-events \
--log-group-name "$LOG_GROUP" \
--log-stream-name "$LOG_STREAM" \
--start-from-head \
--query 'events[].message' \
--output text 2>/dev/null || echo "(failed to fetch logs)"
echo "::endgroup::"

- name: Write job summary
if: always() && steps.wait.outputs.build_status != ''
env:
BUILD_STATUS: ${{ steps.wait.outputs.build_status }}
LOG_LINK: ${{ steps.wait.outputs.log_link }}
BUILD_ID: ${{ steps.codebuild.outputs.build_id }}
ENVIRONMENT: ${{ inputs.environment }}
run: |
if [[ "$BUILD_STATUS" == "SUCCEEDED" ]]; then
ICON="white_check_mark"
else
ICON="x"
fi

cat >> "$GITHUB_STEP_SUMMARY" <<EOF
## :${ICON}: E2E Tests — ${BUILD_STATUS}

| | |
|---|---|
| **Environment** | \`${ENVIRONMENT}\` |
| **Build ID** | \`${BUILD_ID}\` |
| **Status** | ${BUILD_STATUS} |
| **CloudWatch Logs** | [View logs](${LOG_LINK}) |
EOF

- name: Check result
if: always()
env:
BUILD_STATUS: ${{ steps.wait.outputs.build_status }}
LOG_LINK: ${{ steps.wait.outputs.log_link }}
run: |
if [[ "$BUILD_STATUS" == "SUCCEEDED" ]]; then
echo "E2E tests passed"
else
echo "::error::E2E tests failed — status: $BUILD_STATUS"
echo "::error::CloudWatch logs: $LOG_LINK"
exit 1
fi
Loading
Loading