From d109ba20d21cc9fb09eca354961ee126d1c187ee Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Fri, 24 Apr 2026 13:40:08 +0530 Subject: [PATCH 01/16] Enhance CI workflows for staging and validation of Lambda layers --- .github/scripts/poll-validation.sh | 121 ++++++++++++++++++++++++ .github/workflows/publish-dotnet.yml | 60 +++++++++++- .github/workflows/publish-extension.yml | 81 +++++++++++++++- .github/workflows/publish-java.yml | 77 ++++++++++++++- .github/workflows/publish-node.yml | 106 +++++++++++++++++++-- .github/workflows/publish-python.yml | 58 ++++++++++++ .github/workflows/publish-ruby.yml | 67 +++++++++++-- Makefile | 29 ++++++ dotnet/publish-layers.sh | 28 +++++- extension/publish-layer.sh | 36 +++++-- java/publish-layers.sh | 16 +++- libBuild.sh | 51 ++++++++++ nodejs/publish-layers.sh | 56 ++++++++++- python/publish-layers.sh | 16 +++- ruby/publish-layers.sh | 16 +++- 15 files changed, 782 insertions(+), 36 deletions(-) create mode 100755 .github/scripts/poll-validation.sh diff --git a/.github/scripts/poll-validation.sh b/.github/scripts/poll-validation.sh new file mode 100755 index 00000000..3f5a3c02 --- /dev/null +++ b/.github/scripts/poll-validation.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# Call the orchestrator API then poll S3 until the validation summary appears. +# Exit 0 = PASS, exit 1 = FAIL or timeout. +# +# Required env vars: +# ORCHESTRATOR_API_URL full URL of the /validate endpoint +# ORCHESTRATOR_API_KEY x-api-key value +# RESULTS_BUCKET S3 bucket where the orchestrator writes summary.json +# +# Usage: +# poll-validation.sh --runtime \ +# --arn-x86 --arn-arm64 \ +# [--arn-x86-slim ] [--arn-arm64-slim ] \ +# [--os al2|al2023] \ +# [--run-id ] [--timeout ] + +set -euo pipefail + +RUNTIME="" +ARN_X86="" +ARN_ARM64="" +ARN_X86_SLIM="" +ARN_ARM64_SLIM="" +OS_TIER="" +RUN_ID="" +TIMEOUT=${VALIDATION_TIMEOUT_S:-600} + +while [[ $# -gt 0 ]]; do + case $1 in + --runtime) RUNTIME=$2; shift 2 ;; + --arn-x86) ARN_X86=$2; shift 2 ;; + --arn-arm64) ARN_ARM64=$2; shift 2 ;; + --arn-x86-slim) ARN_X86_SLIM=$2; shift 2 ;; + --arn-arm64-slim) ARN_ARM64_SLIM=$2; shift 2 ;; + --os) OS_TIER=$2; shift 2 ;; + --run-id) RUN_ID=$2; shift 2 ;; + --timeout) TIMEOUT=$2; shift 2 ;; + *) echo "Unknown argument: $1"; exit 1 ;; + esac +done + +[[ -z "$RUNTIME" ]] && { echo "ERROR: --runtime is required"; exit 1; } +[[ -z "$ARN_X86" ]] && { echo "ERROR: --arn-x86 is required"; exit 1; } +[[ -z "$ARN_ARM64" ]] && { echo "ERROR: --arn-arm64 is required"; exit 1; } +[[ -z "${ORCHESTRATOR_API_URL:-}" ]] && { echo "ERROR: ORCHESTRATOR_API_URL is not set"; exit 1; } +[[ -z "${ORCHESTRATOR_API_KEY:-}" ]] && { echo "ERROR: ORCHESTRATOR_API_KEY is not set"; exit 1; } +[[ -z "${RESULTS_BUCKET:-}" ]] && { echo "ERROR: RESULTS_BUCKET is not set"; exit 1; } + +if [[ -z "$RUN_ID" ]]; then + RUN_ID="gh-${GITHUB_RUN_ID:-$(date +%s)}-${RUNTIME}" +fi + +# Build JSON payload +PAYLOAD=$(python3 - </dev/null || true) + + if [[ -n "$result" ]]; then + echo "" + echo "=== Validation result for ${RUNTIME} ===" + echo "$result" | python3 -m json.tool + + final_status=$(echo "$result" | python3 -c "import json,sys; print(json.load(sys.stdin)['status'])") + + if [[ "$final_status" == "PASS" ]]; then + echo "PASSED: ${RUNTIME}" + exit 0 + else + echo "FAILED: ${RUNTIME}" + echo "$result" | python3 - <<'PYEOF' +import json, sys +d = json.load(sys.stdin) +for variant in ("standard", "slim"): + section = d.get(variant) or {} + for f in section.get("functions", []): + if f["status"] != "PASS": + print(f" [{variant}] FAIL: {f['function']} — {f['reason']} (error_type={f.get('error_type','?')})") +PYEOF + exit 1 + fi + fi + + remaining=$((end - SECONDS)) + echo " Waiting... (${remaining}s remaining)" + sleep 15 +done + +echo "ERROR: Timeout after ${TIMEOUT}s — no result at s3://${RESULTS_BUCKET}/${KEY}" +exit 1 diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index 41fedbc3..14542971 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -9,17 +9,50 @@ jobs: publish-dotnet: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 + - name: Check Tag id: dotnet-check-tag run: | if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_dotnet ]]; then echo "match=true" >> $GITHUB_OUTPUT fi - - uses: actions/checkout@v4 + - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: platforms: arm64, amd64 + + # ── 1. Publish staging ─────────────────────────────────────────────── + - name: Publish staging layers + if: steps.dotnet-check-tag.outputs.match == 'true' + id: staging + env: + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + run: | + cd dotnet + ./publish-layers.sh publish-staging-dotnet + + # ── 2. Validate ────────────────────────────────────────────────────── + - name: Validate staging layers + if: steps.dotnet-check-tag.outputs.match == 'true' + id: validate + env: + ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} + ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} + RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + run: | + chmod +x .github/scripts/poll-validation.sh + .github/scripts/poll-validation.sh \ + --runtime dotnet10 \ + --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ + --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" + + # ── 3. Release publish ─────────────────────────────────────────────── - name: Publish Dotnet Layer if: steps.dotnet-check-tag.outputs.match == 'true' env: @@ -29,3 +62,28 @@ jobs: run: | cd dotnet ./publish-layers.sh + + # ── 4. Cleanup staging (always) ────────────────────────────────────── + - name: Cleanup staging layers + if: always() && steps.staging.outputs.arn_x86 != '' + env: + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + ARN_X86: ${{ steps.staging.outputs.arn_x86 }} + ARN_ARM64: ${{ steps.staging.outputs.arn_arm64 }} + run: | + cd dotnet + ./publish-layers.sh cleanup-staging-dotnet + + # ── 5. Slack notification ──────────────────────────────────────────── + - name: Notify Slack + if: always() && steps.dotnet-check-tag.outputs.match == 'true' && steps.staging.outputs.arn_x86 != '' + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ steps.validate.outcome == 'success' && ':white_check_mark:' || ':x:' }} *dotnet10* layer validation ${{ steps.validate.outcome }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + } diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index 19785d0d..b2a5ca98 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -8,24 +8,74 @@ on: jobs: publish-extension: runs-on: ubuntu-latest - strategy: - matrix: - python-version: [ 3.12 ] steps: - uses: actions/checkout@v4 + - name: Check Tag id: extension-check-tag run: | if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_extension$ ]]; then echo "match=true" >> $GITHUB_OUTPUT fi + - name: Install publish dependencies if: steps.extension-check-tag.outputs.match == 'true' run: pip install -U awscli + - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: platforms: arm64, amd64 + + # ── 1. Publish staging ─────────────────────────────────────────────── + - name: Publish staging layers + if: steps.extension-check-tag.outputs.match == 'true' + id: staging + env: + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + run: | + cd extension + ./publish-layer.sh publish-staging + + # ── 2. Validate go/al2 ─────────────────────────────────────────────── + - name: Validate staging layers (go/al2) + if: steps.extension-check-tag.outputs.match == 'true' + id: validate_al2 + env: + ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} + ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} + RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + run: | + chmod +x .github/scripts/poll-validation.sh + .github/scripts/poll-validation.sh \ + --runtime go \ + --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ + --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" \ + --os al2 + + # ── 3. Validate go/al2023 ──────────────────────────────────────────── + - name: Validate staging layers (go/al2023) + if: steps.extension-check-tag.outputs.match == 'true' + id: validate_al2023 + env: + ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} + ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} + RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + run: | + chmod +x .github/scripts/poll-validation.sh + .github/scripts/poll-validation.sh \ + --runtime go \ + --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ + --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" \ + --os al2023 + + # ── 4. Release publish ─────────────────────────────────────────────── - name: Publish extension layer if: steps.extension-check-tag.outputs.match == 'true' env: @@ -34,3 +84,28 @@ jobs: run: | cd extension ./publish-layer.sh + + # ── 5. Cleanup staging (always) ────────────────────────────────────── + - name: Cleanup staging layers + if: always() && steps.staging.outputs.arn_x86 != '' + env: + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + ARN_X86: ${{ steps.staging.outputs.arn_x86 }} + ARN_ARM64: ${{ steps.staging.outputs.arn_arm64 }} + run: | + cd extension + ./publish-layer.sh cleanup-staging + + # ── 6. Slack notification ──────────────────────────────────────────── + - name: Notify Slack + if: always() && steps.extension-check-tag.outputs.match == 'true' && steps.staging.outputs.arn_x86 != '' + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ (steps.validate_al2.outcome == 'success' && steps.validate_al2023.outcome == 'success') && ':white_check_mark:' || ':x:' }} *extension (go/al2+al2023)* layer validation al2=${{ steps.validate_al2.outcome }} al2023=${{ steps.validate_al2023.outcome }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + } diff --git a/.github/workflows/publish-java.yml b/.github/workflows/publish-java.yml index 91d63560..34c11ecc 100644 --- a/.github/workflows/publish-java.yml +++ b/.github/workflows/publish-java.yml @@ -14,6 +14,7 @@ jobs: steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # pin@v4 + - name: Set up Java version run: | declare -A map_java_version @@ -25,27 +26,76 @@ jobs: ) java_numeric_version=${map_java_version[${{ matrix.java-version }}]} echo "JAVA_NUMERIC_VERSION=$java_numeric_version" >> $GITHUB_ENV + - name: Use Java ${{ env.JAVA_NUMERIC_VERSION }} uses: actions/setup-java@v4 with: distribution: 'corretto' java-version: ${{ env.JAVA_NUMERIC_VERSION }} + - name: Check Tag id: java-check-tag run: | if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_java$ ]]; then echo "match=true" >> $GITHUB_OUTPUT fi + + # ── 1. Build (java21 only) ──────────────────────────────────────────── + # Docker build + extract zips to dist/java21/ — reused by release step. + - name: Build layer artifacts + if: steps.java-check-tag.outputs.match == 'true' && matrix.java-version == 'java21' + run: make extract-java21-artifacts + + # ── 2. Publish staging (java21 only) ───────────────────────────────── + - name: Publish staging layers + if: steps.java-check-tag.outputs.match == 'true' && matrix.java-version == 'java21' + id: staging + env: + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + run: | + cd java + DIST_DIR=../dist/java21 \ + ./publish-layers.sh publish-staging-java21 + + # ── 3. Validate (java21 only) ───────────────────────────────────────── + - name: Validate staging layers + if: steps.java-check-tag.outputs.match == 'true' && matrix.java-version == 'java21' + id: validate + env: + ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} + ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} + RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + run: | + chmod +x .github/scripts/poll-validation.sh + .github/scripts/poll-validation.sh \ + --runtime java21 \ + --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ + --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" + + # ── 4. Release publish ─────────────────────────────────────────────── + # java21: reuse Docker image (already built). Others: build+publish via make. - name: Publish ${{ matrix.java-version }} layer if: steps.java-check-tag.outputs.match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - run: make publish-${{ matrix.java-version }}-ci + run: | + if [[ "${{ matrix.java-version }}" == "java21" ]]; then + docker run -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY newrelic-lambda-layers-java21 + else + make publish-${{ matrix.java-version }}-ci + fi + + # ── 5. ECR image ───────────────────────────────────────────────────── - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: platforms: arm64, amd64 + - name: Publish ECR image for ${{ matrix.java-version }} if: steps.java-check-tag.outputs.match == 'true' env: @@ -54,3 +104,28 @@ jobs: run: | cd java ./publish-layers.sh build-publish-${{ matrix.java-version }}-ecr-image + + # ── 6. Slack notification (java21 only) ────────────────────────────── + - name: Notify Slack + if: always() && steps.java-check-tag.outputs.match == 'true' && matrix.java-version == 'java21' && steps.staging.outputs.arn_x86 != '' + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ steps.validate.outcome == 'success' && ':white_check_mark:' || ':x:' }} *java21* layer validation ${{ steps.validate.outcome }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + } + + # ── 7. Cleanup staging (java21 only, always) ───────────────────────── + - name: Cleanup staging layers + if: always() && matrix.java-version == 'java21' && steps.staging.outputs.arn_x86 != '' + env: + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + ARN_X86: ${{ steps.staging.outputs.arn_x86 }} + ARN_ARM64: ${{ steps.staging.outputs.arn_arm64 }} + run: | + cd java + ./publish-layers.sh cleanup-staging-java21 diff --git a/.github/workflows/publish-node.yml b/.github/workflows/publish-node.yml index 974ad377..d902d18f 100644 --- a/.github/workflows/publish-node.yml +++ b/.github/workflows/publish-node.yml @@ -11,19 +11,75 @@ jobs: strategy: matrix: node-version: [20, 22, 24] + steps: - uses: actions/checkout@v4 + - name: Setup id: setup uses: ./.github/actions/node-layer-setup with: node-version: ${{ matrix.node-version }} - - name: Publish layer + + # ── 1. Build ───────────────────────────────────────────────────────── + # Build the Docker image and extract the layer zips to dist/nodejs/. + # The same Docker image is reused for the release publish step (docker + # run without rebuild), so the --no-cache build cost is paid only once. + - name: Build layer artifacts + if: steps.setup.outputs.tag-match == 'true' + run: make extract-nodejs${{ matrix.node-version }}-artifacts + + # ── 2. Staging publish ─────────────────────────────────────────────── + # Publish to us-east-1 only with a -staging/-slim-staging suffix. + # Layers land in the staging account (466768951184); no public perms. + - name: Publish staging layers + if: steps.setup.outputs.tag-match == 'true' + id: staging + env: + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + run: | + cd nodejs + DIST_DIR=../dist/nodejs${{ matrix.node-version }} \ + ./publish-layers.sh publish-staging-${{ matrix.node-version }} + + # ── 3. Validate ────────────────────────────────────────────────────── + # Call the orchestrator with staging ARNs and poll S3 until the result + # summary appears. Fails the job if any test function fails. + - name: Validate staging layers + if: steps.setup.outputs.tag-match == 'true' + id: validate + env: + ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} + ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} + RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + run: | + chmod +x .github/scripts/poll-validation.sh + .github/scripts/poll-validation.sh \ + --runtime nodejs${{ matrix.node-version }} \ + --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ + --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" \ + --arn-x86-slim "${{ steps.staging.outputs.arn_x86_slim }}" \ + --arn-arm64-slim "${{ steps.staging.outputs.arn_arm64_slim }}" + + # ── 4. Release publish ─────────────────────────────────────────────── + # Only runs if validation passed. Reuses the already-built Docker image + # and publishes to all production regions with public permissions. + - name: Publish release layers if: steps.setup.outputs.tag-match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - run: make publish-nodejs${{ matrix.node-version }}-ci + run: | + docker run \ + -e AWS_ACCESS_KEY_ID \ + -e AWS_SECRET_ACCESS_KEY \ + newrelic-lambda-layers-nodejs${{ matrix.node-version }} + + # ── 5. ECR image ───────────────────────────────────────────────────── - name: Publish ECR image for nodejs${{ matrix.node-version }} if: steps.setup.outputs.tag-match == 'true' env: @@ -32,14 +88,44 @@ jobs: run: | cd nodejs ./publish-layers.sh build-publish-${{ matrix.node-version }}-ecr-image + + # ── 6. Cleanup staging ─────────────────────────────────────────────── + # Always runs (even if validate or release failed) so staging layers + # are never left stranded in the staging account. + - name: Cleanup staging layers + if: always() && steps.staging.outputs.arn_x86 != '' + env: + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + ARN_X86: ${{ steps.staging.outputs.arn_x86 }} + ARN_ARM64: ${{ steps.staging.outputs.arn_arm64 }} + ARN_X86_SLIM: ${{ steps.staging.outputs.arn_x86_slim }} + ARN_ARM64_SLIM: ${{ steps.staging.outputs.arn_arm64_slim }} + run: | + cd nodejs + ./publish-layers.sh cleanup-staging-${{ matrix.node-version }} + + # ── 7. Slack notification ──────────────────────────────────────────── + - name: Notify Slack + if: always() && steps.setup.outputs.tag-match == 'true' && steps.staging.outputs.arn_x86 != '' + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ steps.validate.outcome == 'success' && ':white_check_mark:' || ':x:' }} *nodejs${{ matrix.node-version }}* layer validation ${{ steps.validate.outcome }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + } + - name: Upload Unit Test Coverage if: steps.setup.outputs.tag-match == 'true' uses: codecov/codecov-action@v5.3.1 with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./nodejs/coverage/unit/lcov.info - fail_ci_if_error: true - flags: unit-tests + token: ${{ secrets.CODECOV_TOKEN }} + files: ./nodejs/coverage/unit/lcov.info + fail_ci_if_error: true + flags: unit-tests publish-node-universal: runs-on: ubuntu-latest @@ -68,7 +154,7 @@ jobs: if: steps.setup.outputs.tag-match == 'true' uses: codecov/codecov-action@v5.3.1 with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./nodejs/coverage/unit/lcov.info - fail_ci_if_error: true - flags: unit-tests + token: ${{ secrets.CODECOV_TOKEN }} + files: ./nodejs/coverage/unit/lcov.info + fail_ci_if_error: true + flags: unit-tests diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index 886c8cb7..cc6e9a4b 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -13,9 +13,42 @@ jobs: python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13', '3.14' ] steps: - uses: actions/checkout@v4 + - name: Setup id: setup uses: ./.github/actions/python-layer-setup + + # ── 1+2. Build + Publish staging (python3.14 only) ─────────────────── + # Builds inline (native pip), publishes to us-east-1 with -staging suffix. + - name: Publish staging layers + if: steps.setup.outputs.tag-match == 'true' && matrix.python-version == '3.14' + id: staging + env: + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + run: | + cd python + ./publish-layers.sh publish-staging-python${{ matrix.python-version }} + + # ── 3. Validate (python3.14 only) ──────────────────────────────────── + - name: Validate staging layers + if: steps.setup.outputs.tag-match == 'true' && matrix.python-version == '3.14' + id: validate + env: + ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} + ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} + RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + run: | + chmod +x .github/scripts/poll-validation.sh + .github/scripts/poll-validation.sh \ + --runtime python314 \ + --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ + --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" + + # ── 4. Release publish ─────────────────────────────────────────────── - name: Publish Python ${{ matrix.python-version }} layer if: steps.setup.outputs.tag-match == 'true' env: @@ -25,6 +58,31 @@ jobs: cd python ./publish-layers.sh python${{ matrix.python-version }} + # ── 5. Slack notification (python3.14 only) ────────────────────────── + - name: Notify Slack + if: always() && steps.setup.outputs.tag-match == 'true' && matrix.python-version == '3.14' && steps.staging.outputs.arn_x86 != '' + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ steps.validate.outcome == 'success' && ':white_check_mark:' || ':x:' }} *python3.14* layer validation ${{ steps.validate.outcome }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + } + + # ── 6. Cleanup staging (python3.14 only, always) ───────────────────── + - name: Cleanup staging layers + if: always() && matrix.python-version == '3.14' && steps.staging.outputs.arn_x86 != '' + env: + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + ARN_X86: ${{ steps.staging.outputs.arn_x86 }} + ARN_ARM64: ${{ steps.staging.outputs.arn_arm64 }} + run: | + cd python + ./publish-layers.sh cleanup-staging-python${{ matrix.python-version }} + publish-python-universal: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index 7881259b..bf4490c9 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -13,32 +13,62 @@ jobs: ruby-version: [3.2, 3.3, 3.4] steps: - uses: actions/checkout@v4.2.2 + - name: Use Ruby ${{ matrix.ruby-version }} uses: ruby/setup-ruby@v1.226.0 with: ruby-version: ${{ matrix.ruby-version }} + - name: Check Tag id: ruby-check-tag run: | if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_ruby$ ]]; then echo "match=true" >> $GITHUB_OUTPUT fi + - name: Clean the workspace run: ./bin/clean working-directory: ruby + - name: Install Ruby Dependencies run: bundle working-directory: ruby - - name: Obtain Ruby version without the dot - id: ruby-version-without-dot - run: >- - echo "::set-output name=VERSION::$( - echo ${{ matrix.ruby-version }} | sed 's/\.//' - )" + - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: platforms: arm64, amd64 + + # ── 1+2. Build + Publish staging (ruby3.4 only) ─────────────────────── + - name: Publish staging layers + if: steps.ruby-check-tag.outputs.match == 'true' && matrix.ruby-version == '3.4' + id: staging + env: + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + run: | + cd ruby + ./publish-layers.sh publish-staging-ruby${{ matrix.ruby-version }} + + # ── 3. Validate (ruby3.4 only) ──────────────────────────────────────── + - name: Validate staging layers + if: steps.ruby-check-tag.outputs.match == 'true' && matrix.ruby-version == '3.4' + id: validate + env: + ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} + ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} + RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + run: | + chmod +x .github/scripts/poll-validation.sh + .github/scripts/poll-validation.sh \ + --runtime ruby34 \ + --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ + --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" + + # ── 4. Release publish ──────────────────────────────────────────────── - name: Build and Publish layer if: steps.ruby-check-tag.outputs.match == 'true' env: @@ -46,3 +76,28 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: ./publish-layers.sh ruby${{ matrix.ruby-version }} working-directory: ruby + + # ── 5. Slack notification (ruby3.4 only) ───────────────────────────── + - name: Notify Slack + if: always() && steps.ruby-check-tag.outputs.match == 'true' && matrix.ruby-version == '3.4' && steps.staging.outputs.arn_x86 != '' + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ steps.validate.outcome == 'success' && ':white_check_mark:' || ':x:' }} *ruby3.4* layer validation ${{ steps.validate.outcome }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + } + + # ── 6. Cleanup staging (ruby3.4 only, always) ──────────────────────── + - name: Cleanup staging layers + if: always() && matrix.ruby-version == '3.4' && steps.staging.outputs.arn_x86 != '' + env: + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + ARN_X86: ${{ steps.staging.outputs.arn_x86 }} + ARN_ARM64: ${{ steps.staging.outputs.arn_arm64 }} + run: | + cd ruby + ./publish-layers.sh cleanup-staging-ruby${{ matrix.ruby-version }} diff --git a/Makefile b/Makefile index 4841d253..da9101d4 100644 --- a/Makefile +++ b/Makefile @@ -74,6 +74,13 @@ publish-java21-local: build-java21 -v "${HOME}/.aws:/home/newrelic-lambda-layers/.aws" \ newrelic-lambda-layers-java21 +extract-java21-artifacts: build-java21 + @docker rm -f java21-artifacts 2>/dev/null || true + mkdir -p dist/java21 + docker create --name java21-artifacts newrelic-lambda-layers-java21 + docker cp java21-artifacts:/home/newrelic-lambda-layers/java/dist/. dist/java21/ + docker rm java21-artifacts + build-nodejs20: docker build \ --no-cache \ @@ -131,6 +138,28 @@ publish-nodejs24-local: build-nodejs24 -v "${HOME}/.aws:/home/newrelic-lambda-layers/.aws" \ newrelic-lambda-layers-nodejs24 +# Extract built zips from the Docker image into dist/nodejs/ for staging publish. +extract-nodejs20-artifacts: build-nodejs20 + @docker rm -f nodejs20-artifacts 2>/dev/null || true + mkdir -p dist/nodejs20 + docker create --name nodejs20-artifacts newrelic-lambda-layers-nodejs20 + docker cp nodejs20-artifacts:/home/newrelic-lambda-layers/nodejs/dist/. dist/nodejs20/ + docker rm nodejs20-artifacts + +extract-nodejs22-artifacts: build-nodejs22 + @docker rm -f nodejs22-artifacts 2>/dev/null || true + mkdir -p dist/nodejs22 + docker create --name nodejs22-artifacts newrelic-lambda-layers-nodejs22 + docker cp nodejs22-artifacts:/home/newrelic-lambda-layers/nodejs/dist/. dist/nodejs22/ + docker rm nodejs22-artifacts + +extract-nodejs24-artifacts: build-nodejs24 + @docker rm -f nodejs24-artifacts 2>/dev/null || true + mkdir -p dist/nodejs24 + docker create --name nodejs24-artifacts newrelic-lambda-layers-nodejs24 + docker cp nodejs24-artifacts:/home/newrelic-lambda-layers/nodejs/dist/. dist/nodejs24/ + docker rm nodejs24-artifacts + build-python-universal: docker build \ --no-cache \ diff --git a/dotnet/publish-layers.sh b/dotnet/publish-layers.sh index 7d62d34e..0d9a1ad5 100755 --- a/dotnet/publish-layers.sh +++ b/dotnet/publish-layers.sh @@ -3,7 +3,7 @@ set -Eeuo pipefail BUILD_DIR=lib # for .net can either be lib or bin. See: https://docs.aws.amazon.com/lambda/latest/dg/packaging-layers.html -DIST_DIR=dist +DIST_DIR=${DIST_DIR:-dist} DOTNET_DIST_ARM64=$DIST_DIR/dotnet.arm64.zip DOTNET_DIST_X86_64=$DIST_DIR/dotnet.x86_64.zip @@ -96,8 +96,26 @@ function get_agent { } -build-dotnet-arm64 -publish-dotnet-arm64 -build-dotnet-x86-64 -publish-dotnet-x86-64 +case "${1:-}" in +"publish-staging-dotnet") + build-dotnet-arm64 + build-dotnet-x86-64 + arn_arm64=$(publish_staging_layer "$DOTNET_DIST_ARM64" dotnet arm64 "$NEWRELIC_AGENT_VERSION") + echo "arn_arm64=${arn_arm64}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + arn_x86=$(publish_staging_layer "$DOTNET_DIST_X86_64" dotnet x86_64 "$NEWRELIC_AGENT_VERSION") + echo "arn_x86=${arn_x86}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + ;; +"cleanup-staging-dotnet") + for arn in "${ARN_X86:-}" "${ARN_ARM64:-}"; do + [[ -z "$arn" ]] && continue + delete_staging_layer "$(echo "$arn" | cut -d: -f8)" "$(echo "$arn" | cut -d: -f9)" + done + ;; +*) + build-dotnet-arm64 + publish-dotnet-arm64 + build-dotnet-x86-64 + publish-dotnet-x86-64 + ;; +esac diff --git a/extension/publish-layer.sh b/extension/publish-layer.sh index 96a400e8..fb5dcf33 100755 --- a/extension/publish-layer.sh +++ b/extension/publish-layer.sh @@ -47,10 +47,34 @@ function publish-layer-arm64 { done } -build-layer-x86 -publish-layer-x86 -publish_docker_ecr $EXTENSION_DIST_ZIP_X86_64 extension x86_64 +function publish-staging { + build-layer-x86 + build-layer-arm64 -build-layer-arm64 -publish-layer-arm64 -publish_docker_ecr $EXTENSION_DIST_ZIP_ARM64 extension arm64 + arn_x86=$(publish_staging_layer "$EXTENSION_DIST_ZIP_X86_64" provided x86_64) + echo "arn_x86=${arn_x86}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + + arn_arm64=$(publish_staging_layer "$EXTENSION_DIST_ZIP_ARM64" provided arm64) + echo "arn_arm64=${arn_arm64}" >> "${GITHUB_OUTPUT:-/dev/stderr}" +} + +function cleanup-staging { + for arn in "${ARN_X86:-}" "${ARN_ARM64:-}"; do + [[ -z "$arn" ]] && continue + delete_staging_layer "$(echo "$arn" | cut -d: -f8)" "$(echo "$arn" | cut -d: -f9)" + done +} + +case "${1:-publish}" in + "publish-staging") publish-staging ;; + "cleanup-staging") cleanup-staging ;; + *) + build-layer-x86 + publish-layer-x86 + publish_docker_ecr $EXTENSION_DIST_ZIP_X86_64 extension x86_64 + + build-layer-arm64 + publish-layer-arm64 + publish_docker_ecr $EXTENSION_DIST_ZIP_ARM64 extension arm64 + ;; +esac diff --git a/java/publish-layers.sh b/java/publish-layers.sh index d5d211a2..5b5fdab6 100755 --- a/java/publish-layers.sh +++ b/java/publish-layers.sh @@ -5,7 +5,7 @@ set -Eeuo pipefail BUILD_DIR=build GRADLE_ARCHIVE=$BUILD_DIR/distributions/NewRelicJavaLayer.zip -DIST_DIR=dist +DIST_DIR=${DIST_DIR:-dist} JAVA8_DIST_ARM64=$DIST_DIR/java8.arm64.zip JAVA8_DIST_X86_64=$DIST_DIR/java8.x86_64.zip JAVA11_DIST_ARM64=$DIST_DIR/java11.arm64.zip @@ -244,6 +244,20 @@ case "$1" in $0 build-java21 $0 publish-java21 ;; +"publish-staging-java21") + build-java21-arm64 + build-java21-x86 + arn_arm64=$(publish_staging_layer "$JAVA21_DIST_ARM64" java21 arm64) + echo "arn_arm64=${arn_arm64}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + arn_x86=$(publish_staging_layer "$JAVA21_DIST_X86_64" java21 x86_64) + echo "arn_x86=${arn_x86}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + ;; +"cleanup-staging-java21") + for arn in "${ARN_X86:-}" "${ARN_ARM64:-}"; do + [[ -z "$arn" ]] && continue + delete_staging_layer "$(echo "$arn" | cut -d: -f8)" "$(echo "$arn" | cut -d: -f9)" + done + ;; *) usage ;; diff --git a/libBuild.sh b/libBuild.sh index 85c85821..e436809b 100644 --- a/libBuild.sh +++ b/libBuild.sh @@ -337,6 +337,57 @@ function publish_layer { } +# Staging region/bucket — overridable via environment for local testing. +STAGING_REGION="${STAGING_REGION:-us-east-1}" + +# Publish a single layer zip to the staging account as -staging[-slim]. +# Prints the full layer ARN to stdout; all progress messages go to stderr. +# Uses direct zip upload (no S3 hop) so no bucket policy changes are needed. +function publish_staging_layer { + layer_archive=$1 + runtime_name=$2 + arch=$3 + newrelic_agent_version=${4:-"none"} + slim=${5:-""} + + layer_name=$( layer_name_str $runtime_name $arch ) + staging_layer_name="${layer_name}-staging" + if [[ $slim == "slim" ]]; then + staging_layer_name="${layer_name}-slim-staging" + fi + + compat_list=( $runtime_name ) + if [[ $runtime_name == "provided" ]]; then compat_list=("provided" "provided.al2" "provided.al2023" "dotnetcore3.1"); fi + if [[ $runtime_name == "dotnet" ]]; then compat_list=("dotnet6" "dotnet8" "dotnet10"); fi + if [[ $runtime_name == "python" ]]; then compat_list=("python3.9" "python3.10" "python3.11" "python3.12" "python3.13" "python3.14"); fi + if [[ $runtime_name == "nodejs" ]]; then compat_list=("nodejs20.x" "nodejs22.x" "nodejs24.x"); fi + + echo "Publishing staging layer ${staging_layer_name} (${arch}) to ${STAGING_REGION}" >&2 + layer_version=$(aws lambda publish-layer-version \ + --layer-name "${staging_layer_name}" \ + --region "${STAGING_REGION}" \ + --zip-file "fileb://${layer_archive}" \ + --compatible-architectures "${arch}" \ + --compatible-runtimes "${compat_list[@]}" \ + --query 'Version' \ + --output text) + + echo "Staged ${staging_layer_name}:${layer_version}" >&2 + account_id=$(aws sts get-caller-identity --query Account --output text) + echo "arn:aws:lambda:${STAGING_REGION}:${account_id}:layer:${staging_layer_name}:${layer_version}" +} + +# Delete a staging layer version (best-effort; never fails the caller). +function delete_staging_layer { + layer_name=$1 + version=$2 + echo "Deleting staging layer ${layer_name}:${version} in ${STAGING_REGION}" + aws lambda delete-layer-version \ + --layer-name "${layer_name}" \ + --version-number "${version}" \ + --region "${STAGING_REGION}" 2>/dev/null || true +} + function publish_docker_ecr { layer_archive=$1 diff --git a/nodejs/publish-layers.sh b/nodejs/publish-layers.sh index e60ca244..eaa6ec81 100755 --- a/nodejs/publish-layers.sh +++ b/nodejs/publish-layers.sh @@ -3,7 +3,7 @@ set -Eeuo pipefail BUILD_DIR=nodejs -DIST_DIR=dist +DIST_DIR=${DIST_DIR:-dist} source ../libBuild.sh @@ -122,6 +122,42 @@ function publish_wrapper { done } +# Publish staging layers for a given node version (us-east-1, -staging suffix). +# Writes arn_x86/arn_arm64/arn_x86_slim/arn_arm64_slim to $GITHUB_OUTPUT. +function publish_staging_wrapper { + node_version=$1 + source $DIST_DIR/nr-env + + for arch in x86_64 arm64; do + ZIP=$DIST_DIR/nodejs${node_version}x.${arch}.zip + ZIP_SLIM=$DIST_DIR/nodejs${node_version}x.${arch}.slim.zip + if [ ! -f "$ZIP" ]; then echo "Package not found: ${ZIP}"; exit 1; fi + if [ ! -f "$ZIP_SLIM" ]; then echo "Package not found: ${ZIP_SLIM}"; exit 1; fi + + arn=$(publish_staging_layer "$ZIP" nodejs${node_version}.x "$arch" "$NEWRELIC_AGENT_VERSION") + arn_slim=$(publish_staging_layer "$ZIP_SLIM" nodejs${node_version}.x "$arch" "$NEWRELIC_AGENT_VERSION" slim) + + if [[ $arch == "x86_64" ]]; then + echo "arn_x86=${arn}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + echo "arn_x86_slim=${arn_slim}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + else + echo "arn_arm64=${arn}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + echo "arn_arm64_slim=${arn_slim}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + fi + done +} + +# Delete the 4 staging layer versions published by publish_staging_wrapper. +# Reads ARNs from env vars: ARN_X86, ARN_ARM64, ARN_X86_SLIM, ARN_ARM64_SLIM. +function cleanup_staging_wrapper { + for arn in "${ARN_X86:-}" "${ARN_ARM64:-}" "${ARN_X86_SLIM:-}" "${ARN_ARM64_SLIM:-}"; do + [[ -z "$arn" ]] && continue + layer_name=$(echo "$arn" | cut -d: -f8) + version=$(echo "$arn" | cut -d: -f9) + delete_staging_layer "$layer_name" "$version" + done +} + case "$1" in "build-universal") build_universal_wrapper arm64 @@ -233,6 +269,24 @@ case "$1" in $0 build-24 $0 publish-24 ;; +"publish-staging-20") + publish_staging_wrapper 20 + ;; +"publish-staging-22") + publish_staging_wrapper 22 + ;; +"publish-staging-24") + publish_staging_wrapper 24 + ;; +"cleanup-staging-20") + cleanup_staging_wrapper + ;; +"cleanup-staging-22") + cleanup_staging_wrapper + ;; +"cleanup-staging-24") + cleanup_staging_wrapper + ;; *) usage ;; diff --git a/python/publish-layers.sh b/python/publish-layers.sh index 8cfd1010..3df7eac0 100755 --- a/python/publish-layers.sh +++ b/python/publish-layers.sh @@ -3,7 +3,7 @@ set -Eeuo pipefail BUILD_DIR=python -DIST_DIR=dist +DIST_DIR=${DIST_DIR:-dist} NEWRELIC_AGENT_VERSION="" VERSION_REGEX="v?([0-9]+\.[0-9]+\.[0-9]+)\.[0-9]+_python" PY39_DIST_ARM64=$DIST_DIR/python39.arm64.zip @@ -205,6 +205,20 @@ case "$1" in publish_python_layer 3.14 x86_64 publish_docker_ecr $PY314_DIST_X86_64 python3.14 x86_64 ;; + "publish-staging-python3.14") + build_python_layer 3.14 arm64 + build_python_layer 3.14 x86_64 + arn_arm64=$(publish_staging_layer "$PY314_DIST_ARM64" python3.14 arm64 "$NEWRELIC_AGENT_VERSION") + echo "arn_arm64=${arn_arm64}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + arn_x86=$(publish_staging_layer "$PY314_DIST_X86_64" python3.14 x86_64 "$NEWRELIC_AGENT_VERSION") + echo "arn_x86=${arn_x86}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + ;; + "cleanup-staging-python3.14") + for arn in "${ARN_X86:-}" "${ARN_ARM64:-}"; do + [[ -z "$arn" ]] && continue + delete_staging_layer "$(echo "$arn" | cut -d: -f8)" "$(echo "$arn" | cut -d: -f9)" + done + ;; *) usage ;; diff --git a/ruby/publish-layers.sh b/ruby/publish-layers.sh index d55b0bb2..d9b1e2a9 100755 --- a/ruby/publish-layers.sh +++ b/ruby/publish-layers.sh @@ -15,7 +15,7 @@ set -Eeuo pipefail # preview-extensions-* -- Extensions preview file RUBY_DIR=ruby -DIST_DIR=dist +DIST_DIR=${DIST_DIR:-dist} WRAPPER_FILE=newrelic_lambda_wrapper.rb NEWRELIC_AGENT_VERSION="" # Set this to a path to a clone of newrelic-lambda-extension to build @@ -208,6 +208,20 @@ case "$1" in publish-ruby32-x86 publish_docker_ecr $RB32_DIST_X86_64 ruby3.2 x86_64 ;; + "publish-staging-ruby3.4") + build-ruby34-arm64 + build-ruby34-x86 + arn_arm64=$(publish_staging_layer "$RB34_DIST_ARM64" ruby3.4 arm64 "$NEWRELIC_AGENT_VERSION") + echo "arn_arm64=${arn_arm64}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + arn_x86=$(publish_staging_layer "$RB34_DIST_X86_64" ruby3.4 x86_64 "$NEWRELIC_AGENT_VERSION") + echo "arn_x86=${arn_x86}" >> "${GITHUB_OUTPUT:-/dev/stderr}" + ;; + "cleanup-staging-ruby3.4") + for arn in "${ARN_X86:-}" "${ARN_ARM64:-}"; do + [[ -z "$arn" ]] && continue + delete_staging_layer "$(echo "$arn" | cut -d: -f8)" "$(echo "$arn" | cut -d: -f9)" + done + ;; *) usage ;; From e6eb859284c62983e8cacf7fa29f506495f3440a Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Fri, 24 Apr 2026 15:11:59 +0530 Subject: [PATCH 02/16] Refactor CI workflows for Java, Node.js, Python, and Ruby to streamline staging, validation, cleanup, and release processes --- .github/workflows/publish-dotnet.yml | 86 +++++++------ .github/workflows/publish-extension.yml | 118 +++++++++--------- .github/workflows/publish-java.yml | 140 +++++++++++---------- .github/workflows/publish-node.yml | 155 ++++++++++++------------ .github/workflows/publish-python.yml | 116 ++++++++++-------- .github/workflows/publish-ruby.yml | 112 ++++++++++------- .github/workflows/validate-layer.yml | 77 ++++++++++++ 7 files changed, 469 insertions(+), 335 deletions(-) create mode 100644 .github/workflows/validate-layer.yml diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index 14542971..83e30d91 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -6,25 +6,26 @@ on: - v**_dotnet jobs: - publish-dotnet: + # ── Job 1: Publish staging (dotnet10) ──────────────────────────────────── + stage-dotnet: runs-on: ubuntu-latest + outputs: + tag-match: ${{ steps.dotnet-check-tag.outputs.match }} + arn_x86: ${{ steps.staging.outputs.arn_x86 }} + arn_arm64: ${{ steps.staging.outputs.arn_arm64 }} steps: - uses: actions/checkout@v4 - - name: Check Tag id: dotnet-check-tag run: | if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_dotnet ]]; then echo "match=true" >> $GITHUB_OUTPUT fi - - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: platforms: arm64, amd64 - - # ── 1. Publish staging ─────────────────────────────────────────────── - - name: Publish staging layers + - name: Publish staging layers (dotnet10) if: steps.dotnet-check-tag.outputs.match == 'true' id: staging env: @@ -35,55 +36,66 @@ jobs: cd dotnet ./publish-layers.sh publish-staging-dotnet - # ── 2. Validate ────────────────────────────────────────────────────── - - name: Validate staging layers - if: steps.dotnet-check-tag.outputs.match == 'true' - id: validate + # ── Job 2: Validate (calls reusable workflow) ───────────────────────────── + validate-dotnet: + needs: stage-dotnet + if: needs.stage-dotnet.outputs.tag-match == 'true' + uses: ./.github/workflows/validate-layer.yml + with: + runtime: dotnet10 + arn_x86: ${{ needs.stage-dotnet.outputs.arn_x86 }} + arn_arm64: ${{ needs.stage-dotnet.outputs.arn_arm64 }} + secrets: inherit + + # ── Job 3: Cleanup staging (always) ────────────────────────────────────── + cleanup-dotnet: + needs: [stage-dotnet, validate-dotnet] + if: always() && needs.stage-dotnet.outputs.arn_x86 != '' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Cleanup staging layers env: - ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} - ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} - RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + ARN_X86: ${{ needs.stage-dotnet.outputs.arn_x86 }} + ARN_ARM64: ${{ needs.stage-dotnet.outputs.arn_arm64 }} run: | - chmod +x .github/scripts/poll-validation.sh - .github/scripts/poll-validation.sh \ - --runtime dotnet10 \ - --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ - --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" + cd dotnet + ./publish-layers.sh cleanup-staging-dotnet - # ── 3. Release publish ─────────────────────────────────────────────── + # ── Job 4: Release dotnet layer — gated on validate passing ─────────────── + publish-dotnet: + needs: [stage-dotnet, validate-dotnet] + if: needs.stage-dotnet.outputs.tag-match == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64, amd64 - name: Publish Dotnet Layer - if: steps.dotnet-check-tag.outputs.match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AGENT_VERSION: ${{ steps.dotnet-check-tag.outputs.version }} run: | cd dotnet ./publish-layers.sh - # ── 4. Cleanup staging (always) ────────────────────────────────────── - - name: Cleanup staging layers - if: always() && steps.staging.outputs.arn_x86 != '' - env: - AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} - STAGING_REGION: us-east-1 - ARN_X86: ${{ steps.staging.outputs.arn_x86 }} - ARN_ARM64: ${{ steps.staging.outputs.arn_arm64 }} - run: | - cd dotnet - ./publish-layers.sh cleanup-staging-dotnet - - # ── 5. Slack notification ──────────────────────────────────────────── + # ── Job 5: Slack release summary — only after validate passed ───────────── + notify-slack: + needs: [stage-dotnet, validate-dotnet, publish-dotnet] + if: always() && needs.validate-dotnet.result == 'success' && needs.stage-dotnet.outputs.tag-match == 'true' + runs-on: ubuntu-latest + steps: - name: Notify Slack - if: always() && steps.dotnet-check-tag.outputs.match == 'true' && steps.staging.outputs.arn_x86 != '' uses: slackapi/slack-github-action@v2.1.0 with: webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} webhook-type: incoming-webhook payload: | { - "text": "${{ steps.validate.outcome == 'success' && ':white_check_mark:' || ':x:' }} *dotnet10* layer validation ${{ steps.validate.outcome }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + "text": "${{ needs.publish-dotnet.result == 'success' && ':white_check_mark:' || ':x:' }} *dotnet10* layer release ${{ needs.publish-dotnet.result == 'success' && 'succeeded' || 'FAILED' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" } diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index b2a5ca98..52de68e0 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -6,28 +6,28 @@ on: - v**_extension jobs: - publish-extension: + # ── Job 1: Publish staging (extension) ─────────────────────────────────── + stage-extension: runs-on: ubuntu-latest + outputs: + tag-match: ${{ steps.extension-check-tag.outputs.match }} + arn_x86: ${{ steps.staging.outputs.arn_x86 }} + arn_arm64: ${{ steps.staging.outputs.arn_arm64 }} steps: - uses: actions/checkout@v4 - - name: Check Tag id: extension-check-tag run: | if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_extension$ ]]; then echo "match=true" >> $GITHUB_OUTPUT fi - - name: Install publish dependencies if: steps.extension-check-tag.outputs.match == 'true' run: pip install -U awscli - - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: platforms: arm64, amd64 - - # ── 1. Publish staging ─────────────────────────────────────────────── - name: Publish staging layers if: steps.extension-check-tag.outputs.match == 'true' id: staging @@ -39,45 +39,64 @@ jobs: cd extension ./publish-layer.sh publish-staging - # ── 2. Validate go/al2 ─────────────────────────────────────────────── - - name: Validate staging layers (go/al2) - if: steps.extension-check-tag.outputs.match == 'true' - id: validate_al2 - env: - ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} - ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} - RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} - AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} - run: | - chmod +x .github/scripts/poll-validation.sh - .github/scripts/poll-validation.sh \ - --runtime go \ - --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ - --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" \ - --os al2 + # ── Job 2a: Validate go/al2 (calls reusable workflow) ──────────────────── + validate-al2: + needs: stage-extension + if: needs.stage-extension.outputs.tag-match == 'true' + uses: ./.github/workflows/validate-layer.yml + with: + runtime: go + label: go/al2 + arn_x86: ${{ needs.stage-extension.outputs.arn_x86 }} + arn_arm64: ${{ needs.stage-extension.outputs.arn_arm64 }} + os: al2 + secrets: inherit - # ── 3. Validate go/al2023 ──────────────────────────────────────────── - - name: Validate staging layers (go/al2023) - if: steps.extension-check-tag.outputs.match == 'true' - id: validate_al2023 + # ── Job 2b: Validate go/al2023 (calls reusable workflow, runs in parallel) ─ + validate-al2023: + needs: stage-extension + if: needs.stage-extension.outputs.tag-match == 'true' + uses: ./.github/workflows/validate-layer.yml + with: + runtime: go + label: go/al2023 + arn_x86: ${{ needs.stage-extension.outputs.arn_x86 }} + arn_arm64: ${{ needs.stage-extension.outputs.arn_arm64 }} + os: al2023 + secrets: inherit + + # ── Job 3: Cleanup staging (always, after both validates) ───────────────── + cleanup-extension: + needs: [stage-extension, validate-al2, validate-al2023] + if: always() && needs.stage-extension.outputs.arn_x86 != '' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Cleanup staging layers env: - ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} - ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} - RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + ARN_X86: ${{ needs.stage-extension.outputs.arn_x86 }} + ARN_ARM64: ${{ needs.stage-extension.outputs.arn_arm64 }} run: | - chmod +x .github/scripts/poll-validation.sh - .github/scripts/poll-validation.sh \ - --runtime go \ - --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ - --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" \ - --os al2023 + cd extension + ./publish-layer.sh cleanup-staging - # ── 4. Release publish ─────────────────────────────────────────────── + # ── Job 4: Release extension — gated on BOTH validates passing ──────────── + publish-extension: + needs: [stage-extension, validate-al2, validate-al2023] + if: needs.stage-extension.outputs.tag-match == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install publish dependencies + run: pip install -U awscli + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64, amd64 - name: Publish extension layer - if: steps.extension-check-tag.outputs.match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -85,27 +104,18 @@ jobs: cd extension ./publish-layer.sh - # ── 5. Cleanup staging (always) ────────────────────────────────────── - - name: Cleanup staging layers - if: always() && steps.staging.outputs.arn_x86 != '' - env: - AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} - STAGING_REGION: us-east-1 - ARN_X86: ${{ steps.staging.outputs.arn_x86 }} - ARN_ARM64: ${{ steps.staging.outputs.arn_arm64 }} - run: | - cd extension - ./publish-layer.sh cleanup-staging - - # ── 6. Slack notification ──────────────────────────────────────────── + # ── Job 5: Slack release summary — only after both validates passed ──────── + notify-slack: + needs: [stage-extension, validate-al2, validate-al2023, publish-extension] + if: always() && needs.validate-al2.result == 'success' && needs.validate-al2023.result == 'success' && needs.stage-extension.outputs.tag-match == 'true' + runs-on: ubuntu-latest + steps: - name: Notify Slack - if: always() && steps.extension-check-tag.outputs.match == 'true' && steps.staging.outputs.arn_x86 != '' uses: slackapi/slack-github-action@v2.1.0 with: webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} webhook-type: incoming-webhook payload: | { - "text": "${{ (steps.validate_al2.outcome == 'success' && steps.validate_al2023.outcome == 'success') && ':white_check_mark:' || ':x:' }} *extension (go/al2+al2023)* layer validation al2=${{ steps.validate_al2.outcome }} al2023=${{ steps.validate_al2023.outcome }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + "text": "${{ needs.publish-extension.result == 'success' && ':white_check_mark:' || ':x:' }} *extension (go/al2+al2023)* layer release ${{ needs.publish-extension.result == 'success' && 'succeeded' || 'FAILED' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" } diff --git a/.github/workflows/publish-java.yml b/.github/workflows/publish-java.yml index 34c11ecc..abd745f4 100644 --- a/.github/workflows/publish-java.yml +++ b/.github/workflows/publish-java.yml @@ -6,49 +6,31 @@ on: - v**_java jobs: - publish-java: + # ── Job 1: Build + publish staging (java21 only) ────────────────────────── + stage-java: runs-on: ubuntu-latest - strategy: - matrix: - java-version: [ java8al2, java11, java17, java21 ] - + outputs: + tag-match: ${{ steps.java-check-tag.outputs.match }} + arn_x86: ${{ steps.staging.outputs.arn_x86 }} + arn_arm64: ${{ steps.staging.outputs.arn_arm64 }} steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # pin@v4 - - - name: Set up Java version - run: | - declare -A map_java_version - map_java_version=( - ["java8al2"]="8" - ["java11"]="11" - ["java17"]="17" - ["java21"]="21" - ) - java_numeric_version=${map_java_version[${{ matrix.java-version }}]} - echo "JAVA_NUMERIC_VERSION=$java_numeric_version" >> $GITHUB_ENV - - - name: Use Java ${{ env.JAVA_NUMERIC_VERSION }} + - name: Use Java 21 uses: actions/setup-java@v4 with: distribution: 'corretto' - java-version: ${{ env.JAVA_NUMERIC_VERSION }} - + java-version: '21' - name: Check Tag id: java-check-tag run: | if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_java$ ]]; then echo "match=true" >> $GITHUB_OUTPUT fi - - # ── 1. Build (java21 only) ──────────────────────────────────────────── - # Docker build + extract zips to dist/java21/ — reused by release step. - - name: Build layer artifacts - if: steps.java-check-tag.outputs.match == 'true' && matrix.java-version == 'java21' + - name: Build layer artifacts (java21) + if: steps.java-check-tag.outputs.match == 'true' run: make extract-java21-artifacts - - # ── 2. Publish staging (java21 only) ───────────────────────────────── - - name: Publish staging layers - if: steps.java-check-tag.outputs.match == 'true' && matrix.java-version == 'java21' + - name: Publish staging layers (java21) + if: steps.java-check-tag.outputs.match == 'true' id: staging env: AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} @@ -56,30 +38,67 @@ jobs: STAGING_REGION: us-east-1 run: | cd java - DIST_DIR=../dist/java21 \ - ./publish-layers.sh publish-staging-java21 + DIST_DIR=../dist/java21 ./publish-layers.sh publish-staging-java21 + + # ── Job 2: Validate (calls reusable workflow) ───────────────────────────── + validate-java: + needs: stage-java + if: needs.stage-java.outputs.tag-match == 'true' + uses: ./.github/workflows/validate-layer.yml + with: + runtime: java21 + arn_x86: ${{ needs.stage-java.outputs.arn_x86 }} + arn_arm64: ${{ needs.stage-java.outputs.arn_arm64 }} + secrets: inherit - # ── 3. Validate (java21 only) ───────────────────────────────────────── - - name: Validate staging layers - if: steps.java-check-tag.outputs.match == 'true' && matrix.java-version == 'java21' - id: validate + # ── Job 3: Cleanup staging (always) ────────────────────────────────────── + cleanup-java: + needs: [stage-java, validate-java] + if: always() && needs.stage-java.outputs.arn_x86 != '' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Cleanup staging layers env: - ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} - ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} - RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + ARN_X86: ${{ needs.stage-java.outputs.arn_x86 }} + ARN_ARM64: ${{ needs.stage-java.outputs.arn_arm64 }} run: | - chmod +x .github/scripts/poll-validation.sh - .github/scripts/poll-validation.sh \ - --runtime java21 \ - --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ - --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" + cd java + ./publish-layers.sh cleanup-staging-java21 - # ── 4. Release publish ─────────────────────────────────────────────── - # java21: reuse Docker image (already built). Others: build+publish via make. + # ── Job 4: Release all Java versions — gated on validate passing ────────── + publish-java: + needs: [stage-java, validate-java] + if: needs.stage-java.outputs.tag-match == 'true' + runs-on: ubuntu-latest + strategy: + matrix: + java-version: [java8al2, java11, java17, java21] + steps: + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # pin@v4 + - name: Set up Java version + run: | + declare -A map_java_version + map_java_version=( + ["java8al2"]="8" + ["java11"]="11" + ["java17"]="17" + ["java21"]="21" + ) + java_numeric_version=${map_java_version[${{ matrix.java-version }}]} + echo "JAVA_NUMERIC_VERSION=$java_numeric_version" >> $GITHUB_ENV + - name: Use Java ${{ env.JAVA_NUMERIC_VERSION }} + uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: ${{ env.JAVA_NUMERIC_VERSION }} + - name: Build layer artifacts (java21 only) + if: matrix.java-version == 'java21' + run: make extract-java21-artifacts - name: Publish ${{ matrix.java-version }} layer - if: steps.java-check-tag.outputs.match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -89,15 +108,11 @@ jobs: else make publish-${{ matrix.java-version }}-ci fi - - # ── 5. ECR image ───────────────────────────────────────────────────── - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: platforms: arm64, amd64 - - name: Publish ECR image for ${{ matrix.java-version }} - if: steps.java-check-tag.outputs.match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -105,27 +120,18 @@ jobs: cd java ./publish-layers.sh build-publish-${{ matrix.java-version }}-ecr-image - # ── 6. Slack notification (java21 only) ────────────────────────────── + # ── Job 5: Slack release summary — only after validate passed ───────────── + notify-slack: + needs: [stage-java, validate-java, publish-java] + if: always() && needs.validate-java.result == 'success' && needs.stage-java.outputs.tag-match == 'true' + runs-on: ubuntu-latest + steps: - name: Notify Slack - if: always() && steps.java-check-tag.outputs.match == 'true' && matrix.java-version == 'java21' && steps.staging.outputs.arn_x86 != '' uses: slackapi/slack-github-action@v2.1.0 with: webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} webhook-type: incoming-webhook payload: | { - "text": "${{ steps.validate.outcome == 'success' && ':white_check_mark:' || ':x:' }} *java21* layer validation ${{ steps.validate.outcome }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + "text": "${{ needs.publish-java.result == 'success' && ':white_check_mark:' || ':x:' }} *java* layers (8al2 / 11 / 17 / 21) release ${{ needs.publish-java.result == 'success' && 'succeeded' || 'FAILED' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" } - - # ── 7. Cleanup staging (java21 only, always) ───────────────────────── - - name: Cleanup staging layers - if: always() && matrix.java-version == 'java21' && steps.staging.outputs.arn_x86 != '' - env: - AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} - STAGING_REGION: us-east-1 - ARN_X86: ${{ steps.staging.outputs.arn_x86 }} - ARN_ARM64: ${{ steps.staging.outputs.arn_arm64 }} - run: | - cd java - ./publish-layers.sh cleanup-staging-java21 diff --git a/.github/workflows/publish-node.yml b/.github/workflows/publish-node.yml index d902d18f..2b73ceab 100644 --- a/.github/workflows/publish-node.yml +++ b/.github/workflows/publish-node.yml @@ -6,33 +6,26 @@ on: - v**_nodejs jobs: - publish-node: + # ── Job 1: Build + publish staging (node24 only) ────────────────────────── + stage-node: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20, 22, 24] - + outputs: + tag-match: ${{ steps.setup.outputs.tag-match }} + arn_x86: ${{ steps.staging.outputs.arn_x86 }} + arn_arm64: ${{ steps.staging.outputs.arn_arm64 }} + arn_x86_slim: ${{ steps.staging.outputs.arn_x86_slim }} + arn_arm64_slim: ${{ steps.staging.outputs.arn_arm64_slim }} steps: - uses: actions/checkout@v4 - - name: Setup id: setup uses: ./.github/actions/node-layer-setup with: - node-version: ${{ matrix.node-version }} - - # ── 1. Build ───────────────────────────────────────────────────────── - # Build the Docker image and extract the layer zips to dist/nodejs/. - # The same Docker image is reused for the release publish step (docker - # run without rebuild), so the --no-cache build cost is paid only once. - - name: Build layer artifacts + node-version: 24 + - name: Build layer artifacts (node24) if: steps.setup.outputs.tag-match == 'true' - run: make extract-nodejs${{ matrix.node-version }}-artifacts - - # ── 2. Staging publish ─────────────────────────────────────────────── - # Publish to us-east-1 only with a -staging/-slim-staging suffix. - # Layers land in the staging account (466768951184); no public perms. - - name: Publish staging layers + run: make extract-nodejs24-artifacts + - name: Publish staging layers (node24) if: steps.setup.outputs.tag-match == 'true' id: staging env: @@ -41,35 +34,58 @@ jobs: STAGING_REGION: us-east-1 run: | cd nodejs - DIST_DIR=../dist/nodejs${{ matrix.node-version }} \ - ./publish-layers.sh publish-staging-${{ matrix.node-version }} + DIST_DIR=../dist/nodejs24 ./publish-layers.sh publish-staging-24 - # ── 3. Validate ────────────────────────────────────────────────────── - # Call the orchestrator with staging ARNs and poll S3 until the result - # summary appears. Fails the job if any test function fails. - - name: Validate staging layers - if: steps.setup.outputs.tag-match == 'true' - id: validate + # ── Job 2: Validate (calls reusable workflow) ───────────────────────────── + validate-node: + needs: stage-node + if: needs.stage-node.outputs.tag-match == 'true' + uses: ./.github/workflows/validate-layer.yml + with: + runtime: nodejs24 + arn_x86: ${{ needs.stage-node.outputs.arn_x86 }} + arn_arm64: ${{ needs.stage-node.outputs.arn_arm64 }} + arn_x86_slim: ${{ needs.stage-node.outputs.arn_x86_slim }} + arn_arm64_slim: ${{ needs.stage-node.outputs.arn_arm64_slim }} + secrets: inherit + + # ── Job 3: Cleanup staging (always, even if validate failed) ───────────── + cleanup-node: + needs: [stage-node, validate-node] + if: always() && needs.stage-node.outputs.arn_x86 != '' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Cleanup staging layers env: - ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} - ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} - RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + ARN_X86: ${{ needs.stage-node.outputs.arn_x86 }} + ARN_ARM64: ${{ needs.stage-node.outputs.arn_arm64 }} + ARN_X86_SLIM: ${{ needs.stage-node.outputs.arn_x86_slim }} + ARN_ARM64_SLIM: ${{ needs.stage-node.outputs.arn_arm64_slim }} run: | - chmod +x .github/scripts/poll-validation.sh - .github/scripts/poll-validation.sh \ - --runtime nodejs${{ matrix.node-version }} \ - --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ - --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" \ - --arn-x86-slim "${{ steps.staging.outputs.arn_x86_slim }}" \ - --arn-arm64-slim "${{ steps.staging.outputs.arn_arm64_slim }}" + cd nodejs + ./publish-layers.sh cleanup-staging-24 - # ── 4. Release publish ─────────────────────────────────────────────── - # Only runs if validation passed. Reuses the already-built Docker image - # and publishes to all production regions with public permissions. + # ── Job 4: Release node20/22/24 — gated on validate passing ────────────── + publish-node: + needs: [stage-node, validate-node] + if: needs.stage-node.outputs.tag-match == 'true' + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20, 22, 24] + steps: + - uses: actions/checkout@v4 + - name: Setup + uses: ./.github/actions/node-layer-setup + with: + node-version: ${{ matrix.node-version }} + - name: Build layer artifacts + run: make extract-nodejs${{ matrix.node-version }}-artifacts - name: Publish release layers - if: steps.setup.outputs.tag-match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -78,48 +94,14 @@ jobs: -e AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY \ newrelic-lambda-layers-nodejs${{ matrix.node-version }} - - # ── 5. ECR image ───────────────────────────────────────────────────── - name: Publish ECR image for nodejs${{ matrix.node-version }} - if: steps.setup.outputs.tag-match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: | cd nodejs ./publish-layers.sh build-publish-${{ matrix.node-version }}-ecr-image - - # ── 6. Cleanup staging ─────────────────────────────────────────────── - # Always runs (even if validate or release failed) so staging layers - # are never left stranded in the staging account. - - name: Cleanup staging layers - if: always() && steps.staging.outputs.arn_x86 != '' - env: - AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} - STAGING_REGION: us-east-1 - ARN_X86: ${{ steps.staging.outputs.arn_x86 }} - ARN_ARM64: ${{ steps.staging.outputs.arn_arm64 }} - ARN_X86_SLIM: ${{ steps.staging.outputs.arn_x86_slim }} - ARN_ARM64_SLIM: ${{ steps.staging.outputs.arn_arm64_slim }} - run: | - cd nodejs - ./publish-layers.sh cleanup-staging-${{ matrix.node-version }} - - # ── 7. Slack notification ──────────────────────────────────────────── - - name: Notify Slack - if: always() && steps.setup.outputs.tag-match == 'true' && steps.staging.outputs.arn_x86 != '' - uses: slackapi/slack-github-action@v2.1.0 - with: - webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} - webhook-type: incoming-webhook - payload: | - { - "text": "${{ steps.validate.outcome == 'success' && ':white_check_mark:' || ':x:' }} *nodejs${{ matrix.node-version }}* layer validation ${{ steps.validate.outcome }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" - } - - name: Upload Unit Test Coverage - if: steps.setup.outputs.tag-match == 'true' uses: codecov/codecov-action@v5.3.1 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -127,23 +109,23 @@ jobs: fail_ci_if_error: true flags: unit-tests + # ── Job 5: Release universal — gated on validate passing ───────────────── publish-node-universal: + needs: [stage-node, validate-node] + if: needs.stage-node.outputs.tag-match == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup - id: setup uses: ./.github/actions/node-layer-setup with: node-version: 22 - name: Publish universal layer - if: steps.setup.outputs.tag-match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: make publish-nodejs-universal-ci - name: Publish universal ECR image - if: steps.setup.outputs.tag-match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -151,10 +133,25 @@ jobs: cd nodejs ./publish-layers.sh build-publish-universal-ecr-image - name: Upload Unit Test Coverage - if: steps.setup.outputs.tag-match == 'true' uses: codecov/codecov-action@v5.3.1 with: token: ${{ secrets.CODECOV_TOKEN }} files: ./nodejs/coverage/unit/lcov.info fail_ci_if_error: true flags: unit-tests + + # ── Job 6: Slack release summary — only after validate passed ───────────── + notify-slack: + needs: [stage-node, validate-node, publish-node, publish-node-universal] + if: always() && needs.validate-node.result == 'success' && needs.stage-node.outputs.tag-match == 'true' + runs-on: ubuntu-latest + steps: + - name: Notify Slack + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ (needs.publish-node.result == 'success' && needs.publish-node-universal.result == 'success') && ':white_check_mark:' || ':x:' }} *nodejs* layers (20 / 22 / 24 / universal) release ${{ (needs.publish-node.result == 'success' && needs.publish-node-universal.result == 'success') && 'succeeded' || 'FAILED' }} — release: ${{ needs.publish-node.result }}, universal: ${{ needs.publish-node-universal.result }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + } diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index cc6e9a4b..c74c2782 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -6,22 +6,20 @@ on: - v**_python jobs: - publish-python: + # ── Job 1: Publish staging (python3.14 only) ────────────────────────────── + stage-python: runs-on: ubuntu-latest - strategy: - matrix: - python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13', '3.14' ] + outputs: + tag-match: ${{ steps.setup.outputs.tag-match }} + arn_x86: ${{ steps.staging.outputs.arn_x86 }} + arn_arm64: ${{ steps.staging.outputs.arn_arm64 }} steps: - uses: actions/checkout@v4 - - name: Setup id: setup uses: ./.github/actions/python-layer-setup - - # ── 1+2. Build + Publish staging (python3.14 only) ─────────────────── - # Builds inline (native pip), publishes to us-east-1 with -staging suffix. - - name: Publish staging layers - if: steps.setup.outputs.tag-match == 'true' && matrix.python-version == '3.14' + - name: Publish staging layers (python3.14) + if: steps.setup.outputs.tag-match == 'true' id: staging env: AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} @@ -29,28 +27,50 @@ jobs: STAGING_REGION: us-east-1 run: | cd python - ./publish-layers.sh publish-staging-python${{ matrix.python-version }} + ./publish-layers.sh publish-staging-python3.14 + + # ── Job 2: Validate (calls reusable workflow) ───────────────────────────── + validate-python: + needs: stage-python + if: needs.stage-python.outputs.tag-match == 'true' + uses: ./.github/workflows/validate-layer.yml + with: + runtime: python314 + arn_x86: ${{ needs.stage-python.outputs.arn_x86 }} + arn_arm64: ${{ needs.stage-python.outputs.arn_arm64 }} + secrets: inherit - # ── 3. Validate (python3.14 only) ──────────────────────────────────── - - name: Validate staging layers - if: steps.setup.outputs.tag-match == 'true' && matrix.python-version == '3.14' - id: validate + # ── Job 3: Cleanup staging (always) ────────────────────────────────────── + cleanup-python: + needs: [stage-python, validate-python] + if: always() && needs.stage-python.outputs.arn_x86 != '' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Cleanup staging layers env: - ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} - ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} - RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + ARN_X86: ${{ needs.stage-python.outputs.arn_x86 }} + ARN_ARM64: ${{ needs.stage-python.outputs.arn_arm64 }} run: | - chmod +x .github/scripts/poll-validation.sh - .github/scripts/poll-validation.sh \ - --runtime python314 \ - --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ - --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" + cd python + ./publish-layers.sh cleanup-staging-python3.14 - # ── 4. Release publish ─────────────────────────────────────────────── + # ── Job 4: Release all Python versions — gated on validate passing ──────── + publish-python: + needs: [stage-python, validate-python] + if: needs.stage-python.outputs.tag-match == 'true' + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] + steps: + - uses: actions/checkout@v4 + - name: Setup + uses: ./.github/actions/python-layer-setup - name: Publish Python ${{ matrix.python-version }} layer - if: steps.setup.outputs.tag-match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -58,43 +78,35 @@ jobs: cd python ./publish-layers.sh python${{ matrix.python-version }} - # ── 5. Slack notification (python3.14 only) ────────────────────────── - - name: Notify Slack - if: always() && steps.setup.outputs.tag-match == 'true' && matrix.python-version == '3.14' && steps.staging.outputs.arn_x86 != '' - uses: slackapi/slack-github-action@v2.1.0 - with: - webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} - webhook-type: incoming-webhook - payload: | - { - "text": "${{ steps.validate.outcome == 'success' && ':white_check_mark:' || ':x:' }} *python3.14* layer validation ${{ steps.validate.outcome }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" - } - - # ── 6. Cleanup staging (python3.14 only, always) ───────────────────── - - name: Cleanup staging layers - if: always() && matrix.python-version == '3.14' && steps.staging.outputs.arn_x86 != '' - env: - AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} - STAGING_REGION: us-east-1 - ARN_X86: ${{ steps.staging.outputs.arn_x86 }} - ARN_ARM64: ${{ steps.staging.outputs.arn_arm64 }} - run: | - cd python - ./publish-layers.sh cleanup-staging-python${{ matrix.python-version }} - + # ── Job 5: Release universal — gated on validate passing ───────────────── publish-python-universal: + needs: [stage-python, validate-python] + if: needs.stage-python.outputs.tag-match == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup - id: setup uses: ./.github/actions/python-layer-setup - name: Publish universal Python layer - if: steps.setup.outputs.tag-match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: | cd python ./publish-layers.sh python + + # ── Job 6: Slack release summary — only after validate passed ───────────── + notify-slack: + needs: [stage-python, validate-python, publish-python, publish-python-universal] + if: always() && needs.validate-python.result == 'success' && needs.stage-python.outputs.tag-match == 'true' + runs-on: ubuntu-latest + steps: + - name: Notify Slack + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ (needs.publish-python.result == 'success' && needs.publish-python-universal.result == 'success') && ':white_check_mark:' || ':x:' }} *python* layers (3.9–3.14 + universal) release ${{ (needs.publish-python.result == 'success' && needs.publish-python-universal.result == 'success') && 'succeeded' || 'FAILED' }} — release: ${{ needs.publish-python.result }}, universal: ${{ needs.publish-python-universal.result }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + } diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index bf4490c9..6b4f6307 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -6,42 +6,37 @@ on: - v**_ruby jobs: - publish-ruby: + # ── Job 1: Publish staging (ruby3.4 only) ──────────────────────────────── + stage-ruby: runs-on: ubuntu-latest - strategy: - matrix: - ruby-version: [3.2, 3.3, 3.4] + outputs: + tag-match: ${{ steps.ruby-check-tag.outputs.match }} + arn_x86: ${{ steps.staging.outputs.arn_x86 }} + arn_arm64: ${{ steps.staging.outputs.arn_arm64 }} steps: - uses: actions/checkout@v4.2.2 - - - name: Use Ruby ${{ matrix.ruby-version }} + - name: Use Ruby 3.4 uses: ruby/setup-ruby@v1.226.0 with: - ruby-version: ${{ matrix.ruby-version }} - + ruby-version: 3.4 - name: Check Tag id: ruby-check-tag run: | if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_ruby$ ]]; then echo "match=true" >> $GITHUB_OUTPUT fi - - name: Clean the workspace run: ./bin/clean working-directory: ruby - - name: Install Ruby Dependencies run: bundle working-directory: ruby - - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: platforms: arm64, amd64 - - # ── 1+2. Build + Publish staging (ruby3.4 only) ─────────────────────── - - name: Publish staging layers - if: steps.ruby-check-tag.outputs.match == 'true' && matrix.ruby-version == '3.4' + - name: Publish staging layers (ruby3.4) + if: steps.ruby-check-tag.outputs.match == 'true' id: staging env: AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} @@ -49,55 +44,80 @@ jobs: STAGING_REGION: us-east-1 run: | cd ruby - ./publish-layers.sh publish-staging-ruby${{ matrix.ruby-version }} + ./publish-layers.sh publish-staging-ruby3.4 - # ── 3. Validate (ruby3.4 only) ──────────────────────────────────────── - - name: Validate staging layers - if: steps.ruby-check-tag.outputs.match == 'true' && matrix.ruby-version == '3.4' - id: validate + # ── Job 2: Validate (calls reusable workflow) ───────────────────────────── + validate-ruby: + needs: stage-ruby + if: needs.stage-ruby.outputs.tag-match == 'true' + uses: ./.github/workflows/validate-layer.yml + with: + runtime: ruby34 + arn_x86: ${{ needs.stage-ruby.outputs.arn_x86 }} + arn_arm64: ${{ needs.stage-ruby.outputs.arn_arm64 }} + secrets: inherit + + # ── Job 3: Cleanup staging (always) ────────────────────────────────────── + cleanup-ruby: + needs: [stage-ruby, validate-ruby] + if: always() && needs.stage-ruby.outputs.arn_x86 != '' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Cleanup staging layers env: - ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} - ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} - RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + STAGING_REGION: us-east-1 + ARN_X86: ${{ needs.stage-ruby.outputs.arn_x86 }} + ARN_ARM64: ${{ needs.stage-ruby.outputs.arn_arm64 }} run: | - chmod +x .github/scripts/poll-validation.sh - .github/scripts/poll-validation.sh \ - --runtime ruby34 \ - --arn-x86 "${{ steps.staging.outputs.arn_x86 }}" \ - --arn-arm64 "${{ steps.staging.outputs.arn_arm64 }}" + cd ruby + ./publish-layers.sh cleanup-staging-ruby3.4 - # ── 4. Release publish ──────────────────────────────────────────────── + # ── Job 4: Release all Ruby versions — gated on validate passing ────────── + publish-ruby: + needs: [stage-ruby, validate-ruby] + if: needs.stage-ruby.outputs.tag-match == 'true' + runs-on: ubuntu-latest + strategy: + matrix: + ruby-version: [3.2, 3.3, 3.4] + steps: + - uses: actions/checkout@v4.2.2 + - name: Use Ruby ${{ matrix.ruby-version }} + uses: ruby/setup-ruby@v1.226.0 + with: + ruby-version: ${{ matrix.ruby-version }} + - name: Clean the workspace + run: ./bin/clean + working-directory: ruby + - name: Install Ruby Dependencies + run: bundle + working-directory: ruby + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64, amd64 - name: Build and Publish layer - if: steps.ruby-check-tag.outputs.match == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: ./publish-layers.sh ruby${{ matrix.ruby-version }} working-directory: ruby - # ── 5. Slack notification (ruby3.4 only) ───────────────────────────── + # ── Job 5: Slack release summary — only after validate passed ───────────── + notify-slack: + needs: [stage-ruby, validate-ruby, publish-ruby] + if: always() && needs.validate-ruby.result == 'success' && needs.stage-ruby.outputs.tag-match == 'true' + runs-on: ubuntu-latest + steps: - name: Notify Slack - if: always() && steps.ruby-check-tag.outputs.match == 'true' && matrix.ruby-version == '3.4' && steps.staging.outputs.arn_x86 != '' uses: slackapi/slack-github-action@v2.1.0 with: webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} webhook-type: incoming-webhook payload: | { - "text": "${{ steps.validate.outcome == 'success' && ':white_check_mark:' || ':x:' }} *ruby3.4* layer validation ${{ steps.validate.outcome }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + "text": "${{ needs.publish-ruby.result == 'success' && ':white_check_mark:' || ':x:' }} *ruby* layers (3.2 / 3.3 / 3.4) release ${{ needs.publish-ruby.result == 'success' && 'succeeded' || 'FAILED' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" } - - # ── 6. Cleanup staging (ruby3.4 only, always) ──────────────────────── - - name: Cleanup staging layers - if: always() && matrix.ruby-version == '3.4' && steps.staging.outputs.arn_x86 != '' - env: - AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} - STAGING_REGION: us-east-1 - ARN_X86: ${{ steps.staging.outputs.arn_x86 }} - ARN_ARM64: ${{ steps.staging.outputs.arn_arm64 }} - run: | - cd ruby - ./publish-layers.sh cleanup-staging-ruby${{ matrix.ruby-version }} diff --git a/.github/workflows/validate-layer.yml b/.github/workflows/validate-layer.yml new file mode 100644 index 00000000..b976754c --- /dev/null +++ b/.github/workflows/validate-layer.yml @@ -0,0 +1,77 @@ +name: Validate Layer (reusable) + +on: + workflow_call: + inputs: + runtime: + required: true + type: string + label: + description: Display name used in Slack messages (defaults to runtime) + required: false + type: string + default: '' + arn_x86: + required: true + type: string + arn_arm64: + required: true + type: string + arn_x86_slim: + required: false + type: string + default: '' + arn_arm64_slim: + required: false + type: string + default: '' + os: + required: false + type: string + default: '' + secrets: + ORCHESTRATOR_API_URL: + required: true + ORCHESTRATOR_API_KEY: + required: true + VALIDATION_RESULTS_BUCKET: + required: true + STAGING_AWS_ACCESS_KEY_ID: + required: true + STAGING_AWS_SECRET_ACCESS_KEY: + required: true + SLACK_VALIDATION_WEBHOOK: + required: true + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Validate staging layers + id: validate + env: + ORCHESTRATOR_API_URL: ${{ secrets.ORCHESTRATOR_API_URL }} + ORCHESTRATOR_API_KEY: ${{ secrets.ORCHESTRATOR_API_KEY }} + RESULTS_BUCKET: ${{ secrets.VALIDATION_RESULTS_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} + run: | + chmod +x .github/scripts/poll-validation.sh + ARGS="--runtime ${{ inputs.runtime }} --arn-x86 ${{ inputs.arn_x86 }} --arn-arm64 ${{ inputs.arn_arm64 }}" + [[ -n "${{ inputs.arn_x86_slim }}" ]] && ARGS="$ARGS --arn-x86-slim ${{ inputs.arn_x86_slim }}" + [[ -n "${{ inputs.arn_arm64_slim }}" ]] && ARGS="$ARGS --arn-arm64-slim ${{ inputs.arn_arm64_slim }}" + [[ -n "${{ inputs.os }}" ]] && ARGS="$ARGS --os ${{ inputs.os }}" + .github/scripts/poll-validation.sh $ARGS + + - name: Notify Slack — validation failed + if: failure() + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + webhook-type: incoming-webhook + payload: | + { + "text": ":x: *${{ inputs.label || inputs.runtime }}* layer validation FAILED — release blocked — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + } From 947f17c16ff01c612f191791b3f1f9d50bffc335 Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Mon, 27 Apr 2026 12:06:48 +0530 Subject: [PATCH 03/16] fix: reduce S3 poll frequency to 5 min and extend default timeout to 30 min MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Polling every 15s was noisy and unnecessary — validation takes several minutes on the orchestrator side. Switch to 300s sleep so the job only checks when results are realistically ready. Default VALIDATION_TIMEOUT_S raised from 600s to 1800s so at least 5–6 polls occur before giving up. Added timeout-minutes: 35 on the validate job to match. --- .github/scripts/poll-validation.sh | 4 ++-- .github/workflows/validate-layer.yml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/scripts/poll-validation.sh b/.github/scripts/poll-validation.sh index 3f5a3c02..6b06bbba 100755 --- a/.github/scripts/poll-validation.sh +++ b/.github/scripts/poll-validation.sh @@ -23,7 +23,7 @@ ARN_X86_SLIM="" ARN_ARM64_SLIM="" OS_TIER="" RUN_ID="" -TIMEOUT=${VALIDATION_TIMEOUT_S:-600} +TIMEOUT=${VALIDATION_TIMEOUT_S:-1800} while [[ $# -gt 0 ]]; do case $1 in @@ -114,7 +114,7 @@ PYEOF remaining=$((end - SECONDS)) echo " Waiting... (${remaining}s remaining)" - sleep 15 + sleep 300 done echo "ERROR: Timeout after ${TIMEOUT}s — no result at s3://${RESULTS_BUCKET}/${KEY}" diff --git a/.github/workflows/validate-layer.yml b/.github/workflows/validate-layer.yml index b976754c..e6f359cb 100644 --- a/.github/workflows/validate-layer.yml +++ b/.github/workflows/validate-layer.yml @@ -46,6 +46,7 @@ on: jobs: validate: runs-on: ubuntu-latest + timeout-minutes: 35 steps: - uses: actions/checkout@v4 From 8e78d7969444282c49df7ff364290eb37574bde6 Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Mon, 27 Apr 2026 14:56:49 +0530 Subject: [PATCH 04/16] feat: implement run_region_loop for layer publishing across all runtimes to improve error handling and reporting --- .github/workflows/publish-dotnet.yml | 5 ++- .github/workflows/publish-extension.yml | 5 ++- .github/workflows/publish-java.yml | 13 +++++- .github/workflows/publish-node.yml | 13 +++++- .github/workflows/publish-python.yml | 9 +++- .github/workflows/publish-ruby.yml | 6 ++- dotnet/publish-layers.sh | 8 +--- extension/publish-layer.sh | 8 +--- java/publish-layers.sh | 32 ++++---------- libBuild.sh | 58 +++++++++++++++++++++++++ nodejs/publish-layers.sh | 8 +--- python/publish-layers.sh | 10 +---- ruby/publish-layers.sh | 6 +-- 13 files changed, 119 insertions(+), 62 deletions(-) diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index 83e30d91..3b7209c5 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -70,6 +70,8 @@ jobs: needs: [stage-dotnet, validate-dotnet] if: needs.stage-dotnet.outputs.tag-match == 'true' runs-on: ubuntu-latest + outputs: + failure_summary: ${{ steps.publish.outputs.failure_summary }} steps: - uses: actions/checkout@v4 - name: Set up QEMU @@ -77,6 +79,7 @@ jobs: with: platforms: arm64, amd64 - name: Publish Dotnet Layer + id: publish env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -97,5 +100,5 @@ jobs: webhook-type: incoming-webhook payload: | { - "text": "${{ needs.publish-dotnet.result == 'success' && ':white_check_mark:' || ':x:' }} *dotnet10* layer release ${{ needs.publish-dotnet.result == 'success' && 'succeeded' || 'FAILED' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + "text": "${{ needs.publish-dotnet.result == 'success' && ':white_check_mark:' || ':x:' }} *dotnet10* layer release ${{ needs.publish-dotnet.result == 'success' && 'succeeded' || 'FAILED' }}${{ needs.publish-dotnet.outputs.failure_summary != '' && format(' — {0}', needs.publish-dotnet.outputs.failure_summary) || '' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" } diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index 52de68e0..fbcb57e4 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -88,6 +88,8 @@ jobs: needs: [stage-extension, validate-al2, validate-al2023] if: needs.stage-extension.outputs.tag-match == 'true' runs-on: ubuntu-latest + outputs: + failure_summary: ${{ steps.publish.outputs.failure_summary }} steps: - uses: actions/checkout@v4 - name: Install publish dependencies @@ -97,6 +99,7 @@ jobs: with: platforms: arm64, amd64 - name: Publish extension layer + id: publish env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -117,5 +120,5 @@ jobs: webhook-type: incoming-webhook payload: | { - "text": "${{ needs.publish-extension.result == 'success' && ':white_check_mark:' || ':x:' }} *extension (go/al2+al2023)* layer release ${{ needs.publish-extension.result == 'success' && 'succeeded' || 'FAILED' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + "text": "${{ needs.publish-extension.result == 'success' && ':white_check_mark:' || ':x:' }} *extension (go/al2+al2023)* layer release ${{ needs.publish-extension.result == 'success' && 'succeeded' || 'FAILED' }}${{ needs.publish-extension.outputs.failure_summary != '' && format(' — {0}', needs.publish-extension.outputs.failure_summary) || '' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" } diff --git a/.github/workflows/publish-java.yml b/.github/workflows/publish-java.yml index abd745f4..ca21bae4 100644 --- a/.github/workflows/publish-java.yml +++ b/.github/workflows/publish-java.yml @@ -75,8 +75,11 @@ jobs: if: needs.stage-java.outputs.tag-match == 'true' runs-on: ubuntu-latest strategy: + fail-fast: false matrix: java-version: [java8al2, java11, java17, java21] + outputs: + failure_summary: ${{ steps.publish.outputs.failure_summary }} steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # pin@v4 - name: Set up Java version @@ -99,12 +102,18 @@ jobs: if: matrix.java-version == 'java21' run: make extract-java21-artifacts - name: Publish ${{ matrix.java-version }} layer + id: publish env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: | if [[ "${{ matrix.java-version }}" == "java21" ]]; then - docker run -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY newrelic-lambda-layers-java21 + docker run \ + -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \ + -e GITHUB_OUTPUT -e GITHUB_STEP_SUMMARY \ + -v "$GITHUB_OUTPUT:$GITHUB_OUTPUT" \ + -v "$GITHUB_STEP_SUMMARY:$GITHUB_STEP_SUMMARY" \ + newrelic-lambda-layers-java21 else make publish-${{ matrix.java-version }}-ci fi @@ -133,5 +142,5 @@ jobs: webhook-type: incoming-webhook payload: | { - "text": "${{ needs.publish-java.result == 'success' && ':white_check_mark:' || ':x:' }} *java* layers (8al2 / 11 / 17 / 21) release ${{ needs.publish-java.result == 'success' && 'succeeded' || 'FAILED' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + "text": "${{ needs.publish-java.result == 'success' && ':white_check_mark:' || ':x:' }} *java* layers (8al2 / 11 / 17 / 21) release ${{ needs.publish-java.result == 'success' && 'succeeded' || 'FAILED' }}${{ needs.publish-java.outputs.failure_summary != '' && format(' — {0}', needs.publish-java.outputs.failure_summary) || '' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" } diff --git a/.github/workflows/publish-node.yml b/.github/workflows/publish-node.yml index 2b73ceab..52cbe3a5 100644 --- a/.github/workflows/publish-node.yml +++ b/.github/workflows/publish-node.yml @@ -75,8 +75,11 @@ jobs: if: needs.stage-node.outputs.tag-match == 'true' runs-on: ubuntu-latest strategy: + fail-fast: false matrix: node-version: [20, 22, 24] + outputs: + failure_summary: ${{ steps.publish.outputs.failure_summary }} steps: - uses: actions/checkout@v4 - name: Setup @@ -86,6 +89,7 @@ jobs: - name: Build layer artifacts run: make extract-nodejs${{ matrix.node-version }}-artifacts - name: Publish release layers + id: publish env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -93,6 +97,10 @@ jobs: docker run \ -e AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY \ + -e GITHUB_OUTPUT \ + -e GITHUB_STEP_SUMMARY \ + -v "$GITHUB_OUTPUT:$GITHUB_OUTPUT" \ + -v "$GITHUB_STEP_SUMMARY:$GITHUB_STEP_SUMMARY" \ newrelic-lambda-layers-nodejs${{ matrix.node-version }} - name: Publish ECR image for nodejs${{ matrix.node-version }} env: @@ -114,6 +122,8 @@ jobs: needs: [stage-node, validate-node] if: needs.stage-node.outputs.tag-match == 'true' runs-on: ubuntu-latest + outputs: + failure_summary: ${{ steps.publish-universal.outputs.failure_summary }} steps: - uses: actions/checkout@v4 - name: Setup @@ -121,6 +131,7 @@ jobs: with: node-version: 22 - name: Publish universal layer + id: publish-universal env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -153,5 +164,5 @@ jobs: webhook-type: incoming-webhook payload: | { - "text": "${{ (needs.publish-node.result == 'success' && needs.publish-node-universal.result == 'success') && ':white_check_mark:' || ':x:' }} *nodejs* layers (20 / 22 / 24 / universal) release ${{ (needs.publish-node.result == 'success' && needs.publish-node-universal.result == 'success') && 'succeeded' || 'FAILED' }} — release: ${{ needs.publish-node.result }}, universal: ${{ needs.publish-node-universal.result }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + "text": "${{ (needs.publish-node.result == 'success' && needs.publish-node-universal.result == 'success') && ':white_check_mark:' || ':x:' }} *nodejs* layers (20 / 22 / 24 / universal) release ${{ (needs.publish-node.result == 'success' && needs.publish-node-universal.result == 'success') && 'succeeded' || 'FAILED' }} — release: ${{ needs.publish-node.result }}, universal: ${{ needs.publish-node-universal.result }}${{ needs.publish-node.outputs.failure_summary != '' && format(' — {0}', needs.publish-node.outputs.failure_summary) || needs.publish-node-universal.outputs.failure_summary != '' && format(' — {0}', needs.publish-node-universal.outputs.failure_summary) || '' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" } diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index c74c2782..266dcae0 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -64,13 +64,17 @@ jobs: if: needs.stage-python.outputs.tag-match == 'true' runs-on: ubuntu-latest strategy: + fail-fast: false matrix: python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] + outputs: + failure_summary: ${{ steps.publish.outputs.failure_summary }} steps: - uses: actions/checkout@v4 - name: Setup uses: ./.github/actions/python-layer-setup - name: Publish Python ${{ matrix.python-version }} layer + id: publish env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -83,11 +87,14 @@ jobs: needs: [stage-python, validate-python] if: needs.stage-python.outputs.tag-match == 'true' runs-on: ubuntu-latest + outputs: + failure_summary: ${{ steps.publish-universal.outputs.failure_summary }} steps: - uses: actions/checkout@v4 - name: Setup uses: ./.github/actions/python-layer-setup - name: Publish universal Python layer + id: publish-universal env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -108,5 +115,5 @@ jobs: webhook-type: incoming-webhook payload: | { - "text": "${{ (needs.publish-python.result == 'success' && needs.publish-python-universal.result == 'success') && ':white_check_mark:' || ':x:' }} *python* layers (3.9–3.14 + universal) release ${{ (needs.publish-python.result == 'success' && needs.publish-python-universal.result == 'success') && 'succeeded' || 'FAILED' }} — release: ${{ needs.publish-python.result }}, universal: ${{ needs.publish-python-universal.result }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + "text": "${{ (needs.publish-python.result == 'success' && needs.publish-python-universal.result == 'success') && ':white_check_mark:' || ':x:' }} *python* layers (3.9–3.14 + universal) release ${{ (needs.publish-python.result == 'success' && needs.publish-python-universal.result == 'success') && 'succeeded' || 'FAILED' }} — release: ${{ needs.publish-python.result }}, universal: ${{ needs.publish-python-universal.result }}${{ needs.publish-python.outputs.failure_summary != '' && format(' — {0}', needs.publish-python.outputs.failure_summary) || needs.publish-python-universal.outputs.failure_summary != '' && format(' — {0}', needs.publish-python-universal.outputs.failure_summary) || '' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" } diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index 6b4f6307..89a98c42 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -81,8 +81,11 @@ jobs: if: needs.stage-ruby.outputs.tag-match == 'true' runs-on: ubuntu-latest strategy: + fail-fast: false matrix: ruby-version: [3.2, 3.3, 3.4] + outputs: + failure_summary: ${{ steps.publish.outputs.failure_summary }} steps: - uses: actions/checkout@v4.2.2 - name: Use Ruby ${{ matrix.ruby-version }} @@ -100,6 +103,7 @@ jobs: with: platforms: arm64, amd64 - name: Build and Publish layer + id: publish env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -119,5 +123,5 @@ jobs: webhook-type: incoming-webhook payload: | { - "text": "${{ needs.publish-ruby.result == 'success' && ':white_check_mark:' || ':x:' }} *ruby* layers (3.2 / 3.3 / 3.4) release ${{ needs.publish-ruby.result == 'success' && 'succeeded' || 'FAILED' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" + "text": "${{ needs.publish-ruby.result == 'success' && ':white_check_mark:' || ':x:' }} *ruby* layers (3.2 / 3.3 / 3.4) release ${{ needs.publish-ruby.result == 'success' && 'succeeded' || 'FAILED' }}${{ needs.publish-ruby.outputs.failure_summary != '' && format(' — {0}', needs.publish-ruby.outputs.failure_summary) || '' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" } diff --git a/dotnet/publish-layers.sh b/dotnet/publish-layers.sh index 0d9a1ad5..53a8187a 100755 --- a/dotnet/publish-layers.sh +++ b/dotnet/publish-layers.sh @@ -36,9 +36,7 @@ function publish-dotnet-x86-64 { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $DOTNET_DIST_X86_64 $region dotnet x86_64 $NEWRELIC_AGENT_VERSION - done + run_region_loop "$DOTNET_DIST_X86_64" dotnet x86_64 "$NEWRELIC_AGENT_VERSION" publish_docker_ecr $DOTNET_DIST_X86_64 dotnet x86_64 } @@ -61,9 +59,7 @@ function publish-dotnet-arm64 { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $DOTNET_DIST_ARM64 $region dotnet arm64 $NEWRELIC_AGENT_VERSION - done + run_region_loop "$DOTNET_DIST_ARM64" dotnet arm64 "$NEWRELIC_AGENT_VERSION" publish_docker_ecr $DOTNET_DIST_ARM64 dotnet arm64 } diff --git a/extension/publish-layer.sh b/extension/publish-layer.sh index fb5dcf33..a7622151 100755 --- a/extension/publish-layer.sh +++ b/extension/publish-layer.sh @@ -31,9 +31,7 @@ function publish-layer-x86 { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $EXTENSION_DIST_ZIP_X86_64 $region provided x86_64 provided - done + run_region_loop "$EXTENSION_DIST_ZIP_X86_64" provided x86_64 provided } function publish-layer-arm64 { @@ -42,9 +40,7 @@ function publish-layer-arm64 { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $EXTENSION_DIST_ZIP_ARM64 $region provided arm64 provided - done + run_region_loop "$EXTENSION_DIST_ZIP_ARM64" provided arm64 provided } function publish-staging { diff --git a/java/publish-layers.sh b/java/publish-layers.sh index 5b5fdab6..37cb092e 100755 --- a/java/publish-layers.sh +++ b/java/publish-layers.sh @@ -65,9 +65,7 @@ function publish-java8al2-arm64 { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $JAVA8_DIST_ARM64 $region java8.al2 arm64 - done + run_region_loop "$JAVA8_DIST_ARM64" java8.al2 arm64 } function publish-java8al2-x86 { @@ -76,9 +74,7 @@ function publish-java8al2-x86 { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $JAVA8_DIST_X86_64 $region java8.al2 x86_64 - done + run_region_loop "$JAVA8_DIST_X86_64" java8.al2 x86_64 } function build-java11-arm64 { @@ -95,9 +91,7 @@ function publish-java11-arm64 { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $JAVA11_DIST_ARM64 $region java11 arm64 - done + run_region_loop "$JAVA11_DIST_ARM64" java11 arm64 } function publish-java11-x86 { @@ -106,9 +100,7 @@ function publish-java11-x86 { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $JAVA11_DIST_X86_64 $region java11 x86_64 - done + run_region_loop "$JAVA11_DIST_X86_64" java11 x86_64 } function build-java17-arm64 { @@ -125,9 +117,7 @@ function publish-java17-arm64 { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $JAVA17_DIST_ARM64 $region java17 arm64 - done + run_region_loop "$JAVA17_DIST_ARM64" java17 arm64 } function publish-java17-x86 { @@ -136,9 +126,7 @@ function publish-java17-x86 { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $JAVA17_DIST_X86_64 $region java17 x86_64 - done + run_region_loop "$JAVA17_DIST_X86_64" java17 x86_64 } function build-java21-arm64 { @@ -155,9 +143,7 @@ function publish-java21-arm64 { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $JAVA21_DIST_ARM64 $region java21 arm64 - done + run_region_loop "$JAVA21_DIST_ARM64" java21 arm64 } function publish-java21-x86 { @@ -166,9 +152,7 @@ function publish-java21-x86 { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $JAVA21_DIST_X86_64 $region java21 x86_64 - done + run_region_loop "$JAVA21_DIST_X86_64" java21 x86_64 } case "$1" in diff --git a/libBuild.sh b/libBuild.sh index e436809b..2f5cd121 100644 --- a/libBuild.sh +++ b/libBuild.sh @@ -503,3 +503,61 @@ function publish_docker_hub { echo "docker push newrelic/newrelic-lambda-layers:${language_flag}-${version_flag}${arch_flag}" docker push newrelic/newrelic-lambda-layers:${language_flag}-${version_flag}${arch_flag} } + +# Calls publish_layer for one region; prints [OK]/[FAIL] and returns 0/1. +# Args: [version] [slim] +publish_layer_safe() { + local region="${2}" + if publish_layer "$@"; then + echo " [OK] ${region}" + return 0 + else + echo " [FAIL] ${region} — skipped (see AWS error above)" + return 1 + fi +} + +# Iterates REGIONS[@], calling publish_layer_safe per region. +# Writes a table to $GITHUB_STEP_SUMMARY and failure_summary to $GITHUB_OUTPUT. +# Exits 1 (after all regions are attempted) if any region failed. +# Usage: run_region_loop [version] [slim] +run_region_loop() { + local zip=$1 + shift + local extra_args=("$@") + local -a failed=() passed=() + + for region in "${REGIONS[@]}"; do + if publish_layer_safe "$zip" "$region" "${extra_args[@]}"; then + passed+=("$region") + else + failed+=("$region") + fi + done + + local total=$(( ${#passed[@]} + ${#failed[@]} )) + local label="${extra_args[0]:-layer} ${extra_args[1]:-}" + + if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then + { + printf "### Region Publish: %s\n" "$label" + printf "| Region | Status |\n|--------|--------|\n" + for r in "${passed[@]}"; do printf "| \`%s\` | ✅ passed |\n" "$r"; done + for r in "${failed[@]}"; do printf "| \`%s\` | ❌ FAILED |\n" "$r"; done + } >> "$GITHUB_STEP_SUMMARY" + fi + + if [[ -n "${GITHUB_OUTPUT:-}" && ${#failed[@]} -gt 0 ]]; then + echo "failure_summary=${#failed[@]}/${total} regions failed: ${failed[*]}" >> "$GITHUB_OUTPUT" + fi + + echo "" + echo "=== Region Publish Summary: ${label} ===" + echo " Passed (${#passed[@]}/${total}): ${passed[*]:-none}" + echo " Failed (${#failed[@]}/${total}): ${failed[*]:-none}" + + if [[ ${#failed[@]} -gt 0 ]]; then + echo "ERROR: ${#failed[@]}/${total} region(s) failed" + return 1 + fi +} diff --git a/nodejs/publish-layers.sh b/nodejs/publish-layers.sh index eaa6ec81..a39cb8ce 100755 --- a/nodejs/publish-layers.sh +++ b/nodejs/publish-layers.sh @@ -64,9 +64,7 @@ function publish_universal_wrapper { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $ZIP $region nodejs ${arch} $NEWRELIC_AGENT_VERSION $slim - done + run_region_loop "$ZIP" nodejs "${arch}" "$NEWRELIC_AGENT_VERSION" "$slim" } function build_wrapper { @@ -117,9 +115,7 @@ function publish_wrapper { exit 1 fi - for region in "${REGIONS[@]}"; do - publish_layer $ZIP $region nodejs${node_version}.x ${arch} $NEWRELIC_AGENT_VERSION $slim - done + run_region_loop "$ZIP" "nodejs${node_version}.x" "${arch}" "$NEWRELIC_AGENT_VERSION" "$slim" } # Publish staging layers for a given node version (us-east-1, -staging suffix). diff --git a/python/publish-layers.sh b/python/publish-layers.sh index 3df7eac0..d789a8f6 100755 --- a/python/publish-layers.sh +++ b/python/publish-layers.sh @@ -78,10 +78,7 @@ function publish_python_layer { REGIONS=("${REGIONS[@]}"); fi - for region in "${REGIONS[@]}"; do - echo "Publishing layer for python${python_version} (${arch}) to region ${region}" - publish_layer ${ZIP} $region python${python_version} ${arch} $NEWRELIC_AGENT_VERSION - done + run_region_loop "$ZIP" "python${python_version}" "${arch}" "$NEWRELIC_AGENT_VERSION" } @@ -127,10 +124,7 @@ function publish_universal_python_layer { exit 1 fi - for region in "${REGIONS[@]}"; do - echo "Publishing universal Python layer (${arch}) to region ${region}" - publish_layer ${ZIP} $region python ${arch} $NEWRELIC_AGENT_VERSION - done + run_region_loop "$ZIP" python "${arch}" "$NEWRELIC_AGENT_VERSION" } diff --git a/ruby/publish-layers.sh b/ruby/publish-layers.sh index d9b1e2a9..f45f1ce8 100755 --- a/ruby/publish-layers.sh +++ b/ruby/publish-layers.sh @@ -175,11 +175,7 @@ function publish_ruby_for_arch { local arch=$2 local dist_file=$3 - for region in "${REGIONS[@]}"; do - echo "Publishing $dist_file for region=$region, ruby=$ruby_version, arch=$arch" - publish_layer $dist_file $region "ruby${ruby_version}" $arch $NEWRELIC_AGENT_VERSION - done - echo 'Publishing complete' + run_region_loop "$dist_file" "ruby${ruby_version}" "$arch" "$NEWRELIC_AGENT_VERSION" } set +u # permit $1 to be unbound so that '*' matches it when no args are present From fb8804aff578618fb5f5d8a5ce8a51897937fc78 Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Mon, 27 Apr 2026 15:27:14 +0530 Subject: [PATCH 05/16] =?UTF-8?q?fix:=20region-resilient=20publishing=20?= =?UTF-8?q?=E2=80=94=20continue=20on=20failure,=20report=20failed=20region?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add publish_layer_safe + run_region_loop to libBuild.sh so a single region failure no longer aborts the entire publish loop; all regions are attempted and a pass/fail summary is printed at the end. - Fix empty layer_version propagation: add || return 1 after publish-layer-version command substitution so failures inside publish_public_layer are correctly surfaced when set -e is inactive (i.e. when called from an if-context). - Make GITHUB_STEP_SUMMARY and GITHUB_OUTPUT writes non-fatal (|| true) to avoid permission-denied failures inside Docker containers. - Replace all bare for-region loops in nodejs, python, java, ruby, dotnet, extension publish scripts with run_region_loop. - Add strategy.fail-fast: false to all matrix publish jobs so e.g. node22/node24 are not cancelled when node20 fails. - Mount GITHUB_OUTPUT + GITHUB_STEP_SUMMARY into Docker steps (node, java21) so failure_summary can propagate out of the container. - Add outputs: failure_summary to every publish job; include "N/total regions failed: " in all Slack payloads when set. --- libBuild.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libBuild.sh b/libBuild.sh index 2f5cd121..c09ea2a0 100644 --- a/libBuild.sh +++ b/libBuild.sh @@ -250,7 +250,7 @@ function publish_public_layer { --compatible-runtimes ${compat_list[*]} \ --region "$region" \ --output text \ - --query Version) + --query Version) || return 1 echo "Published ${runtime_name} layer version ${layer_version} to ${region}" echo "Setting public permissions for ${runtime_name} layer version ${layer_version} in ${region}" @@ -544,11 +544,11 @@ run_region_loop() { printf "| Region | Status |\n|--------|--------|\n" for r in "${passed[@]}"; do printf "| \`%s\` | ✅ passed |\n" "$r"; done for r in "${failed[@]}"; do printf "| \`%s\` | ❌ FAILED |\n" "$r"; done - } >> "$GITHUB_STEP_SUMMARY" + } >> "$GITHUB_STEP_SUMMARY" 2>/dev/null || true fi if [[ -n "${GITHUB_OUTPUT:-}" && ${#failed[@]} -gt 0 ]]; then - echo "failure_summary=${#failed[@]}/${total} regions failed: ${failed[*]}" >> "$GITHUB_OUTPUT" + echo "failure_summary=${#failed[@]}/${total} regions failed: ${failed[*]}" >> "$GITHUB_OUTPUT" 2>/dev/null || true fi echo "" From db916d2817d08606fbf0bfc659c96b1d04a92a6f Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Mon, 27 Apr 2026 15:53:16 +0530 Subject: [PATCH 06/16] feat: implement Notify Slack Layer action for structured notifications on layer releases --- .github/actions/notify-slack-layer/action.yml | 116 ++++++++++++++++++ .github/workflows/publish-dotnet.yml | 20 +-- .github/workflows/publish-extension.yml | 20 +-- .github/workflows/publish-java.yml | 26 ++-- .github/workflows/publish-node.yml | 28 +++-- .github/workflows/publish-python.yml | 31 +++-- .github/workflows/publish-ruby.yml | 25 ++-- 7 files changed, 218 insertions(+), 48 deletions(-) create mode 100644 .github/actions/notify-slack-layer/action.yml diff --git a/.github/actions/notify-slack-layer/action.yml b/.github/actions/notify-slack-layer/action.yml new file mode 100644 index 00000000..ab52100c --- /dev/null +++ b/.github/actions/notify-slack-layer/action.yml @@ -0,0 +1,116 @@ +name: Notify Slack Layer Release +description: Builds and sends a structured Slack notification for lambda layer releases + +inputs: + language_name: + description: Language display name, e.g. "Node.js" + required: true + versions_json: + description: | + JSON array: [{"key":"20","label":"Node.js 20","job":"publish-node (20)","fallback":"success"}] + - key: unique identifier + - label: display label in Slack message + - job: GitHub Actions job name (used for per-matrix API lookup) + - fallback: result to use if API lookup finds no match + required: true + failure_summaries: + description: Newline-separated failure_summary strings from publish jobs + required: false + default: "" + slack_webhook: + description: Slack incoming webhook URL + required: true + gh_token: + required: true + repo: + required: true + run_id: + required: true + ref_name: + required: true + actor: + required: true + server_url: + required: true + +runs: + using: composite + steps: + - name: Build Slack payload + shell: bash + env: + LANGUAGE_NAME: ${{ inputs.language_name }} + VERSIONS_JSON: ${{ inputs.versions_json }} + FAILURE_SUMMARIES: ${{ inputs.failure_summaries }} + GH_TOKEN: ${{ inputs.gh_token }} + REPO: ${{ inputs.repo }} + RUN_ID: ${{ inputs.run_id }} + TAG: ${{ inputs.ref_name }} + ACTOR: ${{ inputs.actor }} + SERVER_URL: ${{ inputs.server_url }} + run: | + python3 << 'PYEOF' + import json, os, subprocess + + versions = json.loads(os.environ["VERSIONS_JSON"]) + + r = subprocess.run( + ["gh", "api", "--paginate", + f"repos/{os.environ['REPO']}/actions/runs/{os.environ['RUN_ID']}/jobs", + "-q", ".jobs[]"], + capture_output=True, text=True + ) + api_jobs = {} + for line in r.stdout.strip().splitlines(): + try: + job = json.loads(line) + api_jobs[job["name"]] = job.get("conclusion") or "in_progress" + except Exception: + pass + + results = {} + for v in versions: + results[v["key"]] = api_jobs.get(v["job"], v["fallback"]) + + total = len(versions) + failed_count = sum(1 for v in versions if results.get(v["key"]) != "success") + all_ok = failed_count == 0 + + lang = os.environ["LANGUAGE_NAME"] + icon = ":white_check_mark:" if all_ok else ":x:" + status = "Succeeded" if all_ok else f"Failed ({failed_count}/{total} versions failed)" + lines = [ + f"{icon} *{lang} Layer Release {status}*", + f"Tag: `{os.environ['TAG']}`", + f"Triggered by: {os.environ['ACTOR']}", + "", + "*Layer Results:*", + ] + for v in versions: + r = results.get(v["key"], "unknown") + em = ":white_check_mark:" if r == "success" else ":x:" + reason = "Layer published successfully" if r == "success" else "Layer publish to AWS failed" + lines.append(f"{em} {v['label']} — {reason}") + + skipped = [] + for fs in os.environ.get("FAILURE_SUMMARIES", "").splitlines(): + if "regions failed:" in fs: + for region in fs.split("regions failed:")[-1].strip().split(): + if region not in skipped: + skipped.append(region) + if skipped: + lines += ["", ":warning: *Regions skipped (AWS infra errors):*"] + lines += [f"• {r}" for r in skipped] + + run_url = f"{os.environ['SERVER_URL']}/{os.environ['REPO']}/actions/runs/{os.environ['RUN_ID']}" + lines += ["", f"<{run_url}|View Run>"] + + with open("/tmp/slack-payload.json", "w") as f: + json.dump({"text": "\n".join(lines)}, f) + PYEOF + - name: Notify Slack + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ inputs.slack_webhook }} + webhook-type: incoming-webhook + payload-file-path: /tmp/slack-payload.json diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index 3b7209c5..09439a40 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -93,12 +93,16 @@ jobs: if: always() && needs.validate-dotnet.result == 'success' && needs.stage-dotnet.outputs.tag-match == 'true' runs-on: ubuntu-latest steps: - - name: Notify Slack - uses: slackapi/slack-github-action@v2.1.0 + - uses: actions/checkout@v4 + - uses: ./.github/actions/notify-slack-layer with: - webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} - webhook-type: incoming-webhook - payload: | - { - "text": "${{ needs.publish-dotnet.result == 'success' && ':white_check_mark:' || ':x:' }} *dotnet10* layer release ${{ needs.publish-dotnet.result == 'success' && 'succeeded' || 'FAILED' }}${{ needs.publish-dotnet.outputs.failure_summary != '' && format(' — {0}', needs.publish-dotnet.outputs.failure_summary) || '' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" - } + language_name: Dotnet + versions_json: '[{"key":"dotnet10","label":"Dotnet 10","job":"publish-dotnet","fallback":"${{ needs.publish-dotnet.result }}"}]' + failure_summaries: ${{ needs.publish-dotnet.outputs.failure_summary }} + slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + gh_token: ${{ github.token }} + repo: ${{ github.repository }} + run_id: ${{ github.run_id }} + ref_name: ${{ github.ref_name }} + actor: ${{ github.actor }} + server_url: ${{ github.server_url }} diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index fbcb57e4..f831cd94 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -113,12 +113,16 @@ jobs: if: always() && needs.validate-al2.result == 'success' && needs.validate-al2023.result == 'success' && needs.stage-extension.outputs.tag-match == 'true' runs-on: ubuntu-latest steps: - - name: Notify Slack - uses: slackapi/slack-github-action@v2.1.0 + - uses: actions/checkout@v4 + - uses: ./.github/actions/notify-slack-layer with: - webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} - webhook-type: incoming-webhook - payload: | - { - "text": "${{ needs.publish-extension.result == 'success' && ':white_check_mark:' || ':x:' }} *extension (go/al2+al2023)* layer release ${{ needs.publish-extension.result == 'success' && 'succeeded' || 'FAILED' }}${{ needs.publish-extension.outputs.failure_summary != '' && format(' — {0}', needs.publish-extension.outputs.failure_summary) || '' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" - } + language_name: Extension (Go/AL2+AL2023) + versions_json: '[{"key":"extension","label":"Extension (go/al2+al2023)","job":"publish-extension","fallback":"${{ needs.publish-extension.result }}"}]' + failure_summaries: ${{ needs.publish-extension.outputs.failure_summary }} + slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + gh_token: ${{ github.token }} + repo: ${{ github.repository }} + run_id: ${{ github.run_id }} + ref_name: ${{ github.ref_name }} + actor: ${{ github.actor }} + server_url: ${{ github.server_url }} diff --git a/.github/workflows/publish-java.yml b/.github/workflows/publish-java.yml index ca21bae4..dc156f5d 100644 --- a/.github/workflows/publish-java.yml +++ b/.github/workflows/publish-java.yml @@ -135,12 +135,22 @@ jobs: if: always() && needs.validate-java.result == 'success' && needs.stage-java.outputs.tag-match == 'true' runs-on: ubuntu-latest steps: - - name: Notify Slack - uses: slackapi/slack-github-action@v2.1.0 + - uses: actions/checkout@v4 + - uses: ./.github/actions/notify-slack-layer with: - webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} - webhook-type: incoming-webhook - payload: | - { - "text": "${{ needs.publish-java.result == 'success' && ':white_check_mark:' || ':x:' }} *java* layers (8al2 / 11 / 17 / 21) release ${{ needs.publish-java.result == 'success' && 'succeeded' || 'FAILED' }}${{ needs.publish-java.outputs.failure_summary != '' && format(' — {0}', needs.publish-java.outputs.failure_summary) || '' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" - } + language_name: Java + versions_json: |- + [ + {"key":"java8al2","label":"Java 8 (AL2)","job":"publish-java (java8al2)","fallback":"${{ needs.publish-java.result }}"}, + {"key":"java11","label":"Java 11","job":"publish-java (java11)","fallback":"${{ needs.publish-java.result }}"}, + {"key":"java17","label":"Java 17","job":"publish-java (java17)","fallback":"${{ needs.publish-java.result }}"}, + {"key":"java21","label":"Java 21","job":"publish-java (java21)","fallback":"${{ needs.publish-java.result }}"} + ] + failure_summaries: ${{ needs.publish-java.outputs.failure_summary }} + slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + gh_token: ${{ github.token }} + repo: ${{ github.repository }} + run_id: ${{ github.run_id }} + ref_name: ${{ github.ref_name }} + actor: ${{ github.actor }} + server_url: ${{ github.server_url }} diff --git a/.github/workflows/publish-node.yml b/.github/workflows/publish-node.yml index 52cbe3a5..281f1ff0 100644 --- a/.github/workflows/publish-node.yml +++ b/.github/workflows/publish-node.yml @@ -157,12 +157,24 @@ jobs: if: always() && needs.validate-node.result == 'success' && needs.stage-node.outputs.tag-match == 'true' runs-on: ubuntu-latest steps: - - name: Notify Slack - uses: slackapi/slack-github-action@v2.1.0 + - uses: actions/checkout@v4 + - uses: ./.github/actions/notify-slack-layer with: - webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} - webhook-type: incoming-webhook - payload: | - { - "text": "${{ (needs.publish-node.result == 'success' && needs.publish-node-universal.result == 'success') && ':white_check_mark:' || ':x:' }} *nodejs* layers (20 / 22 / 24 / universal) release ${{ (needs.publish-node.result == 'success' && needs.publish-node-universal.result == 'success') && 'succeeded' || 'FAILED' }} — release: ${{ needs.publish-node.result }}, universal: ${{ needs.publish-node-universal.result }}${{ needs.publish-node.outputs.failure_summary != '' && format(' — {0}', needs.publish-node.outputs.failure_summary) || needs.publish-node-universal.outputs.failure_summary != '' && format(' — {0}', needs.publish-node-universal.outputs.failure_summary) || '' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" - } + language_name: Node.js + versions_json: |- + [ + {"key":"20","label":"Node.js 20","job":"publish-node (20)","fallback":"${{ needs.publish-node.result }}"}, + {"key":"22","label":"Node.js 22","job":"publish-node (22)","fallback":"${{ needs.publish-node.result }}"}, + {"key":"24","label":"Node.js 24","job":"publish-node (24)","fallback":"${{ needs.publish-node.result }}"}, + {"key":"universal","label":"Node.js Universal","job":"publish-node-universal","fallback":"${{ needs.publish-node-universal.result }}"} + ] + failure_summaries: | + ${{ needs.publish-node.outputs.failure_summary }} + ${{ needs.publish-node-universal.outputs.failure_summary }} + slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + gh_token: ${{ github.token }} + repo: ${{ github.repository }} + run_id: ${{ github.run_id }} + ref_name: ${{ github.ref_name }} + actor: ${{ github.actor }} + server_url: ${{ github.server_url }} diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index 266dcae0..4f8e290a 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -108,12 +108,27 @@ jobs: if: always() && needs.validate-python.result == 'success' && needs.stage-python.outputs.tag-match == 'true' runs-on: ubuntu-latest steps: - - name: Notify Slack - uses: slackapi/slack-github-action@v2.1.0 + - uses: actions/checkout@v4 + - uses: ./.github/actions/notify-slack-layer with: - webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} - webhook-type: incoming-webhook - payload: | - { - "text": "${{ (needs.publish-python.result == 'success' && needs.publish-python-universal.result == 'success') && ':white_check_mark:' || ':x:' }} *python* layers (3.9–3.14 + universal) release ${{ (needs.publish-python.result == 'success' && needs.publish-python-universal.result == 'success') && 'succeeded' || 'FAILED' }} — release: ${{ needs.publish-python.result }}, universal: ${{ needs.publish-python-universal.result }}${{ needs.publish-python.outputs.failure_summary != '' && format(' — {0}', needs.publish-python.outputs.failure_summary) || needs.publish-python-universal.outputs.failure_summary != '' && format(' — {0}', needs.publish-python-universal.outputs.failure_summary) || '' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" - } + language_name: Python + versions_json: |- + [ + {"key":"3.9","label":"Python 3.9","job":"publish-python (3.9)","fallback":"${{ needs.publish-python.result }}"}, + {"key":"3.10","label":"Python 3.10","job":"publish-python (3.10)","fallback":"${{ needs.publish-python.result }}"}, + {"key":"3.11","label":"Python 3.11","job":"publish-python (3.11)","fallback":"${{ needs.publish-python.result }}"}, + {"key":"3.12","label":"Python 3.12","job":"publish-python (3.12)","fallback":"${{ needs.publish-python.result }}"}, + {"key":"3.13","label":"Python 3.13","job":"publish-python (3.13)","fallback":"${{ needs.publish-python.result }}"}, + {"key":"3.14","label":"Python 3.14","job":"publish-python (3.14)","fallback":"${{ needs.publish-python.result }}"}, + {"key":"universal","label":"Python Universal","job":"publish-python-universal","fallback":"${{ needs.publish-python-universal.result }}"} + ] + failure_summaries: | + ${{ needs.publish-python.outputs.failure_summary }} + ${{ needs.publish-python-universal.outputs.failure_summary }} + slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + gh_token: ${{ github.token }} + repo: ${{ github.repository }} + run_id: ${{ github.run_id }} + ref_name: ${{ github.ref_name }} + actor: ${{ github.actor }} + server_url: ${{ github.server_url }} diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index 89a98c42..a65d9d1f 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -116,12 +116,21 @@ jobs: if: always() && needs.validate-ruby.result == 'success' && needs.stage-ruby.outputs.tag-match == 'true' runs-on: ubuntu-latest steps: - - name: Notify Slack - uses: slackapi/slack-github-action@v2.1.0 + - uses: actions/checkout@v4 + - uses: ./.github/actions/notify-slack-layer with: - webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} - webhook-type: incoming-webhook - payload: | - { - "text": "${{ needs.publish-ruby.result == 'success' && ':white_check_mark:' || ':x:' }} *ruby* layers (3.2 / 3.3 / 3.4) release ${{ needs.publish-ruby.result == 'success' && 'succeeded' || 'FAILED' }}${{ needs.publish-ruby.outputs.failure_summary != '' && format(' — {0}', needs.publish-ruby.outputs.failure_summary) || '' }} — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run #${{ github.run_id }}>" - } + language_name: Ruby + versions_json: |- + [ + {"key":"3.2","label":"Ruby 3.2","job":"publish-ruby (3.2)","fallback":"${{ needs.publish-ruby.result }}"}, + {"key":"3.3","label":"Ruby 3.3","job":"publish-ruby (3.3)","fallback":"${{ needs.publish-ruby.result }}"}, + {"key":"3.4","label":"Ruby 3.4","job":"publish-ruby (3.4)","fallback":"${{ needs.publish-ruby.result }}"} + ] + failure_summaries: ${{ needs.publish-ruby.outputs.failure_summary }} + slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} + gh_token: ${{ github.token }} + repo: ${{ github.repository }} + run_id: ${{ github.run_id }} + ref_name: ${{ github.ref_name }} + actor: ${{ github.actor }} + server_url: ${{ github.server_url }} From aeac43aeb4b4a571cd091519979d233068aaa90f Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Tue, 28 Apr 2026 11:23:56 +0530 Subject: [PATCH 07/16] feat: add automatic region retry on re-run for all publish workflows --- .github/actions/node-layer-setup/action.yml | 3 +- .github/actions/python-layer-setup/action.yml | 3 +- .github/actions/region-retry/action.yml | 105 ++++++++++++++++++ .github/workflows/publish-dotnet.yml | 27 ++++- .github/workflows/publish-extension.yml | 27 ++++- .github/workflows/publish-java.yml | 28 ++++- .github/workflows/publish-node.yml | 43 +++++++ .github/workflows/publish-python.yml | 42 +++++++ .github/workflows/publish-ruby.yml | 27 ++++- libBuild.sh | 17 ++- 10 files changed, 315 insertions(+), 7 deletions(-) create mode 100644 .github/actions/region-retry/action.yml diff --git a/.github/actions/node-layer-setup/action.yml b/.github/actions/node-layer-setup/action.yml index 4a0c206d..d4ebab5a 100644 --- a/.github/actions/node-layer-setup/action.yml +++ b/.github/actions/node-layer-setup/action.yml @@ -22,7 +22,8 @@ runs: id: check-tag shell: bash run: | - if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_nodejs$ ]]; then + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]] || \ + [[ "${{ github.event.ref }}" =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_nodejs$ ]]; then echo "match=true" >> $GITHUB_OUTPUT fi - name: Run Node unit tests diff --git a/.github/actions/python-layer-setup/action.yml b/.github/actions/python-layer-setup/action.yml index ce8149b9..95763f70 100644 --- a/.github/actions/python-layer-setup/action.yml +++ b/.github/actions/python-layer-setup/action.yml @@ -13,7 +13,8 @@ runs: id: check-tag shell: bash run: | - if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_python$ ]]; then + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]] || \ + [[ "${{ github.event.ref }}" =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_python$ ]]; then echo "match=true" >> $GITHUB_OUTPUT fi - name: Install python dependencies diff --git a/.github/actions/region-retry/action.yml b/.github/actions/region-retry/action.yml new file mode 100644 index 00000000..9a7f9763 --- /dev/null +++ b/.github/actions/region-retry/action.yml @@ -0,0 +1,105 @@ +name: Region Retry State +description: | + Save failed regions after a publish attempt, or load them before a re-run. + On attempt N > 1, load automatically restricts publishing to the regions + that failed in attempt N-1. This makes "Re-run failed jobs" smart: it only + retries the regions that actually need it. + + Usage in a publish job: + # Before publish step: + - uses: ./.github/actions/region-retry + id: region-retry-load + with: { mode: load, job_key: nodejs-20, run_id: ..., run_attempt: ... } + + # In publish step env: + # PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} + + # After publish step (if: always()): + - uses: ./.github/actions/region-retry + with: { mode: save, job_key: nodejs-20, failure_summary: ..., run_id: ..., run_attempt: ... } + +inputs: + mode: + description: '"save" — write failed regions artifact after publish. "load" — read it before publish.' + required: true + job_key: + description: 'Unique key for this publish job, e.g. "nodejs-20", "python-3.9", "java-java21".' + required: true + failure_summary: + description: 'failure_summary output from the publish step (save mode only).' + required: false + default: '' + run_id: + description: '${{ github.run_id }}' + required: true + run_attempt: + description: '${{ github.run_attempt }}' + required: true + +outputs: + publish_regions: + description: 'Comma-separated regions to target (empty = all). Set PUBLISH_REGIONS env var to this value.' + value: ${{ steps.load-regions.outputs.publish_regions }} + +runs: + using: composite + steps: + # ── SAVE: write failed regions to an artifact named by attempt number ───── + - name: Write failed-regions file + if: inputs.mode == 'save' + id: write-file + shell: bash + run: | + fs="${{ inputs.failure_summary }}" + outfile="/tmp/failed-regions-${{ inputs.job_key }}.txt" + if [[ "$fs" == *"regions failed:"* ]]; then + regions="${fs#*regions failed: }" + printf '%s' "${regions// /,}" > "$outfile" + echo "has_failures=true" >> "$GITHUB_OUTPUT" + echo "Captured failed regions: ${regions}" + else + echo "has_failures=false" >> "$GITHUB_OUTPUT" + fi + + - name: Upload failed-regions artifact + if: inputs.mode == 'save' && steps.write-file.outputs.has_failures == 'true' + uses: actions/upload-artifact@v4 + with: + name: failed-regions-${{ inputs.job_key }}-${{ inputs.run_id }}-attempt-${{ inputs.run_attempt }} + path: /tmp/failed-regions-${{ inputs.job_key }}.txt + retention-days: 7 + + # ── LOAD: on re-run, download the previous attempt's failed-regions ──────── + - name: Compute previous attempt artifact name + if: inputs.mode == 'load' + id: artifact-name + shell: bash + run: | + prev=$(( ${{ inputs.run_attempt }} - 1 )) + echo "name=failed-regions-${{ inputs.job_key }}-${{ inputs.run_id }}-attempt-${prev}" >> "$GITHUB_OUTPUT" + + - name: Download previous failed-regions artifact + if: inputs.mode == 'load' && fromJSON(inputs.run_attempt) > 1 + id: download + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: ${{ steps.artifact-name.outputs.name }} + path: /tmp/prev-attempt-${{ inputs.job_key }} + + - name: Set publish_regions output + if: inputs.mode == 'load' + id: load-regions + shell: bash + run: | + regions="" + f="/tmp/prev-attempt-${{ inputs.job_key }}/failed-regions-${{ inputs.job_key }}.txt" + if [[ -f "$f" ]]; then + regions=$(cat "$f") + if [[ -n "$regions" ]]; then + echo "Re-run: restricting to previously failed regions: ${regions}" + else + echo "Previous attempt had no failures — publishing to all regions." + fi + fi + echo "publish_regions=${regions}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index 09439a40..4f208c61 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -4,6 +4,12 @@ on: push: tags: - v**_dotnet + workflow_dispatch: + inputs: + regions: + description: 'Comma-separated regions to (re)publish. Leave empty to publish all regions.' + required: false + default: '' jobs: # ── Job 1: Publish staging (dotnet10) ──────────────────────────────────── @@ -18,7 +24,8 @@ jobs: - name: Check Tag id: dotnet-check-tag run: | - if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_dotnet ]]; then + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]] || \ + [[ "${{ github.event.ref }}" =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_dotnet ]]; then echo "match=true" >> $GITHUB_OUTPUT fi - name: Set up QEMU @@ -78,14 +85,32 @@ jobs: uses: docker/setup-qemu-action@v3 with: platforms: arm64, amd64 + - name: Load failed regions for re-run + id: region-retry-load + uses: ./.github/actions/region-retry + with: + mode: load + job_key: dotnet + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} - name: Publish Dotnet Layer id: publish env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} run: | cd dotnet ./publish-layers.sh + - name: Save failed regions for re-run + if: always() + uses: ./.github/actions/region-retry + with: + mode: save + job_key: dotnet + failure_summary: ${{ steps.publish.outputs.failure_summary }} + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} # ── Job 5: Slack release summary — only after validate passed ───────────── notify-slack: diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index f831cd94..41da95b2 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -4,6 +4,12 @@ on: push: tags: - v**_extension + workflow_dispatch: + inputs: + regions: + description: 'Comma-separated regions to (re)publish. Leave empty to publish all regions.' + required: false + default: '' jobs: # ── Job 1: Publish staging (extension) ─────────────────────────────────── @@ -18,7 +24,8 @@ jobs: - name: Check Tag id: extension-check-tag run: | - if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_extension$ ]]; then + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]] || \ + [[ "${{ github.event.ref }}" =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_extension$ ]]; then echo "match=true" >> $GITHUB_OUTPUT fi - name: Install publish dependencies @@ -98,14 +105,32 @@ jobs: uses: docker/setup-qemu-action@v3 with: platforms: arm64, amd64 + - name: Load failed regions for re-run + id: region-retry-load + uses: ./.github/actions/region-retry + with: + mode: load + job_key: extension + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} - name: Publish extension layer id: publish env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} run: | cd extension ./publish-layer.sh + - name: Save failed regions for re-run + if: always() + uses: ./.github/actions/region-retry + with: + mode: save + job_key: extension + failure_summary: ${{ steps.publish.outputs.failure_summary }} + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} # ── Job 5: Slack release summary — only after both validates passed ──────── notify-slack: diff --git a/.github/workflows/publish-java.yml b/.github/workflows/publish-java.yml index dc156f5d..1b50fccc 100644 --- a/.github/workflows/publish-java.yml +++ b/.github/workflows/publish-java.yml @@ -4,6 +4,12 @@ on: push: tags: - v**_java + workflow_dispatch: + inputs: + regions: + description: 'Comma-separated regions to (re)publish. Leave empty to publish all regions.' + required: false + default: '' jobs: # ── Job 1: Build + publish staging (java21 only) ────────────────────────── @@ -23,7 +29,8 @@ jobs: - name: Check Tag id: java-check-tag run: | - if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_java$ ]]; then + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]] || \ + [[ "${{ github.event.ref }}" =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_java$ ]]; then echo "match=true" >> $GITHUB_OUTPUT fi - name: Build layer artifacts (java21) @@ -101,22 +108,41 @@ jobs: - name: Build layer artifacts (java21 only) if: matrix.java-version == 'java21' run: make extract-java21-artifacts + - name: Load failed regions for re-run + id: region-retry-load + uses: ./.github/actions/region-retry + with: + mode: load + job_key: java-${{ matrix.java-version }} + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} - name: Publish ${{ matrix.java-version }} layer id: publish env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} run: | if [[ "${{ matrix.java-version }}" == "java21" ]]; then docker run \ -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \ -e GITHUB_OUTPUT -e GITHUB_STEP_SUMMARY \ + -e PUBLISH_REGIONS \ -v "$GITHUB_OUTPUT:$GITHUB_OUTPUT" \ -v "$GITHUB_STEP_SUMMARY:$GITHUB_STEP_SUMMARY" \ newrelic-lambda-layers-java21 else make publish-${{ matrix.java-version }}-ci fi + - name: Save failed regions for re-run + if: always() + uses: ./.github/actions/region-retry + with: + mode: save + job_key: java-${{ matrix.java-version }} + failure_summary: ${{ steps.publish.outputs.failure_summary }} + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: diff --git a/.github/workflows/publish-node.yml b/.github/workflows/publish-node.yml index 281f1ff0..00bd750b 100644 --- a/.github/workflows/publish-node.yml +++ b/.github/workflows/publish-node.yml @@ -4,6 +4,12 @@ on: push: tags: - v**_nodejs + workflow_dispatch: + inputs: + regions: + description: 'Comma-separated regions to (re)publish. Leave empty to publish all regions.' + required: false + default: '' jobs: # ── Job 1: Build + publish staging (node24 only) ────────────────────────── @@ -88,20 +94,39 @@ jobs: node-version: ${{ matrix.node-version }} - name: Build layer artifacts run: make extract-nodejs${{ matrix.node-version }}-artifacts + - name: Load failed regions for re-run + id: region-retry-load + uses: ./.github/actions/region-retry + with: + mode: load + job_key: nodejs-${{ matrix.node-version }} + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} - name: Publish release layers id: publish env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} run: | docker run \ -e AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY \ -e GITHUB_OUTPUT \ -e GITHUB_STEP_SUMMARY \ + -e PUBLISH_REGIONS \ -v "$GITHUB_OUTPUT:$GITHUB_OUTPUT" \ -v "$GITHUB_STEP_SUMMARY:$GITHUB_STEP_SUMMARY" \ newrelic-lambda-layers-nodejs${{ matrix.node-version }} + - name: Save failed regions for re-run + if: always() + uses: ./.github/actions/region-retry + with: + mode: save + job_key: nodejs-${{ matrix.node-version }} + failure_summary: ${{ steps.publish.outputs.failure_summary }} + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} - name: Publish ECR image for nodejs${{ matrix.node-version }} env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -130,12 +155,30 @@ jobs: uses: ./.github/actions/node-layer-setup with: node-version: 22 + - name: Load failed regions for re-run + id: region-retry-load + uses: ./.github/actions/region-retry + with: + mode: load + job_key: nodejs-universal + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} - name: Publish universal layer id: publish-universal env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} run: make publish-nodejs-universal-ci + - name: Save failed regions for re-run + if: always() + uses: ./.github/actions/region-retry + with: + mode: save + job_key: nodejs-universal + failure_summary: ${{ steps.publish-universal.outputs.failure_summary }} + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} - name: Publish universal ECR image env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index 4f8e290a..a285b8fe 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -4,6 +4,12 @@ on: push: tags: - v**_python + workflow_dispatch: + inputs: + regions: + description: 'Comma-separated regions to (re)publish. Leave empty to publish all regions.' + required: false + default: '' jobs: # ── Job 1: Publish staging (python3.14 only) ────────────────────────────── @@ -73,14 +79,32 @@ jobs: - uses: actions/checkout@v4 - name: Setup uses: ./.github/actions/python-layer-setup + - name: Load failed regions for re-run + id: region-retry-load + uses: ./.github/actions/region-retry + with: + mode: load + job_key: python-${{ matrix.python-version }} + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} - name: Publish Python ${{ matrix.python-version }} layer id: publish env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} run: | cd python ./publish-layers.sh python${{ matrix.python-version }} + - name: Save failed regions for re-run + if: always() + uses: ./.github/actions/region-retry + with: + mode: save + job_key: python-${{ matrix.python-version }} + failure_summary: ${{ steps.publish.outputs.failure_summary }} + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} # ── Job 5: Release universal — gated on validate passing ───────────────── publish-python-universal: @@ -93,14 +117,32 @@ jobs: - uses: actions/checkout@v4 - name: Setup uses: ./.github/actions/python-layer-setup + - name: Load failed regions for re-run + id: region-retry-load + uses: ./.github/actions/region-retry + with: + mode: load + job_key: python-universal + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} - name: Publish universal Python layer id: publish-universal env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} run: | cd python ./publish-layers.sh python + - name: Save failed regions for re-run + if: always() + uses: ./.github/actions/region-retry + with: + mode: save + job_key: python-universal + failure_summary: ${{ steps.publish-universal.outputs.failure_summary }} + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} # ── Job 6: Slack release summary — only after validate passed ───────────── notify-slack: diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index a65d9d1f..de2d3d59 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -4,6 +4,12 @@ on: push: tags: - v**_ruby + workflow_dispatch: + inputs: + regions: + description: 'Comma-separated regions to (re)publish. Leave empty to publish all regions.' + required: false + default: '' jobs: # ── Job 1: Publish staging (ruby3.4 only) ──────────────────────────────── @@ -22,7 +28,8 @@ jobs: - name: Check Tag id: ruby-check-tag run: | - if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_ruby$ ]]; then + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]] || \ + [[ "${{ github.event.ref }}" =~ ^refs/tags/v[0-9]+(\.[0-9]+)*_ruby$ ]]; then echo "match=true" >> $GITHUB_OUTPUT fi - name: Clean the workspace @@ -102,13 +109,31 @@ jobs: uses: docker/setup-qemu-action@v3 with: platforms: arm64, amd64 + - name: Load failed regions for re-run + id: region-retry-load + uses: ./.github/actions/region-retry + with: + mode: load + job_key: ruby-${{ matrix.ruby-version }} + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} - name: Build and Publish layer id: publish env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} run: ./publish-layers.sh ruby${{ matrix.ruby-version }} working-directory: ruby + - name: Save failed regions for re-run + if: always() + uses: ./.github/actions/region-retry + with: + mode: save + job_key: ruby-${{ matrix.ruby-version }} + failure_summary: ${{ steps.publish.outputs.failure_summary }} + run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} # ── Job 5: Slack release summary — only after validate passed ───────────── notify-slack: diff --git a/libBuild.sh b/libBuild.sh index c09ea2a0..9d5b59b5 100644 --- a/libBuild.sh +++ b/libBuild.sh @@ -527,7 +527,22 @@ run_region_loop() { local extra_args=("$@") local -a failed=() passed=() - for region in "${REGIONS[@]}"; do + # If PUBLISH_REGIONS is set, restrict to that comma-separated list of regions. + # Used by workflow_dispatch re-runs to retry only specific failed regions. + local -a target_regions=("${REGIONS[@]}") + if [[ -n "${PUBLISH_REGIONS:-}" ]]; then + local -a filter + IFS=',' read -ra filter <<< "${PUBLISH_REGIONS//[[:space:]]/}" + target_regions=() + for r in "${REGIONS[@]}"; do + for f in "${filter[@]}"; do + [[ "$r" == "$f" ]] && { target_regions+=("$r"); break; } + done + done + echo "=== Region filter active: targeting ${#target_regions[@]}/${#REGIONS[@]} region(s): ${target_regions[*]:-none} ===" + fi + + for region in "${target_regions[@]}"; do if publish_layer_safe "$zip" "$region" "${extra_args[@]}"; then passed+=("$region") else From 3c6998fa17d0196ba3d384eff15cbcc37eaa44b3 Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Tue, 28 Apr 2026 11:45:10 +0530 Subject: [PATCH 08/16] fix: remove expression syntax from composite action description fields --- .github/actions/region-retry/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/region-retry/action.yml b/.github/actions/region-retry/action.yml index 9a7f9763..a115c16a 100644 --- a/.github/actions/region-retry/action.yml +++ b/.github/actions/region-retry/action.yml @@ -12,7 +12,7 @@ description: | with: { mode: load, job_key: nodejs-20, run_id: ..., run_attempt: ... } # In publish step env: - # PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} + # PUBLISH_REGIONS: steps.region-retry-load.outputs.publish_regions || inputs.regions # After publish step (if: always()): - uses: ./.github/actions/region-retry @@ -30,10 +30,10 @@ inputs: required: false default: '' run_id: - description: '${{ github.run_id }}' + description: 'Pass github.run_id from the calling workflow.' required: true run_attempt: - description: '${{ github.run_attempt }}' + description: 'Pass github.run_attempt from the calling workflow.' required: true outputs: From 60509e76c1d2ed296984ce694f0e4f7735adbb39 Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Tue, 28 Apr 2026 12:16:21 +0530 Subject: [PATCH 09/16] feat: add ECR error handling, per-image failure tracking, and Slack notification --- .github/actions/notify-slack-layer/action.yml | 21 +++++--- .github/workflows/publish-java.yml | 6 ++- .github/workflows/publish-node.yml | 6 +++ java/publish-layers.sh | 20 ++++--- libBuild.sh | 52 +++++++++++++++++++ nodejs/publish-layers.sh | 48 +++++++++-------- 6 files changed, 116 insertions(+), 37 deletions(-) diff --git a/.github/actions/notify-slack-layer/action.yml b/.github/actions/notify-slack-layer/action.yml index ab52100c..e8f2a35f 100644 --- a/.github/actions/notify-slack-layer/action.yml +++ b/.github/actions/notify-slack-layer/action.yml @@ -92,15 +92,24 @@ runs: reason = "Layer published successfully" if r == "success" else "Layer publish to AWS failed" lines.append(f"{em} {v['label']} — {reason}") - skipped = [] + skipped_regions = [] + skipped_ecr = [] for fs in os.environ.get("FAILURE_SUMMARIES", "").splitlines(): + fs = fs.strip() if "regions failed:" in fs: for region in fs.split("regions failed:")[-1].strip().split(): - if region not in skipped: - skipped.append(region) - if skipped: - lines += ["", ":warning: *Regions skipped (AWS infra errors):*"] - lines += [f"• {r}" for r in skipped] + if region not in skipped_regions: + skipped_regions.append(region) + elif "ECR images failed:" in fs: + for img in fs.split("ECR images failed:")[-1].strip().split(): + if img not in skipped_ecr: + skipped_ecr.append(img) + if skipped_regions: + lines += ["", ":warning: *Regions with publish errors:*"] + lines += [f"• {r}" for r in skipped_regions] + if skipped_ecr: + lines += ["", ":warning: *ECR images failed to publish:*"] + lines += [f"• {i}" for i in skipped_ecr] run_url = f"{os.environ['SERVER_URL']}/{os.environ['REPO']}/actions/runs/{os.environ['RUN_ID']}" lines += ["", f"<{run_url}|View Run>"] diff --git a/.github/workflows/publish-java.yml b/.github/workflows/publish-java.yml index 1b50fccc..b71832a6 100644 --- a/.github/workflows/publish-java.yml +++ b/.github/workflows/publish-java.yml @@ -87,6 +87,7 @@ jobs: java-version: [java8al2, java11, java17, java21] outputs: failure_summary: ${{ steps.publish.outputs.failure_summary }} + ecr_failure_summary: ${{ steps.publish-ecr.outputs.ecr_failure_summary }} steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # pin@v4 - name: Set up Java version @@ -148,6 +149,7 @@ jobs: with: platforms: arm64, amd64 - name: Publish ECR image for ${{ matrix.java-version }} + id: publish-ecr env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -172,7 +174,9 @@ jobs: {"key":"java17","label":"Java 17","job":"publish-java (java17)","fallback":"${{ needs.publish-java.result }}"}, {"key":"java21","label":"Java 21","job":"publish-java (java21)","fallback":"${{ needs.publish-java.result }}"} ] - failure_summaries: ${{ needs.publish-java.outputs.failure_summary }} + failure_summaries: | + ${{ needs.publish-java.outputs.failure_summary }} + ${{ needs.publish-java.outputs.ecr_failure_summary }} slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} gh_token: ${{ github.token }} repo: ${{ github.repository }} diff --git a/.github/workflows/publish-node.yml b/.github/workflows/publish-node.yml index 00bd750b..27468a99 100644 --- a/.github/workflows/publish-node.yml +++ b/.github/workflows/publish-node.yml @@ -86,6 +86,7 @@ jobs: node-version: [20, 22, 24] outputs: failure_summary: ${{ steps.publish.outputs.failure_summary }} + ecr_failure_summary: ${{ steps.publish-ecr.outputs.ecr_failure_summary }} steps: - uses: actions/checkout@v4 - name: Setup @@ -128,6 +129,7 @@ jobs: run_id: ${{ github.run_id }} run_attempt: ${{ github.run_attempt }} - name: Publish ECR image for nodejs${{ matrix.node-version }} + id: publish-ecr env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -149,6 +151,7 @@ jobs: runs-on: ubuntu-latest outputs: failure_summary: ${{ steps.publish-universal.outputs.failure_summary }} + ecr_failure_summary: ${{ steps.publish-ecr-universal.outputs.ecr_failure_summary }} steps: - uses: actions/checkout@v4 - name: Setup @@ -180,6 +183,7 @@ jobs: run_id: ${{ github.run_id }} run_attempt: ${{ github.run_attempt }} - name: Publish universal ECR image + id: publish-ecr-universal env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -213,7 +217,9 @@ jobs: ] failure_summaries: | ${{ needs.publish-node.outputs.failure_summary }} + ${{ needs.publish-node.outputs.ecr_failure_summary }} ${{ needs.publish-node-universal.outputs.failure_summary }} + ${{ needs.publish-node-universal.outputs.ecr_failure_summary }} slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} gh_token: ${{ github.token }} repo: ${{ github.repository }} diff --git a/java/publish-layers.sh b/java/publish-layers.sh index 37cb092e..e0b2545e 100755 --- a/java/publish-layers.sh +++ b/java/publish-layers.sh @@ -190,27 +190,31 @@ case "$1" in ;; "build-publish-java8al2-ecr-image") build-java8al2-arm64 - publish_docker_ecr $JAVA8_DIST_ARM64 java8 arm64 + publish_ecr_safe $JAVA8_DIST_ARM64 java8 arm64 build-java8al2-x86 - publish_docker_ecr $JAVA8_DIST_X86_64 java8 x86_64 + publish_ecr_safe $JAVA8_DIST_X86_64 java8 x86_64 + finalize_ecr_results "java8al2" ;; "build-publish-java11-ecr-image") build-java11-arm64 - publish_docker_ecr $JAVA11_DIST_ARM64 java11 arm64 + publish_ecr_safe $JAVA11_DIST_ARM64 java11 arm64 build-java11-x86 - publish_docker_ecr $JAVA11_DIST_X86_64 java11 x86_64 + publish_ecr_safe $JAVA11_DIST_X86_64 java11 x86_64 + finalize_ecr_results "java11" ;; "build-publish-java17-ecr-image") build-java17-arm64 - publish_docker_ecr $JAVA17_DIST_ARM64 java17 arm64 + publish_ecr_safe $JAVA17_DIST_ARM64 java17 arm64 build-java17-x86 - publish_docker_ecr $JAVA17_DIST_X86_64 java17 x86_64 + publish_ecr_safe $JAVA17_DIST_X86_64 java17 x86_64 + finalize_ecr_results "java17" ;; "build-publish-java21-ecr-image") build-java21-arm64 - publish_docker_ecr $JAVA21_DIST_ARM64 java21 arm64 + publish_ecr_safe $JAVA21_DIST_ARM64 java21 arm64 build-java21-x86 - publish_docker_ecr $JAVA21_DIST_X86_64 java21 x86_64 + publish_ecr_safe $JAVA21_DIST_X86_64 java21 x86_64 + finalize_ecr_results "java21" ;; "java8al2") $0 build-java8al2 diff --git a/libBuild.sh b/libBuild.sh index 9d5b59b5..21f08fdc 100644 --- a/libBuild.sh +++ b/libBuild.sh @@ -576,3 +576,55 @@ run_region_loop() { return 1 fi } + +# Global accumulators for ECR publish results within one script invocation. +declare -ga _ECR_PASSED=() +declare -ga _ECR_FAILED=() + +# Calls publish_docker_ecr; accumulates pass/fail into _ECR_PASSED/_ECR_FAILED. +# Never propagates failure — call finalize_ecr_results at the end instead. +# Args: same as publish_docker_ecr ( [slim]) +publish_ecr_safe() { + local label="${2}-${3}${4:+-$4}" + if publish_docker_ecr "$@"; then + echo " [ECR OK] ${label}" + _ECR_PASSED+=("$label") + else + echo " [ECR FAIL] ${label} — see Docker/AWS error above" + _ECR_FAILED+=("$label") + fi + return 0 +} + +# Writes ECR summary table to $GITHUB_STEP_SUMMARY and ecr_failure_summary to +# $GITHUB_OUTPUT, then returns 1 if any images failed. +# Call once after all publish_ecr_safe calls for a given publish block. +# Args: [label] — optional human label for the summary heading +finalize_ecr_results() { + local label="${1:-ECR}" + local total=$(( ${#_ECR_PASSED[@]} + ${#_ECR_FAILED[@]} )) + + if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then + { + printf "### ECR Image Publish: %s\n" "$label" + printf "| Image | Status |\n|-------|--------|\n" + for i in "${_ECR_PASSED[@]}"; do printf "| \`%s\` | ✅ passed |\n" "$i"; done + for i in "${_ECR_FAILED[@]}"; do printf "| \`%s\` | ❌ FAILED |\n" "$i"; done + } >> "$GITHUB_STEP_SUMMARY" 2>/dev/null || true + fi + + echo "" + echo "=== ECR Publish Summary: ${label} ===" + echo " Passed (${#_ECR_PASSED[@]}/${total}): ${_ECR_PASSED[*]:-none}" + echo " Failed (${#_ECR_FAILED[@]}/${total}): ${_ECR_FAILED[*]:-none}" + + if [[ ${#_ECR_FAILED[@]} -gt 0 ]]; then + if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "ecr_failure_summary=${#_ECR_FAILED[@]}/${total} ECR images failed: ${_ECR_FAILED[*]}" \ + >> "$GITHUB_OUTPUT" 2>/dev/null || true + fi + echo "ERROR: ${#_ECR_FAILED[@]}/${total} ECR image(s) failed" + return 1 + fi + return 0 +} diff --git a/nodejs/publish-layers.sh b/nodejs/publish-layers.sh index a39cb8ce..a5d4ccef 100755 --- a/nodejs/publish-layers.sh +++ b/nodejs/publish-layers.sh @@ -169,13 +169,14 @@ case "$1" in ;; "build-publish-universal-ecr-image") build_universal_wrapper arm64 - publish_docker_ecr $DIST_DIR/nodejs.arm64.zip nodejs arm64 + publish_ecr_safe $DIST_DIR/nodejs.arm64.zip nodejs arm64 build_universal_wrapper arm64 slim - publish_docker_ecr $DIST_DIR/nodejs.arm64.slim.zip nodejs arm64 slim + publish_ecr_safe $DIST_DIR/nodejs.arm64.slim.zip nodejs arm64 slim build_universal_wrapper x86_64 - publish_docker_ecr $DIST_DIR/nodejs.x86_64.zip nodejs x86_64 + publish_ecr_safe $DIST_DIR/nodejs.x86_64.zip nodejs x86_64 build_universal_wrapper x86_64 slim - publish_docker_ecr $DIST_DIR/nodejs.x86_64.slim.zip nodejs x86_64 slim + publish_ecr_safe $DIST_DIR/nodejs.x86_64.slim.zip nodejs x86_64 slim + finalize_ecr_results "nodejs-universal" ;; "nodejs") $0 build-universal @@ -224,34 +225,37 @@ case "$1" in publish_wrapper 24 x86_64 slim ;; "build-publish-20-ecr-image") - build_wrapper 20 arm64 - publish_docker_ecr $DIST_DIR/nodejs20x.arm64.zip nodejs20.x arm64 + build_wrapper 20 arm64 + publish_ecr_safe $DIST_DIR/nodejs20x.arm64.zip nodejs20.x arm64 build_wrapper 20 arm64 slim - publish_docker_ecr $DIST_DIR/nodejs20x.arm64.slim.zip nodejs20.x arm64 slim - build_wrapper 20 x86_64 - publish_docker_ecr $DIST_DIR/nodejs20x.x86_64.zip nodejs20.x x86_64 + publish_ecr_safe $DIST_DIR/nodejs20x.arm64.slim.zip nodejs20.x arm64 slim + build_wrapper 20 x86_64 + publish_ecr_safe $DIST_DIR/nodejs20x.x86_64.zip nodejs20.x x86_64 build_wrapper 20 x86_64 slim - publish_docker_ecr $DIST_DIR/nodejs20x.x86_64.slim.zip nodejs20.x x86_64 slim + publish_ecr_safe $DIST_DIR/nodejs20x.x86_64.slim.zip nodejs20.x x86_64 slim + finalize_ecr_results "nodejs20.x" ;; "build-publish-22-ecr-image") - build_wrapper 22 arm64 - publish_docker_ecr $DIST_DIR/nodejs22x.arm64.zip nodejs22.x arm64 + build_wrapper 22 arm64 + publish_ecr_safe $DIST_DIR/nodejs22x.arm64.zip nodejs22.x arm64 build_wrapper 22 arm64 slim - publish_docker_ecr $DIST_DIR/nodejs22x.arm64.slim.zip nodejs22.x arm64 - build_wrapper 22 x86_64 - publish_docker_ecr $DIST_DIR/nodejs22x.x86_64.zip nodejs22.x x86_64 + publish_ecr_safe $DIST_DIR/nodejs22x.arm64.slim.zip nodejs22.x arm64 slim + build_wrapper 22 x86_64 + publish_ecr_safe $DIST_DIR/nodejs22x.x86_64.zip nodejs22.x x86_64 build_wrapper 22 x86_64 slim - publish_docker_ecr $DIST_DIR/nodejs22x.x86_64.slim.zip nodejs22.x x86_64 slim + publish_ecr_safe $DIST_DIR/nodejs22x.x86_64.slim.zip nodejs22.x x86_64 slim + finalize_ecr_results "nodejs22.x" ;; "build-publish-24-ecr-image") - build_wrapper 24 arm64 - publish_docker_ecr $DIST_DIR/nodejs24x.arm64.zip nodejs24.x arm64 + build_wrapper 24 arm64 + publish_ecr_safe $DIST_DIR/nodejs24x.arm64.zip nodejs24.x arm64 build_wrapper 24 arm64 slim - publish_docker_ecr $DIST_DIR/nodejs24x.arm64.slim.zip nodejs24.x arm64 - build_wrapper 24 x86_64 - publish_docker_ecr $DIST_DIR/nodejs24x.x86_64.zip nodejs24.x x86_64 + publish_ecr_safe $DIST_DIR/nodejs24x.arm64.slim.zip nodejs24.x arm64 slim + build_wrapper 24 x86_64 + publish_ecr_safe $DIST_DIR/nodejs24x.x86_64.zip nodejs24.x x86_64 build_wrapper 24 x86_64 slim - publish_docker_ecr $DIST_DIR/nodejs24x.x86_64.slim.zip nodejs24.x x86_64 slim + publish_ecr_safe $DIST_DIR/nodejs24x.x86_64.slim.zip nodejs24.x x86_64 slim + finalize_ecr_results "nodejs24.x" ;; "nodejs20") $0 build-20 From 6a9f62bdba34061302e524449530050e35596aec Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Tue, 28 Apr 2026 12:24:48 +0530 Subject: [PATCH 10/16] feat: make regions, S3 bucket prefix, and ECR repository configurable via env vars --- libBuild.sh | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/libBuild.sh b/libBuild.sh index 21f08fdc..86a8628e 100644 --- a/libBuild.sh +++ b/libBuild.sh @@ -31,6 +31,21 @@ REGIONS=( us-west-2 ) +# Override the default region list for testing: +# LAYER_REGIONS="us-east-1,us-west-2" ./publish-layers.sh ... +if [[ -n "${LAYER_REGIONS:-}" ]]; then + IFS=',' read -ra REGIONS <<< "${LAYER_REGIONS//[[:space:]]/}" + echo "=== LAYER_REGIONS override: publishing to ${#REGIONS[@]} region(s): ${REGIONS[*]} ===" +fi + +# S3 bucket name prefix — bucket per region is "-". +# Override for testing: S3_BUCKET_PREFIX="my-test-bucket-prefix" +S3_BUCKET_PREFIX="${S3_BUCKET_PREFIX:-nr-layers}" + +# Public ECR repository alias — override for testing: +# ECR_REPOSITORY="q6k3q1g1" ./publish-layers.sh build-publish-20-ecr-image +ECR_REPOSITORY="${ECR_REPOSITORY:-x6n7b2o2}" + EXTENSION_DIST_DIR=extensions EXTENSION_DIST_ZIP=extension.zip EXTENSION_DIST_PREVIEW_FILE=preview-extensions-ggqizro707 @@ -278,7 +293,7 @@ function publish_layer { hash=$( hash_file $layer_archive | awk '{ print $1 }' ) - bucket_name="nr-layers-${region}" + bucket_name="${S3_BUCKET_PREFIX}-${region}" s3_key="$( s3_prefix $runtime_name )/${hash}.${arch}.zip" compat_list=( $runtime_name ) @@ -436,9 +451,8 @@ function publish_docker_ecr { echo "File does not start with 'dist/': $file_without_dist" fi - # public ecr repository name - # maintainer can use this("q6k3q1g1") repo name for testing - repository="x6n7b2o2" + # Public ECR repository alias — set ECR_REPOSITORY env var to override + repository="${ECR_REPOSITORY}" # copy dockerfile cp ../Dockerfile.ecrImage . From d8bc3b4313b403109344197645d578a120eb8b11 Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Tue, 28 Apr 2026 13:34:07 +0530 Subject: [PATCH 11/16] fix: pass LAYER_REGIONS, S3_BUCKET_PREFIX, ECR_REPOSITORY from Actions vars to publish steps --- .github/workflows/publish-dotnet.yml | 2 ++ .github/workflows/publish-extension.yml | 2 ++ .github/workflows/publish-java.yml | 5 +++++ .github/workflows/publish-node.yml | 8 ++++++++ .github/workflows/publish-python.yml | 4 ++++ .github/workflows/publish-ruby.yml | 2 ++ 6 files changed, 23 insertions(+) diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index 4f208c61..0e217c34 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -99,6 +99,8 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} + LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} + S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} run: | cd dotnet ./publish-layers.sh diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index 41da95b2..e209fbb0 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -119,6 +119,8 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} + LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} + S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} run: | cd extension ./publish-layer.sh diff --git a/.github/workflows/publish-java.yml b/.github/workflows/publish-java.yml index b71832a6..c49cc893 100644 --- a/.github/workflows/publish-java.yml +++ b/.github/workflows/publish-java.yml @@ -123,12 +123,16 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} + LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} + S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} run: | if [[ "${{ matrix.java-version }}" == "java21" ]]; then docker run \ -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \ -e GITHUB_OUTPUT -e GITHUB_STEP_SUMMARY \ -e PUBLISH_REGIONS \ + -e LAYER_REGIONS \ + -e S3_BUCKET_PREFIX \ -v "$GITHUB_OUTPUT:$GITHUB_OUTPUT" \ -v "$GITHUB_STEP_SUMMARY:$GITHUB_STEP_SUMMARY" \ newrelic-lambda-layers-java21 @@ -153,6 +157,7 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }} run: | cd java ./publish-layers.sh build-publish-${{ matrix.java-version }}-ecr-image diff --git a/.github/workflows/publish-node.yml b/.github/workflows/publish-node.yml index 27468a99..de43d882 100644 --- a/.github/workflows/publish-node.yml +++ b/.github/workflows/publish-node.yml @@ -109,6 +109,8 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} + LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} + S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} run: | docker run \ -e AWS_ACCESS_KEY_ID \ @@ -116,6 +118,8 @@ jobs: -e GITHUB_OUTPUT \ -e GITHUB_STEP_SUMMARY \ -e PUBLISH_REGIONS \ + -e LAYER_REGIONS \ + -e S3_BUCKET_PREFIX \ -v "$GITHUB_OUTPUT:$GITHUB_OUTPUT" \ -v "$GITHUB_STEP_SUMMARY:$GITHUB_STEP_SUMMARY" \ newrelic-lambda-layers-nodejs${{ matrix.node-version }} @@ -133,6 +137,7 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }} run: | cd nodejs ./publish-layers.sh build-publish-${{ matrix.node-version }}-ecr-image @@ -172,6 +177,8 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} + LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} + S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} run: make publish-nodejs-universal-ci - name: Save failed regions for re-run if: always() @@ -187,6 +194,7 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }} run: | cd nodejs ./publish-layers.sh build-publish-universal-ecr-image diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index a285b8fe..51e2315f 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -93,6 +93,8 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} + LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} + S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} run: | cd python ./publish-layers.sh python${{ matrix.python-version }} @@ -131,6 +133,8 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} + LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} + S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} run: | cd python ./publish-layers.sh python diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index de2d3d59..d28e8abd 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -123,6 +123,8 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} + LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} + S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} run: ./publish-layers.sh ruby${{ matrix.ruby-version }} working-directory: ruby - name: Save failed regions for re-run From 62ece9225fb0928e871602dc66735d970ba9f78d Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Tue, 28 Apr 2026 14:25:27 +0530 Subject: [PATCH 12/16] fix: capture region failures from Docker via temp file to fix retry mechanism Docker containers run as root but cannot write to GITHUB_OUTPUT/GITHUB_STEP_SUMMARY (owned by the runner user, mode 600). Failures were silently dropped, so no artifact was ever saved and re-runs always published to all regions. Fix: - run_region_loop writes to FAILED_REGIONS_FILE (new env var) when set - publish-node.yml and publish-java.yml pre-create a world-writable temp file, mount it into Docker, then relay its contents to GITHUB_OUTPUT after docker exits - chmod a+rw GITHUB_STEP_SUMMARY before Docker so the summary table renders --- .github/workflows/publish-java.yml | 13 +++++++++++-- .github/workflows/publish-node.yml | 12 ++++++++++-- libBuild.sh | 12 ++++++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish-java.yml b/.github/workflows/publish-java.yml index c49cc893..8849b02f 100644 --- a/.github/workflows/publish-java.yml +++ b/.github/workflows/publish-java.yml @@ -127,15 +127,24 @@ jobs: S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} run: | if [[ "${{ matrix.java-version }}" == "java21" ]]; then + failed_file="$(mktemp)" + chmod a+rw "$failed_file" "$GITHUB_STEP_SUMMARY" + set +e docker run \ -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \ - -e GITHUB_OUTPUT -e GITHUB_STEP_SUMMARY \ + -e GITHUB_STEP_SUMMARY \ -e PUBLISH_REGIONS \ -e LAYER_REGIONS \ -e S3_BUCKET_PREFIX \ - -v "$GITHUB_OUTPUT:$GITHUB_OUTPUT" \ + -e "FAILED_REGIONS_FILE=${failed_file}" \ -v "$GITHUB_STEP_SUMMARY:$GITHUB_STEP_SUMMARY" \ + -v "${failed_file}:${failed_file}" \ newrelic-lambda-layers-java21 + docker_exit=$? + set -e + [[ -s "${failed_file}" ]] && cat "${failed_file}" >> "$GITHUB_OUTPUT" || true + rm -f "${failed_file}" + exit "${docker_exit}" else make publish-${{ matrix.java-version }}-ci fi diff --git a/.github/workflows/publish-node.yml b/.github/workflows/publish-node.yml index de43d882..0d845a95 100644 --- a/.github/workflows/publish-node.yml +++ b/.github/workflows/publish-node.yml @@ -112,17 +112,25 @@ jobs: LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} run: | + failed_file="$(mktemp)" + chmod a+rw "$failed_file" "$GITHUB_STEP_SUMMARY" + set +e docker run \ -e AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY \ - -e GITHUB_OUTPUT \ -e GITHUB_STEP_SUMMARY \ -e PUBLISH_REGIONS \ -e LAYER_REGIONS \ -e S3_BUCKET_PREFIX \ - -v "$GITHUB_OUTPUT:$GITHUB_OUTPUT" \ + -e "FAILED_REGIONS_FILE=${failed_file}" \ -v "$GITHUB_STEP_SUMMARY:$GITHUB_STEP_SUMMARY" \ + -v "${failed_file}:${failed_file}" \ newrelic-lambda-layers-nodejs${{ matrix.node-version }} + docker_exit=$? + set -e + [[ -s "${failed_file}" ]] && cat "${failed_file}" >> "$GITHUB_OUTPUT" || true + rm -f "${failed_file}" + exit "${docker_exit}" - name: Save failed regions for re-run if: always() uses: ./.github/actions/region-retry diff --git a/libBuild.sh b/libBuild.sh index 86a8628e..d9fee8af 100644 --- a/libBuild.sh +++ b/libBuild.sh @@ -576,8 +576,16 @@ run_region_loop() { } >> "$GITHUB_STEP_SUMMARY" 2>/dev/null || true fi - if [[ -n "${GITHUB_OUTPUT:-}" && ${#failed[@]} -gt 0 ]]; then - echo "failure_summary=${#failed[@]}/${total} regions failed: ${failed[*]}" >> "$GITHUB_OUTPUT" 2>/dev/null || true + if [[ ${#failed[@]} -gt 0 ]]; then + local summary="${#failed[@]}/${total} regions failed: ${failed[*]}" + # Non-Docker steps: write directly to GITHUB_OUTPUT + if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "failure_summary=${summary}" >> "$GITHUB_OUTPUT" 2>/dev/null || true + fi + # Docker steps: write to FAILED_REGIONS_FILE; host runner relays to GITHUB_OUTPUT + if [[ -n "${FAILED_REGIONS_FILE:-}" ]]; then + echo "failure_summary=${summary}" >> "${FAILED_REGIONS_FILE}" 2>/dev/null || true + fi fi echo "" From 6065ca9ce452bfb8c1cef78641178c8317c855e8 Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Tue, 28 Apr 2026 14:39:40 +0530 Subject: [PATCH 13/16] feat: enhance Slack notification action with failure_key and run_attempt inputs for better error tracking --- .github/actions/notify-slack-layer/action.yml | 90 ++++++++++++++----- .github/workflows/publish-dotnet.yml | 4 +- .github/workflows/publish-extension.yml | 4 +- .github/workflows/publish-java.yml | 13 ++- .github/workflows/publish-node.yml | 11 ++- .github/workflows/publish-python.yml | 18 ++-- .github/workflows/publish-ruby.yml | 8 +- 7 files changed, 93 insertions(+), 55 deletions(-) diff --git a/.github/actions/notify-slack-layer/action.yml b/.github/actions/notify-slack-layer/action.yml index e8f2a35f..ff6d7496 100644 --- a/.github/actions/notify-slack-layer/action.yml +++ b/.github/actions/notify-slack-layer/action.yml @@ -7,14 +7,15 @@ inputs: required: true versions_json: description: | - JSON array: [{"key":"20","label":"Node.js 20","job":"publish-node (20)","fallback":"success"}] + JSON array: [{"key":"20","label":"Node.js 20","job":"publish-node (20)","fallback":"success","failure_key":"nodejs-20"}] - key: unique identifier - label: display label in Slack message - job: GitHub Actions job name (used for per-matrix API lookup) - fallback: result to use if API lookup finds no match + - failure_key: job_key used in region-retry (for per-version region failure detail) required: true failure_summaries: - description: Newline-separated failure_summary strings from publish jobs + description: Newline-separated failure_summary strings from publish jobs (used for ECR failures) required: false default: "" slack_webhook: @@ -26,6 +27,10 @@ inputs: required: true run_id: required: true + run_attempt: + description: Pass github.run_attempt from the calling workflow. + required: false + default: "1" ref_name: required: true actor: @@ -45,18 +50,23 @@ runs: GH_TOKEN: ${{ inputs.gh_token }} REPO: ${{ inputs.repo }} RUN_ID: ${{ inputs.run_id }} + RUN_ATTEMPT: ${{ inputs.run_attempt }} TAG: ${{ inputs.ref_name }} ACTOR: ${{ inputs.actor }} SERVER_URL: ${{ inputs.server_url }} run: | python3 << 'PYEOF' - import json, os, subprocess + import json, os, subprocess, glob, re versions = json.loads(os.environ["VERSIONS_JSON"]) + run_id = os.environ["RUN_ID"] + run_attempt = os.environ.get("RUN_ATTEMPT", "1") + repo = os.environ["REPO"] + # ── Fetch per-job results from the GitHub API ──────────────────────── r = subprocess.run( ["gh", "api", "--paginate", - f"repos/{os.environ['REPO']}/actions/runs/{os.environ['RUN_ID']}/jobs", + f"repos/{repo}/actions/runs/{run_id}/jobs", "-q", ".jobs[]"], capture_output=True, text=True ) @@ -72,14 +82,44 @@ runs: for v in versions: results[v["key"]] = api_jobs.get(v["job"], v["fallback"]) - total = len(versions) + # ── Download per-version region failure artifacts ───────────────────── + # Artifacts are named: failed-regions-{job_key}-{run_id}-attempt-{N} + subprocess.run( + ["gh", "run", "download", run_id, + "--pattern", "failed-regions-*", + "--dir", "/tmp/region-artifacts", + "--repo", repo], + capture_output=True, text=True + ) + + version_failures = {} # job_key -> comma-separated failed regions + artifact_base = "/tmp/region-artifacts" + if os.path.isdir(artifact_base): + for artifact_dir in sorted(glob.glob(f"{artifact_base}/failed-regions-*")): + artifact_name = os.path.basename(artifact_dir) + # Strip "failed-regions-" prefix, then split off "-{run_id}-attempt-{N}" + stripped = artifact_name[len("failed-regions-"):] + parts = stripped.rsplit(f"-{run_id}-attempt-", 1) + if len(parts) != 2: + continue + job_key, attempt_num = parts + if attempt_num != run_attempt: + continue + txt_file = os.path.join(artifact_dir, f"failed-regions-{job_key}.txt") + if os.path.isfile(txt_file): + content = open(txt_file).read().strip() + if content: + version_failures[job_key] = content + + # ── Build Slack message ─────────────────────────────────────────────── + total = len(versions) failed_count = sum(1 for v in versions if results.get(v["key"]) != "success") - all_ok = failed_count == 0 + all_ok = failed_count == 0 - lang = os.environ["LANGUAGE_NAME"] - icon = ":white_check_mark:" if all_ok else ":x:" + lang = os.environ["LANGUAGE_NAME"] + icon = ":white_check_mark:" if all_ok else ":x:" status = "Succeeded" if all_ok else f"Failed ({failed_count}/{total} versions failed)" - lines = [ + lines = [ f"{icon} *{lang} Layer Release {status}*", f"Tag: `{os.environ['TAG']}`", f"Triggered by: {os.environ['ACTOR']}", @@ -87,32 +127,34 @@ runs: "*Layer Results:*", ] for v in versions: - r = results.get(v["key"], "unknown") - em = ":white_check_mark:" if r == "success" else ":x:" - reason = "Layer published successfully" if r == "success" else "Layer publish to AWS failed" - lines.append(f"{em} {v['label']} — {reason}") + res = results.get(v["key"], "unknown") + em = ":white_check_mark:" if res == "success" else ":x:" + if res == "success": + detail = "Layer published successfully" + else: + fk = v.get("failure_key", "") + regions_str = version_failures.get(fk, "") + if regions_str: + regions = [r for r in regions_str.split(",") if r] + detail = f"{len(regions)} region(s) failed: {', '.join(regions)}" + else: + detail = "Layer publish to AWS failed" + lines.append(f"{em} {v['label']} — {detail}") - skipped_regions = [] + # ECR failures (from failure_summaries — not stored in artifacts) skipped_ecr = [] for fs in os.environ.get("FAILURE_SUMMARIES", "").splitlines(): fs = fs.strip() - if "regions failed:" in fs: - for region in fs.split("regions failed:")[-1].strip().split(): - if region not in skipped_regions: - skipped_regions.append(region) - elif "ECR images failed:" in fs: + if "ECR images failed:" in fs: for img in fs.split("ECR images failed:")[-1].strip().split(): if img not in skipped_ecr: skipped_ecr.append(img) - if skipped_regions: - lines += ["", ":warning: *Regions with publish errors:*"] - lines += [f"• {r}" for r in skipped_regions] if skipped_ecr: lines += ["", ":warning: *ECR images failed to publish:*"] lines += [f"• {i}" for i in skipped_ecr] - run_url = f"{os.environ['SERVER_URL']}/{os.environ['REPO']}/actions/runs/{os.environ['RUN_ID']}" - lines += ["", f"<{run_url}|View Run>"] + run_url = f"{os.environ['SERVER_URL']}/{repo}/actions/runs/{run_id}" + lines += ["", f"<{run_url}|View Run>"] with open("/tmp/slack-payload.json", "w") as f: json.dump({"text": "\n".join(lines)}, f) diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index 0e217c34..e96feb93 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -124,12 +124,12 @@ jobs: - uses: ./.github/actions/notify-slack-layer with: language_name: Dotnet - versions_json: '[{"key":"dotnet10","label":"Dotnet 10","job":"publish-dotnet","fallback":"${{ needs.publish-dotnet.result }}"}]' - failure_summaries: ${{ needs.publish-dotnet.outputs.failure_summary }} + versions_json: '[{"key":"dotnet10","label":"Dotnet 10","job":"publish-dotnet","fallback":"${{ needs.publish-dotnet.result }}","failure_key":"dotnet"}]' slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} gh_token: ${{ github.token }} repo: ${{ github.repository }} run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} ref_name: ${{ github.ref_name }} actor: ${{ github.actor }} server_url: ${{ github.server_url }} diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index e209fbb0..f31f6e35 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -144,12 +144,12 @@ jobs: - uses: ./.github/actions/notify-slack-layer with: language_name: Extension (Go/AL2+AL2023) - versions_json: '[{"key":"extension","label":"Extension (go/al2+al2023)","job":"publish-extension","fallback":"${{ needs.publish-extension.result }}"}]' - failure_summaries: ${{ needs.publish-extension.outputs.failure_summary }} + versions_json: '[{"key":"extension","label":"Extension (go/al2+al2023)","job":"publish-extension","fallback":"${{ needs.publish-extension.result }}","failure_key":"extension"}]' slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} gh_token: ${{ github.token }} repo: ${{ github.repository }} run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} ref_name: ${{ github.ref_name }} actor: ${{ github.actor }} server_url: ${{ github.server_url }} diff --git a/.github/workflows/publish-java.yml b/.github/workflows/publish-java.yml index 8849b02f..06075ca0 100644 --- a/.github/workflows/publish-java.yml +++ b/.github/workflows/publish-java.yml @@ -183,18 +183,17 @@ jobs: language_name: Java versions_json: |- [ - {"key":"java8al2","label":"Java 8 (AL2)","job":"publish-java (java8al2)","fallback":"${{ needs.publish-java.result }}"}, - {"key":"java11","label":"Java 11","job":"publish-java (java11)","fallback":"${{ needs.publish-java.result }}"}, - {"key":"java17","label":"Java 17","job":"publish-java (java17)","fallback":"${{ needs.publish-java.result }}"}, - {"key":"java21","label":"Java 21","job":"publish-java (java21)","fallback":"${{ needs.publish-java.result }}"} + {"key":"java8al2","label":"Java 8 (AL2)","job":"publish-java (java8al2)","fallback":"${{ needs.publish-java.result }}","failure_key":"java-java8al2"}, + {"key":"java11","label":"Java 11","job":"publish-java (java11)","fallback":"${{ needs.publish-java.result }}","failure_key":"java-java11"}, + {"key":"java17","label":"Java 17","job":"publish-java (java17)","fallback":"${{ needs.publish-java.result }}","failure_key":"java-java17"}, + {"key":"java21","label":"Java 21","job":"publish-java (java21)","fallback":"${{ needs.publish-java.result }}","failure_key":"java-java21"} ] - failure_summaries: | - ${{ needs.publish-java.outputs.failure_summary }} - ${{ needs.publish-java.outputs.ecr_failure_summary }} + failure_summaries: ${{ needs.publish-java.outputs.ecr_failure_summary }} slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} gh_token: ${{ github.token }} repo: ${{ github.repository }} run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} ref_name: ${{ github.ref_name }} actor: ${{ github.actor }} server_url: ${{ github.server_url }} diff --git a/.github/workflows/publish-node.yml b/.github/workflows/publish-node.yml index 0d845a95..2639490b 100644 --- a/.github/workflows/publish-node.yml +++ b/.github/workflows/publish-node.yml @@ -226,20 +226,19 @@ jobs: language_name: Node.js versions_json: |- [ - {"key":"20","label":"Node.js 20","job":"publish-node (20)","fallback":"${{ needs.publish-node.result }}"}, - {"key":"22","label":"Node.js 22","job":"publish-node (22)","fallback":"${{ needs.publish-node.result }}"}, - {"key":"24","label":"Node.js 24","job":"publish-node (24)","fallback":"${{ needs.publish-node.result }}"}, - {"key":"universal","label":"Node.js Universal","job":"publish-node-universal","fallback":"${{ needs.publish-node-universal.result }}"} + {"key":"20","label":"Node.js 20","job":"publish-node (20)","fallback":"${{ needs.publish-node.result }}","failure_key":"nodejs-20"}, + {"key":"22","label":"Node.js 22","job":"publish-node (22)","fallback":"${{ needs.publish-node.result }}","failure_key":"nodejs-22"}, + {"key":"24","label":"Node.js 24","job":"publish-node (24)","fallback":"${{ needs.publish-node.result }}","failure_key":"nodejs-24"}, + {"key":"universal","label":"Node.js Universal","job":"publish-node-universal","fallback":"${{ needs.publish-node-universal.result }}","failure_key":"nodejs-universal"} ] failure_summaries: | - ${{ needs.publish-node.outputs.failure_summary }} ${{ needs.publish-node.outputs.ecr_failure_summary }} - ${{ needs.publish-node-universal.outputs.failure_summary }} ${{ needs.publish-node-universal.outputs.ecr_failure_summary }} slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} gh_token: ${{ github.token }} repo: ${{ github.repository }} run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} ref_name: ${{ github.ref_name }} actor: ${{ github.actor }} server_url: ${{ github.server_url }} diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index 51e2315f..36f4fe3b 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -160,21 +160,19 @@ jobs: language_name: Python versions_json: |- [ - {"key":"3.9","label":"Python 3.9","job":"publish-python (3.9)","fallback":"${{ needs.publish-python.result }}"}, - {"key":"3.10","label":"Python 3.10","job":"publish-python (3.10)","fallback":"${{ needs.publish-python.result }}"}, - {"key":"3.11","label":"Python 3.11","job":"publish-python (3.11)","fallback":"${{ needs.publish-python.result }}"}, - {"key":"3.12","label":"Python 3.12","job":"publish-python (3.12)","fallback":"${{ needs.publish-python.result }}"}, - {"key":"3.13","label":"Python 3.13","job":"publish-python (3.13)","fallback":"${{ needs.publish-python.result }}"}, - {"key":"3.14","label":"Python 3.14","job":"publish-python (3.14)","fallback":"${{ needs.publish-python.result }}"}, - {"key":"universal","label":"Python Universal","job":"publish-python-universal","fallback":"${{ needs.publish-python-universal.result }}"} + {"key":"3.9","label":"Python 3.9","job":"publish-python (3.9)","fallback":"${{ needs.publish-python.result }}","failure_key":"python-3.9"}, + {"key":"3.10","label":"Python 3.10","job":"publish-python (3.10)","fallback":"${{ needs.publish-python.result }}","failure_key":"python-3.10"}, + {"key":"3.11","label":"Python 3.11","job":"publish-python (3.11)","fallback":"${{ needs.publish-python.result }}","failure_key":"python-3.11"}, + {"key":"3.12","label":"Python 3.12","job":"publish-python (3.12)","fallback":"${{ needs.publish-python.result }}","failure_key":"python-3.12"}, + {"key":"3.13","label":"Python 3.13","job":"publish-python (3.13)","fallback":"${{ needs.publish-python.result }}","failure_key":"python-3.13"}, + {"key":"3.14","label":"Python 3.14","job":"publish-python (3.14)","fallback":"${{ needs.publish-python.result }}","failure_key":"python-3.14"}, + {"key":"universal","label":"Python Universal","job":"publish-python-universal","fallback":"${{ needs.publish-python-universal.result }}","failure_key":"python-universal"} ] - failure_summaries: | - ${{ needs.publish-python.outputs.failure_summary }} - ${{ needs.publish-python-universal.outputs.failure_summary }} slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} gh_token: ${{ github.token }} repo: ${{ github.repository }} run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} ref_name: ${{ github.ref_name }} actor: ${{ github.actor }} server_url: ${{ github.server_url }} diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index d28e8abd..f661fa09 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -149,15 +149,15 @@ jobs: language_name: Ruby versions_json: |- [ - {"key":"3.2","label":"Ruby 3.2","job":"publish-ruby (3.2)","fallback":"${{ needs.publish-ruby.result }}"}, - {"key":"3.3","label":"Ruby 3.3","job":"publish-ruby (3.3)","fallback":"${{ needs.publish-ruby.result }}"}, - {"key":"3.4","label":"Ruby 3.4","job":"publish-ruby (3.4)","fallback":"${{ needs.publish-ruby.result }}"} + {"key":"3.2","label":"Ruby 3.2","job":"publish-ruby (3.2)","fallback":"${{ needs.publish-ruby.result }}","failure_key":"ruby-3.2"}, + {"key":"3.3","label":"Ruby 3.3","job":"publish-ruby (3.3)","fallback":"${{ needs.publish-ruby.result }}","failure_key":"ruby-3.3"}, + {"key":"3.4","label":"Ruby 3.4","job":"publish-ruby (3.4)","fallback":"${{ needs.publish-ruby.result }}","failure_key":"ruby-3.4"} ] - failure_summaries: ${{ needs.publish-ruby.outputs.failure_summary }} slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} gh_token: ${{ github.token }} repo: ${{ github.repository }} run_id: ${{ github.run_id }} + run_attempt: ${{ github.run_attempt }} ref_name: ${{ github.ref_name }} actor: ${{ github.actor }} server_url: ${{ github.server_url }} From 47efb41e653d14849e347e09e0a186f727a3ada4 Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Tue, 28 Apr 2026 15:03:43 +0530 Subject: [PATCH 14/16] feat: decouple ECR from layer publish, show per-version regions in Slack ECR publish now runs independently (if: always()) so a region failure in layer publishing no longer prevents Docker images from being pushed. Slack notification now downloads failed-regions-* artifacts and shows which specific regions failed per version instead of generic message. Added failure_key to all versions_json entries and run_attempt input to all 6 notify-slack jobs. ECR failures still surfaced separately. --- .github/workflows/publish-java.yml | 2 ++ .github/workflows/publish-node.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/publish-java.yml b/.github/workflows/publish-java.yml index 06075ca0..2747e593 100644 --- a/.github/workflows/publish-java.yml +++ b/.github/workflows/publish-java.yml @@ -158,10 +158,12 @@ jobs: run_id: ${{ github.run_id }} run_attempt: ${{ github.run_attempt }} - name: Set up QEMU + if: always() uses: docker/setup-qemu-action@v3 with: platforms: arm64, amd64 - name: Publish ECR image for ${{ matrix.java-version }} + if: always() id: publish-ecr env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} diff --git a/.github/workflows/publish-node.yml b/.github/workflows/publish-node.yml index 2639490b..a1412df5 100644 --- a/.github/workflows/publish-node.yml +++ b/.github/workflows/publish-node.yml @@ -141,6 +141,7 @@ jobs: run_id: ${{ github.run_id }} run_attempt: ${{ github.run_attempt }} - name: Publish ECR image for nodejs${{ matrix.node-version }} + if: always() id: publish-ecr env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -198,6 +199,7 @@ jobs: run_id: ${{ github.run_id }} run_attempt: ${{ github.run_attempt }} - name: Publish universal ECR image + if: always() id: publish-ecr-universal env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} From 11bcf4211ab019b0dca02e5975d39cacb739101b Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Tue, 28 Apr 2026 15:27:38 +0530 Subject: [PATCH 15/16] feat: enhance layer publishing scripts to include ECR failure summaries and improve error handling --- .github/workflows/publish-dotnet.yml | 3 + .github/workflows/publish-extension.yml | 3 + .github/workflows/publish-python.yml | 7 +++ .github/workflows/publish-ruby.yml | 3 + dotnet/publish-layers.sh | 13 +++-- extension/publish-layer.sh | 12 ++-- python/publish-layers.sh | 77 ++++++++++++++++--------- ruby/publish-layers.sh | 33 +++++++---- 8 files changed, 101 insertions(+), 50 deletions(-) diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index e96feb93..e870f357 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -79,6 +79,7 @@ jobs: runs-on: ubuntu-latest outputs: failure_summary: ${{ steps.publish.outputs.failure_summary }} + ecr_failure_summary: ${{ steps.publish.outputs.ecr_failure_summary }} steps: - uses: actions/checkout@v4 - name: Set up QEMU @@ -101,6 +102,7 @@ jobs: PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} + ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }} run: | cd dotnet ./publish-layers.sh @@ -125,6 +127,7 @@ jobs: with: language_name: Dotnet versions_json: '[{"key":"dotnet10","label":"Dotnet 10","job":"publish-dotnet","fallback":"${{ needs.publish-dotnet.result }}","failure_key":"dotnet"}]' + failure_summaries: ${{ needs.publish-dotnet.outputs.ecr_failure_summary }} slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} gh_token: ${{ github.token }} repo: ${{ github.repository }} diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index f31f6e35..c7afb890 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -97,6 +97,7 @@ jobs: runs-on: ubuntu-latest outputs: failure_summary: ${{ steps.publish.outputs.failure_summary }} + ecr_failure_summary: ${{ steps.publish.outputs.ecr_failure_summary }} steps: - uses: actions/checkout@v4 - name: Install publish dependencies @@ -121,6 +122,7 @@ jobs: PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} + ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }} run: | cd extension ./publish-layer.sh @@ -145,6 +147,7 @@ jobs: with: language_name: Extension (Go/AL2+AL2023) versions_json: '[{"key":"extension","label":"Extension (go/al2+al2023)","job":"publish-extension","fallback":"${{ needs.publish-extension.result }}","failure_key":"extension"}]' + failure_summaries: ${{ needs.publish-extension.outputs.ecr_failure_summary }} slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} gh_token: ${{ github.token }} repo: ${{ github.repository }} diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index 36f4fe3b..8aa8413e 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -75,6 +75,7 @@ jobs: python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] outputs: failure_summary: ${{ steps.publish.outputs.failure_summary }} + ecr_failure_summary: ${{ steps.publish.outputs.ecr_failure_summary }} steps: - uses: actions/checkout@v4 - name: Setup @@ -95,6 +96,7 @@ jobs: PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} + ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }} run: | cd python ./publish-layers.sh python${{ matrix.python-version }} @@ -115,6 +117,7 @@ jobs: runs-on: ubuntu-latest outputs: failure_summary: ${{ steps.publish-universal.outputs.failure_summary }} + ecr_failure_summary: ${{ steps.publish-universal.outputs.ecr_failure_summary }} steps: - uses: actions/checkout@v4 - name: Setup @@ -135,6 +138,7 @@ jobs: PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} + ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }} run: | cd python ./publish-layers.sh python @@ -168,6 +172,9 @@ jobs: {"key":"3.14","label":"Python 3.14","job":"publish-python (3.14)","fallback":"${{ needs.publish-python.result }}","failure_key":"python-3.14"}, {"key":"universal","label":"Python Universal","job":"publish-python-universal","fallback":"${{ needs.publish-python-universal.result }}","failure_key":"python-universal"} ] + failure_summaries: | + ${{ needs.publish-python.outputs.ecr_failure_summary }} + ${{ needs.publish-python-universal.outputs.ecr_failure_summary }} slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} gh_token: ${{ github.token }} repo: ${{ github.repository }} diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index f661fa09..88f5756c 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -93,6 +93,7 @@ jobs: ruby-version: [3.2, 3.3, 3.4] outputs: failure_summary: ${{ steps.publish.outputs.failure_summary }} + ecr_failure_summary: ${{ steps.publish.outputs.ecr_failure_summary }} steps: - uses: actions/checkout@v4.2.2 - name: Use Ruby ${{ matrix.ruby-version }} @@ -125,6 +126,7 @@ jobs: PUBLISH_REGIONS: ${{ steps.region-retry-load.outputs.publish_regions || inputs.regions }} LAYER_REGIONS: ${{ vars.LAYER_REGIONS }} S3_BUCKET_PREFIX: ${{ vars.S3_BUCKET_PREFIX }} + ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }} run: ./publish-layers.sh ruby${{ matrix.ruby-version }} working-directory: ruby - name: Save failed regions for re-run @@ -153,6 +155,7 @@ jobs: {"key":"3.3","label":"Ruby 3.3","job":"publish-ruby (3.3)","fallback":"${{ needs.publish-ruby.result }}","failure_key":"ruby-3.3"}, {"key":"3.4","label":"Ruby 3.4","job":"publish-ruby (3.4)","fallback":"${{ needs.publish-ruby.result }}","failure_key":"ruby-3.4"} ] + failure_summaries: ${{ needs.publish-ruby.outputs.ecr_failure_summary }} slack_webhook: ${{ secrets.SLACK_VALIDATION_WEBHOOK }} gh_token: ${{ github.token }} repo: ${{ github.repository }} diff --git a/dotnet/publish-layers.sh b/dotnet/publish-layers.sh index 53a8187a..abf29b14 100755 --- a/dotnet/publish-layers.sh +++ b/dotnet/publish-layers.sh @@ -37,8 +37,6 @@ function publish-dotnet-x86-64 { fi run_region_loop "$DOTNET_DIST_X86_64" dotnet x86_64 "$NEWRELIC_AGENT_VERSION" - - publish_docker_ecr $DOTNET_DIST_X86_64 dotnet x86_64 } function build-dotnet-arm64 { @@ -60,8 +58,6 @@ function publish-dotnet-arm64 { fi run_region_loop "$DOTNET_DIST_ARM64" dotnet arm64 "$NEWRELIC_AGENT_VERSION" - - publish_docker_ecr $DOTNET_DIST_ARM64 dotnet arm64 } # exmaple https://download.newrelic.com/dot_net_agent/latest_release/newrelic-dotnet-agent_amd64.tar.gz @@ -108,10 +104,15 @@ case "${1:-}" in done ;; *) + layer_rc=0 build-dotnet-arm64 - publish-dotnet-arm64 + publish-dotnet-arm64 || layer_rc=$? + publish_ecr_safe $DOTNET_DIST_ARM64 dotnet arm64 build-dotnet-x86-64 - publish-dotnet-x86-64 + publish-dotnet-x86-64 || layer_rc=$? + publish_ecr_safe $DOTNET_DIST_X86_64 dotnet x86_64 + finalize_ecr_results "dotnet" + [[ $layer_rc -eq 0 ]] || exit $layer_rc ;; esac diff --git a/extension/publish-layer.sh b/extension/publish-layer.sh index a7622151..c2f3974f 100755 --- a/extension/publish-layer.sh +++ b/extension/publish-layer.sh @@ -65,12 +65,16 @@ case "${1:-publish}" in "publish-staging") publish-staging ;; "cleanup-staging") cleanup-staging ;; *) + layer_rc=0 build-layer-x86 - publish-layer-x86 - publish_docker_ecr $EXTENSION_DIST_ZIP_X86_64 extension x86_64 + publish-layer-x86 || layer_rc=$? + publish_ecr_safe $EXTENSION_DIST_ZIP_X86_64 extension x86_64 build-layer-arm64 - publish-layer-arm64 - publish_docker_ecr $EXTENSION_DIST_ZIP_ARM64 extension arm64 + publish-layer-arm64 || layer_rc=$? + publish_ecr_safe $EXTENSION_DIST_ZIP_ARM64 extension arm64 + + finalize_ecr_results "extension" + [[ $layer_rc -eq 0 ]] || exit $layer_rc ;; esac diff --git a/python/publish-layers.sh b/python/publish-layers.sh index d789a8f6..62f567bd 100755 --- a/python/publish-layers.sh +++ b/python/publish-layers.sh @@ -144,60 +144,81 @@ case "$1" in publish_docker_ecr $PY_DIST_X86_64 python x86_64 ;; "python") + layer_rc=0 build_universal_python_layer arm64 - publish_universal_python_layer arm64 - publish_docker_ecr $PY_DIST_ARM64 python arm64 + publish_universal_python_layer arm64 || layer_rc=$? + publish_ecr_safe $PY_DIST_ARM64 python arm64 build_universal_python_layer x86_64 - publish_universal_python_layer x86_64 - publish_docker_ecr $PY_DIST_X86_64 python x86_64 + publish_universal_python_layer x86_64 || layer_rc=$? + publish_ecr_safe $PY_DIST_X86_64 python x86_64 + finalize_ecr_results "python-universal" + [[ $layer_rc -eq 0 ]] || exit $layer_rc ;; "python3.9") + layer_rc=0 build_python_layer 3.9 arm64 - publish_python_layer 3.9 arm64 - publish_docker_ecr $PY39_DIST_ARM64 python3.9 arm64 + publish_python_layer 3.9 arm64 || layer_rc=$? + publish_ecr_safe $PY39_DIST_ARM64 python3.9 arm64 build_python_layer 3.9 x86_64 - publish_python_layer 3.9 x86_64 - publish_docker_ecr $PY39_DIST_X86_64 python3.9 x86_64 + publish_python_layer 3.9 x86_64 || layer_rc=$? + publish_ecr_safe $PY39_DIST_X86_64 python3.9 x86_64 + finalize_ecr_results "python3.9" + [[ $layer_rc -eq 0 ]] || exit $layer_rc ;; "python3.10") + layer_rc=0 build_python_layer 3.10 arm64 - publish_python_layer 3.10 arm64 - publish_docker_ecr $PY310_DIST_ARM64 python3.10 arm64 + publish_python_layer 3.10 arm64 || layer_rc=$? + publish_ecr_safe $PY310_DIST_ARM64 python3.10 arm64 build_python_layer 3.10 x86_64 - publish_python_layer 3.10 x86_64 - publish_docker_ecr $PY310_DIST_X86_64 python3.10 x86_64 + publish_python_layer 3.10 x86_64 || layer_rc=$? + publish_ecr_safe $PY310_DIST_X86_64 python3.10 x86_64 + finalize_ecr_results "python3.10" + [[ $layer_rc -eq 0 ]] || exit $layer_rc ;; "python3.11") + layer_rc=0 build_python_layer 3.11 arm64 - publish_python_layer 3.11 arm64 - publish_docker_ecr $PY311_DIST_ARM64 python3.11 arm64 + publish_python_layer 3.11 arm64 || layer_rc=$? + publish_ecr_safe $PY311_DIST_ARM64 python3.11 arm64 build_python_layer 3.11 x86_64 - publish_python_layer 3.11 x86_64 - publish_docker_ecr $PY311_DIST_X86_64 python3.11 x86_64 + publish_python_layer 3.11 x86_64 || layer_rc=$? + publish_ecr_safe $PY311_DIST_X86_64 python3.11 x86_64 + finalize_ecr_results "python3.11" + [[ $layer_rc -eq 0 ]] || exit $layer_rc ;; "python3.12") + layer_rc=0 build_python_layer 3.12 arm64 - publish_python_layer 3.12 arm64 - publish_docker_ecr $PY312_DIST_ARM64 python3.12 arm64 + publish_python_layer 3.12 arm64 || layer_rc=$? + publish_ecr_safe $PY312_DIST_ARM64 python3.12 arm64 build_python_layer 3.12 x86_64 - publish_python_layer 3.12 x86_64 - publish_docker_ecr $PY312_DIST_X86_64 python3.12 x86_64 + publish_python_layer 3.12 x86_64 || layer_rc=$? + publish_ecr_safe $PY312_DIST_X86_64 python3.12 x86_64 + finalize_ecr_results "python3.12" + [[ $layer_rc -eq 0 ]] || exit $layer_rc ;; "python3.13") + layer_rc=0 build_python_layer 3.13 arm64 - publish_python_layer 3.13 arm64 - publish_docker_ecr $PY313_DIST_ARM64 python3.13 arm64 + publish_python_layer 3.13 arm64 || layer_rc=$? + publish_ecr_safe $PY313_DIST_ARM64 python3.13 arm64 build_python_layer 3.13 x86_64 - publish_python_layer 3.13 x86_64 - publish_docker_ecr $PY313_DIST_X86_64 python3.13 x86_64 + publish_python_layer 3.13 x86_64 || layer_rc=$? + publish_ecr_safe $PY313_DIST_X86_64 python3.13 x86_64 + finalize_ecr_results "python3.13" + [[ $layer_rc -eq 0 ]] || exit $layer_rc ;; "python3.14") + layer_rc=0 build_python_layer 3.14 arm64 - publish_python_layer 3.14 arm64 - publish_docker_ecr $PY314_DIST_ARM64 python3.14 arm64 + publish_python_layer 3.14 arm64 || layer_rc=$? + publish_ecr_safe $PY314_DIST_ARM64 python3.14 arm64 build_python_layer 3.14 x86_64 - publish_python_layer 3.14 x86_64 - publish_docker_ecr $PY314_DIST_X86_64 python3.14 x86_64 + publish_python_layer 3.14 x86_64 || layer_rc=$? + publish_ecr_safe $PY314_DIST_X86_64 python3.14 x86_64 + finalize_ecr_results "python3.14" + [[ $layer_rc -eq 0 ]] || exit $layer_rc ;; "publish-staging-python3.14") build_python_layer 3.14 arm64 diff --git a/ruby/publish-layers.sh b/ruby/publish-layers.sh index f45f1ce8..9df8e8ad 100755 --- a/ruby/publish-layers.sh +++ b/ruby/publish-layers.sh @@ -181,28 +181,37 @@ function publish_ruby_for_arch { set +u # permit $1 to be unbound so that '*' matches it when no args are present case "$1" in "ruby3.4") + layer_rc=0 build-ruby34-arm64 - publish-ruby34-arm64 - publish_docker_ecr $RB34_DIST_ARM64 ruby3.4 arm64 + publish-ruby34-arm64 || layer_rc=$? + publish_ecr_safe $RB34_DIST_ARM64 ruby3.4 arm64 build-ruby34-x86 - publish-ruby34-x86 - publish_docker_ecr $RB34_DIST_X86_64 ruby3.4 x86_64 + publish-ruby34-x86 || layer_rc=$? + publish_ecr_safe $RB34_DIST_X86_64 ruby3.4 x86_64 + finalize_ecr_results "ruby3.4" + [[ $layer_rc -eq 0 ]] || exit $layer_rc ;; "ruby3.3") + layer_rc=0 build-ruby33-arm64 - publish-ruby33-arm64 - publish_docker_ecr $RB33_DIST_ARM64 ruby3.3 arm64 + publish-ruby33-arm64 || layer_rc=$? + publish_ecr_safe $RB33_DIST_ARM64 ruby3.3 arm64 build-ruby33-x86 - publish-ruby33-x86 - publish_docker_ecr $RB33_DIST_X86_64 ruby3.3 x86_64 + publish-ruby33-x86 || layer_rc=$? + publish_ecr_safe $RB33_DIST_X86_64 ruby3.3 x86_64 + finalize_ecr_results "ruby3.3" + [[ $layer_rc -eq 0 ]] || exit $layer_rc ;; "ruby3.2") + layer_rc=0 build-ruby32-arm64 - publish-ruby32-arm64 - publish_docker_ecr $RB32_DIST_ARM64 ruby3.2 arm64 + publish-ruby32-arm64 || layer_rc=$? + publish_ecr_safe $RB32_DIST_ARM64 ruby3.2 arm64 build-ruby32-x86 - publish-ruby32-x86 - publish_docker_ecr $RB32_DIST_X86_64 ruby3.2 x86_64 + publish-ruby32-x86 || layer_rc=$? + publish_ecr_safe $RB32_DIST_X86_64 ruby3.2 x86_64 + finalize_ecr_results "ruby3.2" + [[ $layer_rc -eq 0 ]] || exit $layer_rc ;; "publish-staging-ruby3.4") build-ruby34-arm64 From d556865ed3170c5407d4857142ebf22189d09269 Mon Sep 17 00:00:00 2001 From: Chodavarapu Naga Viswanadha Avinash Date: Thu, 7 May 2026 15:44:37 +0530 Subject: [PATCH 16/16] chore: updated description to be generalized comments. --- .github/actions/notify-slack-layer/action.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/actions/notify-slack-layer/action.yml b/.github/actions/notify-slack-layer/action.yml index ff6d7496..dc729c77 100644 --- a/.github/actions/notify-slack-layer/action.yml +++ b/.github/actions/notify-slack-layer/action.yml @@ -7,12 +7,18 @@ inputs: required: true versions_json: description: | - JSON array: [{"key":"20","label":"Node.js 20","job":"publish-node (20)","fallback":"success","failure_key":"nodejs-20"}] - - key: unique identifier - - label: display label in Slack message - - job: GitHub Actions job name (used for per-matrix API lookup) - - fallback: result to use if API lookup finds no match - - failure_key: job_key used in region-retry (for per-version region failure detail) + JSON array describing each runtime version to include in the Slack message. + Schema (one object per version): + - key: unique identifier, e.g. "3.9" + - label: display name shown in Slack, e.g. "Python 3.9" + - job: GitHub Actions job name for API result lookup, e.g. "publish-python (3.9)" + - fallback: result to use when the API lookup finds no match, e.g. "success" or "failure" + - failure_key: job_key used by region-retry for per-version region failure detail, e.g. "python-3.9" + Example: + [ + {"key":"3.9","label":"Python 3.9","job":"publish-python (3.9)","fallback":"success","failure_key":"python-3.9"}, + {"key":"3.10","label":"Python 3.10","job":"publish-python (3.10)","fallback":"success","failure_key":"python-3.10"} + ] required: true failure_summaries: description: Newline-separated failure_summary strings from publish jobs (used for ECR failures)