From 38603086119b94126db8b229139ac165ca68a9ce Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 00:28:44 +0000 Subject: [PATCH 01/11] ci: add release workflow Adds a workflow_dispatch release pipeline that builds the runtime library for x86_64 and aarch64, signs artifacts with a GPG key from Secrets Manager, and uploads them to a draft GitHub release. --- .github/workflows/release.yml | 162 ++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..f6701f2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,162 @@ +name: Release + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + create-release: + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + outputs: + tag: ${{ github.ref_name }} + steps: + - name: Create draft release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.ref_name }} + run: | + gh release create "$TAG" \ + --repo "$GITHUB_REPOSITORY" \ + --draft \ + --title "$TAG" + + build: + needs: create-release + runs-on: ${{ matrix.runner }} + timeout-minutes: 15 + container: public.ecr.aws/amazonlinux/amazonlinux:2023 + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + runner: codebuild-aws-lambda-cpp-test-trigger-x86-${{ github.run_id }}-${{ github.run_attempt }} + - arch: aarch64 + runner: codebuild-aws-lambda-cpp-test-trigger-arm64-${{ github.run_id }}-${{ github.run_attempt }} + + steps: + - name: Install prerequisites + run: dnf install -y tar gzip git cmake ninja-build gcc-c++ openssl-devel libcurl-devel + + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.ref_name }} + + - name: Build in Release mode + run: | + cmake -B build -GNinja \ + -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_TESTS=ON + cmake --build build + + - name: Run unit tests + run: cd build && ctest --output-on-failure + + - name: Extract library + run: | + mkdir -p staging + cp build/libaws-lambda-runtime.a staging/libaws-lambda-runtime-${{ matrix.arch }}.a + + - name: Upload artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: libaws-lambda-runtime-${{ matrix.arch }} + path: staging/libaws-lambda-runtime-${{ matrix.arch }}.a + + upload: + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + # The signing role's trust policy is scoped to the `environment:release` + # OIDC subject claim. Without this, the token carries a `ref:` subject and + # AssumeRoleWithWebIdentity is denied. See docs/gpg-key-management.md. + environment: release + permissions: + id-token: write + contents: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: artifacts + merge-multiple: true + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2 + with: + role-to-assume: ${{ secrets.AWS_GPG_SIGNING_ROLE_ARN }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Import GPG key from Secrets Manager + env: + GPG_SECRET_ID: lambda-runtimes/cpp/gpg-signing-key + run: | + # Isolate the keyring to the runner's workspace so nothing leaks into + # the default ~/.gnupg, and keep the key material off disk entirely: + # Secrets Manager streams it straight into gpg --import via stdin. + GNUPGHOME="$(mktemp -d)" + chmod 700 "$GNUPGHOME" + echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV" + + aws secretsmanager get-secret-value \ + --secret-id "$GPG_SECRET_ID" \ + --query SecretString \ + --output text \ + | gpg --batch --homedir "$GNUPGHOME" --import + + # Resolve the fingerprint rather than relying on gpg's default key + # selection, so signing fails loudly if the import went wrong. + FPR="$(gpg --homedir "$GNUPGHOME" --with-colons --list-secret-keys \ + | awk -F: '/^fpr:/ {print $10; exit}')" + if [ -z "$FPR" ]; then + echo "No secret key imported from $GPG_SECRET_ID" >&2 + exit 1 + fi + echo "GPG_FINGERPRINT=$FPR" >> "$GITHUB_ENV" + echo "Imported signing key $FPR" + + - name: Sign artifacts and generate checksums + run: | + cd artifacts + + # Sign each artifact + for file in *.a; do + gpg --batch --yes --local-user "$GPG_FINGERPRINT" \ + --armor --detach-sign "$file" + done + + # Generate checksums file and sign it + sha256sum ./*.a > SHA256SUMS + gpg --batch --yes --local-user "$GPG_FINGERPRINT" \ + --armor --detach-sign SHA256SUMS + + - name: Verify signatures + run: | + cd artifacts + gpg --batch --verify SHA256SUMS.asc SHA256SUMS + for file in *.a; do + gpg --batch --verify "$file.asc" "$file" + done + + - name: Upload assets to release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.ref_name }} + run: | + gh release upload "$TAG" \ + --repo "$GITHUB_REPOSITORY" \ + artifacts/* + + - name: Destroy imported key material + if: always() + run: | + if [ -n "${GNUPGHOME:-}" ] && [ -d "$GNUPGHOME" ]; then + gpgconf --homedir "$GNUPGHOME" --kill all || true + rm -rf "$GNUPGHOME" + fi From f0c838f183afdad8a43b581bb7d691d5bf26e349 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 00:33:58 +0000 Subject: [PATCH 02/11] ci: temporarily trigger release workflow on branch push for testing --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f6701f2..aba158f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,8 @@ name: Release on: - workflow_dispatch: + push: + branches: [maxday/test-release] permissions: contents: read From dd0a6e8ba307c23879905755e909ba6d11378ccc Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 00:42:42 +0000 Subject: [PATCH 03/11] ci: drop job container, build directly on AL2023 CodeBuild runners --- .github/workflows/release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aba158f..14de475 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,6 @@ jobs: needs: create-release runs-on: ${{ matrix.runner }} timeout-minutes: 15 - container: public.ecr.aws/amazonlinux/amazonlinux:2023 strategy: fail-fast: false matrix: From dd100638f3a98d1579407404942c8e25187315e7 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 00:43:18 +0000 Subject: [PATCH 04/11] ci: temporarily disable Tests workflow while iterating on release --- .github/workflows/{tests.yml => tests.yml.disabled} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{tests.yml => tests.yml.disabled} (100%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml.disabled similarity index 100% rename from .github/workflows/tests.yml rename to .github/workflows/tests.yml.disabled From 2aa22ed6ffcef503bcac83255ad7ff4db6f6fac3 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 00:54:49 +0000 Subject: [PATCH 05/11] ci: install build deps from AL2023 S3 repo for VPC runners The CodeBuild runners have no public internet egress, so the default cdn.amazonlinux.com mirrorlist is unreachable. Point dnf at the regional al2023-repos S3 bucket (reachable via the VPC's S3 gateway endpoint) and disable the image's unreachable third-party repos. --- .github/workflows/release.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 14de475..9e7dd71 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,22 @@ jobs: steps: - name: Install prerequisites - run: dnf install -y tar gzip git cmake ninja-build gcc-c++ openssl-devel libcurl-devel + run: | + # The CodeBuild runners live in a VPC with no public egress, so the + # default cdn.amazonlinux.com mirrorlist (and the image's Corretto / + # gh-cli / mono repos) can't be reached. Point dnf at the regional + # AL2023 repo bucket, which is reachable via the VPC's S3 gateway + # endpoint, and disable everything else. + cat > /etc/yum.repos.d/al2023-s3.repo <<'EOF' + [al2023-s3] + name=AL2023 core via S3 gateway endpoint + mirrorlist=https://al2023-repos-eu-west-1.s3.dualstack.eu-west-1.amazonaws.com/core/mirrors/$releasever/$basearch/mirror.list + gpgcheck=1 + gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-amazon-linux-2023 + enabled=1 + EOF + dnf --disablerepo='*' --enablerepo='al2023-s3' install -y \ + tar gzip git cmake ninja-build gcc-c++ openssl-devel libcurl-devel - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: From 8ed558252df23ed2f106af12e06a821ec1f69fc2 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 01:10:15 +0000 Subject: [PATCH 06/11] ci: TEMP probe runner env to determine install strategy --- .github/workflows/release.yml | 40 +++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e7dd71..c347af5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,23 +40,31 @@ jobs: runner: codebuild-aws-lambda-cpp-test-trigger-arm64-${{ github.run_id }}-${{ github.run_attempt }} steps: - - name: Install prerequisites + - name: Probe runner environment + env: + CA_DOMAIN_OWNER: ${{ secrets.AWS_ACCOUNT_ID }} run: | - # The CodeBuild runners live in a VPC with no public egress, so the - # default cdn.amazonlinux.com mirrorlist (and the image's Corretto / - # gh-cli / mono repos) can't be reached. Point dnf at the regional - # AL2023 repo bucket, which is reachable via the VPC's S3 gateway - # endpoint, and disable everything else. - cat > /etc/yum.repos.d/al2023-s3.repo <<'EOF' - [al2023-s3] - name=AL2023 core via S3 gateway endpoint - mirrorlist=https://al2023-repos-eu-west-1.s3.dualstack.eu-west-1.amazonaws.com/core/mirrors/$releasever/$basearch/mirror.list - gpgcheck=1 - gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-amazon-linux-2023 - enabled=1 - EOF - dnf --disablerepo='*' --enablerepo='al2023-s3' install -y \ - tar gzip git cmake ninja-build gcc-c++ openssl-devel libcurl-devel + # TEMP DIAGNOSTIC: the CodeBuild runners have no public egress, so + # figure out exactly what is already available before committing to an + # install strategy. Prints tool availability and whether the service + # role can reach CodeArtifact. Never prints the auth token. + echo "=== tools on PATH ===" + for b in cmake ninja ninja-build gcc g++ python3 pip3 pip aws; do + printf '%-12s ' "$b"; command -v "$b" || echo "(absent)" + done + echo "=== versions (if present) ===" + cmake --version 2>/dev/null | head -1 || true + ninja --version 2>/dev/null || true + python3 -m pip --version 2>/dev/null || echo "python3 -m pip: unavailable" + python3 -m ensurepip --version 2>/dev/null || echo "ensurepip: unavailable" + echo "=== CodeArtifact reachability (service role) ===" + if aws codeartifact get-authorization-token \ + --region eu-west-1 --domain aws-lambda --domain-owner "$CA_DOMAIN_OWNER" \ + --query authorizationToken --output text >/dev/null 2>&1; then + echo "CodeArtifact auth: OK" + else + echo "CodeArtifact auth: FAILED" + fi - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: From 835c3b6a395810f44bf1931d24939bddef631baf Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 01:20:31 +0000 Subject: [PATCH 07/11] ci: install cmake/ninja from in-VPC CodeArtifact pypi-store --- .github/workflows/release.yml | 37 ++++++++++++++--------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c347af5..2f9ea8f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,31 +40,24 @@ jobs: runner: codebuild-aws-lambda-cpp-test-trigger-arm64-${{ github.run_id }}-${{ github.run_attempt }} steps: - - name: Probe runner environment + - name: Install prerequisites env: + AWS_DEFAULT_REGION: eu-west-1 + CA_DOMAIN: aws-lambda CA_DOMAIN_OWNER: ${{ secrets.AWS_ACCOUNT_ID }} + CA_REPO: pypi-store run: | - # TEMP DIAGNOSTIC: the CodeBuild runners have no public egress, so - # figure out exactly what is already available before committing to an - # install strategy. Prints tool availability and whether the service - # role can reach CodeArtifact. Never prints the auth token. - echo "=== tools on PATH ===" - for b in cmake ninja ninja-build gcc g++ python3 pip3 pip aws; do - printf '%-12s ' "$b"; command -v "$b" || echo "(absent)" - done - echo "=== versions (if present) ===" - cmake --version 2>/dev/null | head -1 || true - ninja --version 2>/dev/null || true - python3 -m pip --version 2>/dev/null || echo "python3 -m pip: unavailable" - python3 -m ensurepip --version 2>/dev/null || echo "ensurepip: unavailable" - echo "=== CodeArtifact reachability (service role) ===" - if aws codeartifact get-authorization-token \ - --region eu-west-1 --domain aws-lambda --domain-owner "$CA_DOMAIN_OWNER" \ - --query authorizationToken --output text >/dev/null 2>&1; then - echo "CodeArtifact auth: OK" - else - echo "CodeArtifact auth: FAILED" - fi + # The CodeBuild runners live in a VPC with no public egress, so the + # AL2023 repos (CloudFront) are unreachable and cmake/ninja aren't on + # the image. Install them from the in-VPC CodeArtifact PyPI proxy; + # gcc/g++/git are already present. `codeartifact login` configures pip + # and keeps the auth token out of the command line and logs. + aws codeartifact login --tool pip \ + --domain "$CA_DOMAIN" --domain-owner "$CA_DOMAIN_OWNER" \ + --repository "$CA_REPO" + pip install cmake ninja + cmake --version + ninja --version - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: From 7d8b0a229dc10da1cce744d8a262ff11aa0fb6a1 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 02:10:23 +0000 Subject: [PATCH 08/11] fix: reset tests + trigger event to manual dispatch --- .github/workflows/release.yml | 3 +-- .github/workflows/{tests.yml.disabled => tests.yml} | 0 2 files changed, 1 insertion(+), 2 deletions(-) rename .github/workflows/{tests.yml.disabled => tests.yml} (100%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f9ea8f..9c0f61e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,7 @@ name: Release on: - push: - branches: [maxday/test-release] + workflow_dispatch: permissions: contents: read diff --git a/.github/workflows/tests.yml.disabled b/.github/workflows/tests.yml similarity index 100% rename from .github/workflows/tests.yml.disabled rename to .github/workflows/tests.yml From 89018687df02b33a642e3e9786eb7334265b35e2 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 02:11:35 +0000 Subject: [PATCH 09/11] fix: remove comments --- .github/workflows/release.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9c0f61e..fe0be78 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,11 +46,6 @@ jobs: CA_DOMAIN_OWNER: ${{ secrets.AWS_ACCOUNT_ID }} CA_REPO: pypi-store run: | - # The CodeBuild runners live in a VPC with no public egress, so the - # AL2023 repos (CloudFront) are unreachable and cmake/ninja aren't on - # the image. Install them from the in-VPC CodeArtifact PyPI proxy; - # gcc/g++/git are already present. `codeartifact login` configures pip - # and keeps the auth token out of the command line and logs. aws codeartifact login --tool pip \ --domain "$CA_DOMAIN" --domain-owner "$CA_DOMAIN_OWNER" \ --repository "$CA_REPO" From b14796b9db90dccd0d69c30aad0eb4f2328f7743 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 11:53:31 +0000 Subject: [PATCH 10/11] fix: pr comments --- .github/workflows/release.yml | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe0be78..5f57370 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -103,52 +103,41 @@ jobs: role-to-assume: ${{ secrets.AWS_GPG_SIGNING_ROLE_ARN }} aws-region: ${{ secrets.AWS_REGION }} - - name: Import GPG key from Secrets Manager + - name: Sign artifacts and generate checksums env: GPG_SECRET_ID: lambda-runtimes/cpp/gpg-signing-key run: | - # Isolate the keyring to the runner's workspace so nothing leaks into - # the default ~/.gnupg, and keep the key material off disk entirely: - # Secrets Manager streams it straight into gpg --import via stdin. GNUPGHOME="$(mktemp -d)" chmod 700 "$GNUPGHOME" - echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV" + export GNUPGHOME + trap 'gpgconf --kill all >/dev/null 2>&1 || true; rm -rf "$GNUPGHOME"' EXIT aws secretsmanager get-secret-value \ --secret-id "$GPG_SECRET_ID" \ --query SecretString \ --output text \ - | gpg --batch --homedir "$GNUPGHOME" --import + | gpg --batch --import - # Resolve the fingerprint rather than relying on gpg's default key - # selection, so signing fails loudly if the import went wrong. - FPR="$(gpg --homedir "$GNUPGHOME" --with-colons --list-secret-keys \ + FPR="$(gpg --with-colons --list-secret-keys \ | awk -F: '/^fpr:/ {print $10; exit}')" if [ -z "$FPR" ]; then echo "No secret key imported from $GPG_SECRET_ID" >&2 exit 1 fi - echo "GPG_FINGERPRINT=$FPR" >> "$GITHUB_ENV" - echo "Imported signing key $FPR" - - name: Sign artifacts and generate checksums - run: | cd artifacts # Sign each artifact for file in *.a; do - gpg --batch --yes --local-user "$GPG_FINGERPRINT" \ + gpg --batch --yes --local-user "$FPR" \ --armor --detach-sign "$file" done # Generate checksums file and sign it sha256sum ./*.a > SHA256SUMS - gpg --batch --yes --local-user "$GPG_FINGERPRINT" \ + gpg --batch --yes --local-user "$FPR" \ --armor --detach-sign SHA256SUMS - - name: Verify signatures - run: | - cd artifacts gpg --batch --verify SHA256SUMS.asc SHA256SUMS for file in *.a; do gpg --batch --verify "$file.asc" "$file" @@ -162,11 +151,3 @@ jobs: gh release upload "$TAG" \ --repo "$GITHUB_REPOSITORY" \ artifacts/* - - - name: Destroy imported key material - if: always() - run: | - if [ -n "${GNUPGHOME:-}" ] && [ -d "$GNUPGHOME" ]; then - gpgconf --homedir "$GNUPGHOME" --kill all || true - rm -rf "$GNUPGHOME" - fi From 3e4aae5b57edba70a2a940e57f1c7d0046b1d1b2 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 12:32:54 +0000 Subject: [PATCH 11/11] fix: pass the tag to CMakeLists --- .github/workflows/release.yml | 9 +++++++-- CMakeLists.txt | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f57370..1f5680f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,9 @@ name: Release on: - workflow_dispatch: + push: + tags: + - 'v*' permissions: contents: read @@ -58,10 +60,13 @@ jobs: ref: ${{ github.ref_name }} - name: Build in Release mode + env: + TAG: ${{ github.ref_name }} run: | cmake -B build -GNinja \ -DCMAKE_BUILD_TYPE=Release \ - -DENABLE_TESTS=ON + -DENABLE_TESTS=ON \ + -DAWS_LAMBDA_CPP_VERSION="${TAG#v}" cmake --build build - name: Run unit tests diff --git a/CMakeLists.txt b/CMakeLists.txt index 09e226f..abdcb82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,9 @@ cmake_minimum_required(VERSION 3.9) set(CMAKE_CXX_STANDARD 11) +set(AWS_LAMBDA_CPP_VERSION "0.0.0" CACHE STRING + "Library version, injected at release time from the git tag.") project(aws-lambda-runtime - VERSION 0.0.0 + VERSION ${AWS_LAMBDA_CPP_VERSION} LANGUAGES CXX) option(ENABLE_LTO "Enables link-time optimization, requires compiler support." OFF)