diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 0a374e06..acf9653d 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -150,9 +150,15 @@ jobs: - name: Compute image tags id: tags + env: + PR_HEAD_REF: ${{ github.head_ref }} run: | SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) DEFAULT_TAG="sha-${SHORT_SHA}" + BRANCH_NAME="${{ github.ref_name }}" + if [ "${{ github.event_name }}" = "pull_request" ]; then + BRANCH_NAME="${PR_HEAD_REF}" + fi if [ -n "${{ inputs.image_tag_override }}" ]; then DEFAULT_TAG="${{ inputs.image_tag_override }}" elif [ "${{ github.event_name }}" = "pull_request" ]; then @@ -189,11 +195,11 @@ jobs: echo "$sanitized" } - SANITIZED_BRANCH=$(sanitize_tag "${{ github.ref_name }}" 128) + SANITIZED_BRANCH=$(sanitize_tag "${BRANCH_NAME}" 128) BRANCH_TAG="${SANITIZED_BRANCH}" BRANCH_SHA_TAG="${SANITIZED_BRANCH}-$(sanitize_tag "${SHORT_SHA}" 7)" if [ "${#SANITIZED_BRANCH}" -gt 120 ]; then - SANITIZED_BRANCH=$(sanitize_tag "${{ github.ref_name }}" 120) + SANITIZED_BRANCH=$(sanitize_tag "${BRANCH_NAME}" 120) BRANCH_SHA_TAG="${SANITIZED_BRANCH}-${SHORT_SHA}" fi @@ -201,7 +207,10 @@ jobs: TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${DEFAULT_TAG}") TAGS+=("${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${DEFAULT_TAG}") - if [ "${{ github.event_name }}" != "pull_request" ]; then + if [ "${{ github.event_name }}" = "pull_request" ]; then + TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${BRANCH_TAG}") + TAGS+=("${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${BRANCH_TAG}") + else TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${BRANCH_SHA_TAG}") TAGS+=("${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${BRANCH_SHA_TAG}") @@ -269,6 +278,7 @@ jobs: with: context: . file: ./Dockerfile + platforms: linux/amd64,linux/arm64 push: ${{ steps.image-policy.outputs.push == 'true' }} load: ${{ steps.image-policy.outputs.push != 'true' }} tags: ${{ steps.tags.outputs.tags }} @@ -566,8 +576,9 @@ jobs: - name: Download E2E coverage artifact uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 with: - name: e2e-coverage - path: coverage/e2e + pattern: e2e-coverage-* + path: coverage/e2e-shards + merge-multiple: false - name: Upload coverage to Codecov uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 @@ -590,7 +601,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} flags: e2e - files: coverage/e2e/lcov.info + files: coverage/e2e-shards/**/lcov.info fail_ci_if_error: false codecov-gate: diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index e6d522a6..c9d48503 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -22,6 +22,8 @@ name: Docker Build, Publish & Test on: workflow_dispatch: + pull_request: + push: concurrency: group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.head_ref || github.ref_name }} @@ -138,10 +140,14 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Compute branch tags - if: steps.skip.outputs.skip_build != 'true' && env.TRIGGER_EVENT != 'pull_request' + if: steps.skip.outputs.skip_build != 'true' id: branch-tags run: | - BRANCH_NAME="${TRIGGER_REF#refs/heads/}" + if [[ "$TRIGGER_EVENT" == "pull_request" ]]; then + BRANCH_NAME="${TRIGGER_HEAD_REF}" + else + BRANCH_NAME="${TRIGGER_REF#refs/heads/}" + fi SHORT_SHA="$(echo "${{ env.TRIGGER_HEAD_SHA }}" | cut -c1-7)" sanitize_tag() { @@ -174,11 +180,17 @@ jobs: BASE_BRANCH=$(sanitize_tag "${BRANCH_NAME}" 120) BRANCH_SHA_TAG="${BASE_BRANCH}-${SHORT_SHA}" - echo "branch_sha_tag=${BRANCH_SHA_TAG}" >> "$GITHUB_OUTPUT" + if [[ "$TRIGGER_EVENT" == "pull_request" ]]; then + if [[ "$BRANCH_NAME" == feature/* ]]; then + echo "pr_feature_branch_sha_tag=${BRANCH_SHA_TAG}" >> "$GITHUB_OUTPUT" + fi + else + echo "branch_sha_tag=${BRANCH_SHA_TAG}" >> "$GITHUB_OUTPUT" - if [[ "$TRIGGER_REF" == refs/heads/feature/* ]]; then - echo "feature_branch_tag=${SANITIZED_BRANCH}" >> "$GITHUB_OUTPUT" - echo "feature_branch_sha_tag=${BRANCH_SHA_TAG}" >> "$GITHUB_OUTPUT" + if [[ "$TRIGGER_REF" == refs/heads/feature/* ]]; then + echo "feature_branch_tag=${SANITIZED_BRANCH}" >> "$GITHUB_OUTPUT" + echo "feature_branch_sha_tag=${BRANCH_SHA_TAG}" >> "$GITHUB_OUTPUT" + fi fi - name: Generate Docker metadata @@ -195,6 +207,7 @@ jobs: type=raw,value=latest,enable=${{ env.TRIGGER_REF == 'refs/heads/main' }} type=raw,value=dev,enable=${{ env.TRIGGER_REF == 'refs/heads/development' }} type=raw,value=nightly,enable=${{ env.TRIGGER_REF == 'refs/heads/nightly' }} + type=raw,value=${{ steps.branch-tags.outputs.pr_feature_branch_sha_tag }},enable=${{ env.TRIGGER_EVENT == 'pull_request' && steps.branch-tags.outputs.pr_feature_branch_sha_tag != '' }} type=raw,value=${{ steps.branch-tags.outputs.feature_branch_tag }},enable=${{ env.TRIGGER_EVENT != 'pull_request' && startsWith(env.TRIGGER_REF, 'refs/heads/feature/') && steps.branch-tags.outputs.feature_branch_tag != '' }} type=raw,value=${{ steps.branch-tags.outputs.branch_sha_tag }},enable=${{ env.TRIGGER_EVENT != 'pull_request' && steps.branch-tags.outputs.branch_sha_tag != '' }} type=raw,value=pr-${{ env.TRIGGER_PR_NUMBER }}-{{sha}},enable=${{ env.TRIGGER_EVENT == 'pull_request' }},prefix=,suffix= @@ -207,7 +220,7 @@ jobs: io.charon.build.timestamp=${{ github.event.repository.updated_at }} io.charon.feature.branch=${{ steps.branch-tags.outputs.feature_branch_tag }} # Phase 1 Optimization: Build once, test many - # - For PRs: Single-platform (amd64) + immutable tags (pr-{number}-{short-sha}) + # - For PRs: Multi-platform (amd64, arm64) + immutable tags (pr-{number}-{short-sha}) # - For feature branches: Multi-platform (amd64, arm64) + sanitized tags ({branch}-{short-sha}) # - For main/dev: Multi-platform (amd64, arm64) for production # - Always push to registry (enables downstream workflow consumption) @@ -227,7 +240,8 @@ jobs: set -euo pipefail echo "🔨 Building Docker image with retry logic..." - echo "Platform: ${{ env.TRIGGER_EVENT == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}" + PLATFORMS="linux/amd64,linux/arm64" + echo "Platform: ${PLATFORMS}" # Build tag arguments array from metadata output (properly quoted) TAG_ARGS_ARRAY=() @@ -244,7 +258,7 @@ jobs: # Build the complete command as an array (handles spaces in label values correctly) BUILD_CMD=( docker buildx build - --platform "${{ env.TRIGGER_EVENT == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}" + --platform "${PLATFORMS}" --push "${TAG_ARGS_ARRAY[@]}" "${LABEL_ARGS_ARRAY[@]}" diff --git a/.github/workflows/security-pr.yml b/.github/workflows/security-pr.yml index b0732348..2603c074 100644 --- a/.github/workflows/security-pr.yml +++ b/.github/workflows/security-pr.yml @@ -10,6 +10,11 @@ on: description: 'PR number to scan (optional)' required: false type: string + workflow_run: + workflows: + - Docker Build, Publish & Test + types: + - completed concurrency: group: security-pr-${{ github.event.workflow_run.event || github.event_name }}-${{ github.event.workflow_run.head_branch || github.ref }} diff --git a/.github/workflows/supply-chain-pr.yml b/.github/workflows/supply-chain-pr.yml index e3d94dc1..6be33630 100644 --- a/.github/workflows/supply-chain-pr.yml +++ b/.github/workflows/supply-chain-pr.yml @@ -9,6 +9,11 @@ on: description: "PR number to verify (optional, will auto-detect from workflow_run)" required: false type: string + workflow_run: + workflows: + - Docker Build, Publish & Test + types: + - completed concurrency: group: supply-chain-pr-${{ github.event.workflow_run.event || github.event_name }}-${{ github.event.workflow_run.head_branch || github.ref }} diff --git a/.github/workflows/waf-integration.yml b/.github/workflows/waf-integration.yml index 3d862d50..668cba33 100644 --- a/.github/workflows/waf-integration.yml +++ b/.github/workflows/waf-integration.yml @@ -3,6 +3,9 @@ name: WAF integration # Phase 2-3: Build Once, Test Many - Use registry image instead of building # This workflow now waits for docker-build.yml to complete and pulls the built image on: + workflow_run: + workflows: ["Docker Build, Publish & Test"] + types: [completed] workflow_dispatch: inputs: image_tag: