diff --git a/.github/lambda-layer-publish.toml b/.github/lambda-layer-publish.toml deleted file mode 100644 index 4fd021561..000000000 --- a/.github/lambda-layer-publish.toml +++ /dev/null @@ -1,2 +0,0 @@ -[layer] -sdk-version = "3.0.0" diff --git a/.github/scripts/build_lambda_layer.py b/.github/scripts/build_lambda_layer.py deleted file mode 100644 index 6a59665ce..000000000 --- a/.github/scripts/build_lambda_layer.py +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: 2025-present Amazon.com, Inc. or its affiliates. -# -# SPDX-License-Identifier: Apache-2.0 -from __future__ import annotations - -import argparse -import shutil -import subprocess -import sys -import tempfile -import zipfile -from dataclasses import dataclass -from pathlib import Path - - -UNIVERSAL_WHEEL_SUFFIX = "-py3-none-any.whl" - - -@dataclass(frozen=True) -class BuildConfig: - output: Path - sdk_distribution: Path - otel_distribution: Path - build_dir: Path | None = None - - -def build_layer(config: BuildConfig) -> Path: - """Build a universal Lambda layer containing the SDK and OTel plugin.""" - - _validate_config(config) - - with tempfile.TemporaryDirectory() as temp_dir: - work_dir = config.build_dir or Path(temp_dir) / "layer" - _validate_output_location(config.output, work_dir) - if work_dir.exists(): - shutil.rmtree(work_dir) - layer_python_dir = work_dir / "python" - layer_python_dir.mkdir(parents=True) - - _install_layer_distributions(config, layer_python_dir) - _write_zip(config.output, work_dir) - - return config.output - - -def _validate_config(config: BuildConfig) -> None: - for distribution in (config.sdk_distribution, config.otel_distribution): - if not distribution.is_file(): - raise FileNotFoundError(distribution) - if not distribution.name.endswith(UNIVERSAL_WHEEL_SUFFIX): - raise ValueError( - f"Layer distributions must be universal wheels: {distribution}" - ) - - -def _validate_output_location(output: Path, build_dir: Path) -> None: - if output.resolve().is_relative_to(build_dir.resolve()): - raise ValueError("Layer output must be outside the build directory") - - -def _install_layer_distributions(config: BuildConfig, target_dir: Path) -> None: - command = [ - sys.executable, - "-m", - "pip", - "install", - "--upgrade", - "--target", - str(target_dir), - "--only-binary", - ":all:", - "--no-compile", - ] - # The Core wheel's requirements also apply to layer deployments, including - # the botocore service model needed by its public Lambda operations. - subprocess.run([*command, str(config.sdk_distribution)], check=True) - # Telemetry providers belong to the ADOT/community layer. Do not resolve - # plugin dependencies here and accidentally shadow that distribution. - subprocess.run([*command, "--no-deps", str(config.otel_distribution)], check=True) - - -def _write_zip(output: Path, layer_root: Path) -> None: - output.parent.mkdir(parents=True, exist_ok=True) - if output.exists(): - output.unlink() - - with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_DEFLATED) as archive: - for path in sorted(layer_root.rglob("*")): - if path.is_file(): - archive.write(path, path.relative_to(layer_root)) - - -def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser( - prog="build_lambda_layer.py", - description="Build the universal AWS Durable Execution SDK OTel plugin layer.", - ) - parser.add_argument("--output", type=Path, required=True) - parser.add_argument("--sdk-distribution", type=Path, required=True) - parser.add_argument("--otel-distribution", type=Path, required=True) - parser.add_argument( - "--build-dir", - type=Path, - help="Optional scratch directory. Existing contents are replaced.", - ) - - args = parser.parse_args(argv) - output = build_layer( - BuildConfig( - output=args.output, - sdk_distribution=args.sdk_distribution, - otel_distribution=args.otel_distribution, - build_dir=args.build_dir, - ) - ) - print(output) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.github/scripts/resolve_layer_sdk_version.py b/.github/scripts/resolve_layer_sdk_version.py deleted file mode 100644 index d96b53fdd..000000000 --- a/.github/scripts/resolve_layer_sdk_version.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: 2025-present Amazon.com, Inc. or its affiliates. -# -# SPDX-License-Identifier: Apache-2.0 -from __future__ import annotations - -import argparse -import tomllib -from pathlib import Path - - -def _read_pinned_sdk_version(metadata_path: Path) -> str: - with metadata_path.open("rb") as file: - metadata = tomllib.load(file) - - try: - pinned_version = metadata["layer"]["sdk-version"] - except KeyError as error: - raise ValueError(f"{metadata_path}: missing layer.sdk-version") from error - - if not isinstance(pinned_version, str) or not pinned_version: - raise ValueError(f"{metadata_path}: layer.sdk-version must be a string") - return pinned_version - - -def resolve_layer_sdk_version( - event_name: str, - release_tag: str, - source_sdk_version: str, - metadata_path: Path, -) -> str: - """Resolve the SDK version bundled in the OTel plugin Lambda layer.""" - - pinned_version = _read_pinned_sdk_version(metadata_path) - sdk_release = any(part.startswith("sdk-v") for part in release_tag.split(",")) - - if event_name == "release" and not sdk_release: - return pinned_version - - if event_name == "release" and pinned_version != source_sdk_version: - raise ValueError( - f"New SDK releases must update layer.sdk-version to {source_sdk_version}" - ) - - return source_sdk_version - - -def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser( - description="Resolve the SDK version for the OTel plugin Lambda layer." - ) - parser.add_argument("--event-name", required=True) - parser.add_argument("--release-tag", default="") - parser.add_argument("--source-sdk-version", required=True) - parser.add_argument("--metadata", type=Path, required=True) - args = parser.parse_args(argv) - - print( - resolve_layer_sdk_version( - event_name=args.event_name, - release_tag=args.release_tag, - source_sdk_version=args.source_sdk_version, - metadata_path=args.metadata, - ) - ) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.github/scripts/tests/test_build_lambda_layer.py b/.github/scripts/tests/test_build_lambda_layer.py deleted file mode 100644 index 77026cdcb..000000000 --- a/.github/scripts/tests/test_build_lambda_layer.py +++ /dev/null @@ -1,231 +0,0 @@ -from __future__ import annotations - -import os -import subprocess -import sys -import zipfile -from pathlib import Path - -import pytest - - -sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) - -from build_lambda_layer import BuildConfig, build_layer - - -def _write_test_wheel( - directory: Path, - distribution: str, - package_files: tuple[str, ...], - dependencies: tuple[str, ...] = (), - package_sources: dict[str, str] | None = None, -) -> Path: - normalized_distribution = distribution.replace("-", "_") - wheel = directory / f"{normalized_distribution}-1.0.0-py3-none-any.whl" - dist_info = f"{normalized_distribution}-1.0.0.dist-info" - metadata = [ - "Metadata-Version: 2.1", - f"Name: {distribution}", - "Version: 1.0.0", - *(f"Requires-Dist: {dependency}==1.0.0" for dependency in dependencies), - "", - ] - - with zipfile.ZipFile(wheel, "w") as archive: - for package_file in package_files: - archive.writestr( - package_file, (package_sources or {}).get(package_file, "") - ) - archive.writestr(f"{dist_info}/METADATA", "\n".join(metadata)) - archive.writestr( - f"{dist_info}/WHEEL", - "\n".join( - ( - "Wheel-Version: 1.0", - "Generator: test", - "Root-Is-Purelib: true", - "Tag: py3-none-any", - "", - ) - ), - ) - archive.writestr(f"{dist_info}/RECORD", "") - - return wheel - - -def test_build_layer_installs_distributions_and_zips_lambda_layout( - monkeypatch: pytest.MonkeyPatch, - tmp_path: Path, -) -> None: - sdk_wheel = tmp_path / "aws_durable_execution_sdk_python-1.0.0-py3-none-any.whl" - otel_wheel = ( - tmp_path / "aws_durable_execution_sdk_python_otel-1.0.0-py3-none-any.whl" - ) - sdk_wheel.write_text("sdk") - otel_wheel.write_text("otel") - commands: list[list[str]] = [] - - def fake_run(command: list[str], check: bool) -> subprocess.CompletedProcess[str]: - commands.append(command) - target = Path(command[command.index("--target") + 1]) - for wheel, package in ( - (sdk_wheel, "aws_durable_execution_sdk_python"), - (otel_wheel, "aws_durable_execution_sdk_python_otel"), - ): - if str(wheel) in command: - (target / package).mkdir(exist_ok=True) - (target / package / "__init__.py").write_text("") - return subprocess.CompletedProcess(command, 0) - - monkeypatch.setattr(subprocess, "run", fake_run) - - output = build_layer( - BuildConfig( - output=tmp_path / "layer.zip", - sdk_distribution=sdk_wheel, - otel_distribution=otel_wheel, - ) - ) - - assert output == tmp_path / "layer.zip" - sdk_command = next(command for command in commands if str(sdk_wheel) in command) - otel_command = next(command for command in commands if str(otel_wheel) in command) - assert "--no-deps" not in sdk_command - assert "--no-deps" in otel_command - for command in commands: - assert "--no-compile" in command - assert "--platform" not in command - assert "--python-version" not in command - assert "--abi" not in command - - with zipfile.ZipFile(output) as archive: - assert ( - "python/aws_durable_execution_sdk_python/__init__.py" in archive.namelist() - ) - assert ( - "python/aws_durable_execution_sdk_python_otel/__init__.py" - in archive.namelist() - ) - - -def test_build_layer_includes_sdk_dependencies_without_shadowing_adot( - monkeypatch: pytest.MonkeyPatch, - tmp_path: Path, -) -> None: - sdk_wheel = _write_test_wheel( - tmp_path, - "aws-durable-execution-sdk-python", - ("aws_durable_execution_sdk_python/__init__.py",), - ("boto3",), - ) - otel_wheel = _write_test_wheel( - tmp_path, - "aws-durable-execution-sdk-python-otel", - ("aws_durable_execution_sdk_python_otel/__init__.py",), - ("aws-opentelemetry-distro", "opentelemetry-api"), - ) - # Minimal offline wheel fixtures exercise dependency resolution and layer - # import precedence. They contain no service model or simulated wire API. - _write_test_wheel( - tmp_path, - "boto3", - ("boto3/__init__.py",), - ("botocore",), - package_sources={"boto3/__init__.py": '__version__ = "layer-dependency"\n'}, - ) - _write_test_wheel(tmp_path, "botocore", ("botocore/__init__.py",)) - _write_test_wheel( - tmp_path, - "aws-opentelemetry-distro", - ("amazon/opentelemetry/distro/__init__.py",), - ) - _write_test_wheel( - tmp_path, - "opentelemetry-api", - ("opentelemetry/__init__.py",), - ) - monkeypatch.setenv("PIP_FIND_LINKS", str(tmp_path)) - monkeypatch.setenv("PIP_NO_INDEX", "1") - - output = build_layer( - BuildConfig( - output=tmp_path / "layer.zip", - sdk_distribution=sdk_wheel, - otel_distribution=otel_wheel, - ) - ) - - with zipfile.ZipFile(output) as archive: - names = archive.namelist() - - assert "python/aws_durable_execution_sdk_python/__init__.py" in names - assert "python/aws_durable_execution_sdk_python_otel/__init__.py" in names - assert not any(name.startswith("python/amazon/") for name in names) - assert "python/boto3/__init__.py" in names - assert "python/botocore/__init__.py" in names - assert not any(name.startswith("python/opentelemetry/") for name in names) - - layer_root = tmp_path / "unpacked" - with zipfile.ZipFile(output) as archive: - archive.extractall(layer_root) - runtime = tmp_path / "older-runtime" - (runtime / "boto3").mkdir(parents=True) - (runtime / "boto3" / "__init__.py").write_text('__version__ = "older-runtime"\n') - # Lambda searches the layer before its runtime SDK. An omitted dependency - # would silently import the older runtime package instead. - probe = ( - "import pathlib,sys; " - f"layer=pathlib.Path({str(layer_root / 'python')!r}); " - f"sys.path[:0]=[str(layer),{str(runtime)!r}]; " - "import boto3; " - "assert boto3.__version__ == 'layer-dependency'; " - "assert pathlib.Path(boto3.__file__).is_relative_to(layer)" - ) - subprocess.run([sys.executable, "-I", "-c", probe], check=True) - - -def test_build_layer_requires_built_distributions(tmp_path: Path) -> None: - with pytest.raises(FileNotFoundError): - build_layer( - BuildConfig( - output=tmp_path / "layer.zip", - sdk_distribution=tmp_path / "missing-sdk.whl", - otel_distribution=tmp_path / "missing-otel.whl", - ) - ) - - -def test_build_layer_requires_universal_wheels(tmp_path: Path) -> None: - sdk_wheel = tmp_path / "sdk-1.0.0-cp311-cp311-manylinux2014_x86_64.whl" - otel_wheel = tmp_path / "otel-1.0.0-py3-none-any.whl" - sdk_wheel.write_text("sdk") - otel_wheel.write_text("otel") - - with pytest.raises(ValueError, match="must be universal wheels"): - build_layer( - BuildConfig( - output=tmp_path / "layer.zip", - sdk_distribution=sdk_wheel, - otel_distribution=otel_wheel, - ) - ) - - -def test_build_layer_rejects_output_inside_build_directory(tmp_path: Path) -> None: - sdk_wheel = tmp_path / "sdk-1.0.0-py3-none-any.whl" - otel_wheel = tmp_path / "otel-1.0.0-py3-none-any.whl" - sdk_wheel.write_text("sdk") - otel_wheel.write_text("otel") - build_dir = tmp_path / "layer" - - with pytest.raises(ValueError, match="outside the build directory"): - build_layer( - BuildConfig( - output=build_dir / "layer.zip", - sdk_distribution=sdk_wheel, - otel_distribution=otel_wheel, - build_dir=build_dir, - ) - ) diff --git a/.github/scripts/tests/test_resolve_layer_sdk_version.py b/.github/scripts/tests/test_resolve_layer_sdk_version.py deleted file mode 100644 index 5308179d5..000000000 --- a/.github/scripts/tests/test_resolve_layer_sdk_version.py +++ /dev/null @@ -1,72 +0,0 @@ -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - - -sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) - -from resolve_layer_sdk_version import resolve_layer_sdk_version - - -def _write_metadata(tmp_path: Path, sdk_version: str) -> Path: - metadata = tmp_path / "lambda-layer-publish.toml" - metadata.write_text(f'[layer]\nsdk-version = "{sdk_version}"\n') - return metadata - - -def test_otel_only_release_uses_pinned_published_sdk(tmp_path: Path) -> None: - metadata = _write_metadata(tmp_path, "1.8.0") - - assert ( - resolve_layer_sdk_version( - event_name="release", - release_tag="otel-v0.4.1", - source_sdk_version="1.9.0", - metadata_path=metadata, - ) - == "1.8.0" - ) - - -def test_combined_release_uses_source_sdk(tmp_path: Path) -> None: - metadata = _write_metadata(tmp_path, "1.9.0") - - assert ( - resolve_layer_sdk_version( - event_name="release", - release_tag="sdk-v1.9.0,otel-v0.5.0", - source_sdk_version="1.9.0", - metadata_path=metadata, - ) - == "1.9.0" - ) - - -def test_combined_release_requires_updated_pin(tmp_path: Path) -> None: - metadata = _write_metadata(tmp_path, "1.8.0") - - with pytest.raises(ValueError, match="New SDK releases must update"): - resolve_layer_sdk_version( - event_name="release", - release_tag="sdk-v1.9.0,otel-v0.5.0", - source_sdk_version="1.9.0", - metadata_path=metadata, - ) - - -def test_manual_run_uses_source_sdk(tmp_path: Path) -> None: - metadata = _write_metadata(tmp_path, "1.8.0") - - assert ( - resolve_layer_sdk_version( - event_name="workflow_dispatch", - release_tag="", - source_sdk_version="1.9.0", - metadata_path=metadata, - ) - == "1.9.0" - ) diff --git a/.github/workflows/lambda-layer-publish.yml b/.github/workflows/lambda-layer-publish.yml deleted file mode 100644 index 9c833edf4..000000000 --- a/.github/workflows/lambda-layer-publish.yml +++ /dev/null @@ -1,319 +0,0 @@ -name: Publish OTel Plugin Lambda Layer - -on: - release: - types: [published] - workflow_dispatch: - inputs: - regions: - description: "Comma-separated AWS Regions; defaults to all commercial Regions" - required: false - type: string - -permissions: - contents: read - -concurrency: - group: lambda-layer-publish-${{ github.event.release.tag_name || github.ref_name }} - cancel-in-progress: false - -env: - LAYER_NAME: aws-durable-execution-sdk-python-otel-plugin - SOURCE_REF: ${{ github.event_name == 'workflow_dispatch' && 'main' || github.event.release.tag_name }} - DEFAULT_LAYER_REGIONS: >- - af-south-1, - ap-east-1, - ap-east-2, - ap-northeast-1, - ap-northeast-2, - ap-northeast-3, - ap-south-1, - ap-south-2, - ap-southeast-1, - ap-southeast-2, - ap-southeast-3, - ap-southeast-4, - ap-southeast-5, - ap-southeast-6, - ap-southeast-7, - ca-central-1, - ca-west-1, - eu-central-1, - eu-central-2, - eu-north-1, - eu-south-1, - eu-south-2, - eu-west-1, - eu-west-2, - eu-west-3, - il-central-1, - me-central-1, - me-south-1, - mx-central-1, - sa-east-1, - us-east-1, - us-east-2, - us-west-1, - us-west-2 - -jobs: - build-distributions: - if: >- - (github.event_name == 'workflow_dispatch' && - github.ref == 'refs/heads/main') || - contains(github.event.release.tag_name, 'otel-v') - runs-on: ubuntu-latest - outputs: - sdk_version: ${{ steps.versions.outputs.sdk_version }} - otel_version: ${{ steps.versions.outputs.otel_version }} - - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ env.SOURCE_REF }} - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.11" - - - name: Install Hatch - run: python -m pip install --upgrade hatch==1.16.5 - - - name: Read package versions - id: versions - env: - RELEASE_TAG: ${{ github.event.release.tag_name }} - run: | - SOURCE_SDK_VERSION=$(grep "^__version__" packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/__about__.py | cut -d'"' -f2) - OTEL_VERSION=$(grep "^__version__" packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/__about__.py | cut -d'"' -f2) - SDK_VERSION=$(python .github/scripts/resolve_layer_sdk_version.py \ - --event-name "$GITHUB_EVENT_NAME" \ - --release-tag "$RELEASE_TAG" \ - --source-sdk-version "$SOURCE_SDK_VERSION" \ - --metadata .github/lambda-layer-publish.toml) - echo "sdk_version=${SDK_VERSION}" >> "$GITHUB_OUTPUT" - echo "otel_version=${OTEL_VERSION}" >> "$GITHUB_OUTPUT" - - - name: Build SDK distribution - if: >- - github.event_name == 'workflow_dispatch' || - contains(github.event.release.tag_name, 'sdk-v') - working-directory: packages/aws-durable-execution-sdk-python - run: hatch build - - - name: Download published SDK distribution - if: >- - github.event_name == 'release' && - !contains(github.event.release.tag_name, 'sdk-v') - env: - SDK_VERSION: ${{ steps.versions.outputs.sdk_version }} - working-directory: packages/aws-durable-execution-sdk-python - run: | - python -m pip download \ - --dest dist \ - --no-deps \ - --only-binary=:all: \ - "aws-durable-execution-sdk-python==${SDK_VERSION}" - - - name: Build OTel plugin distribution - working-directory: packages/aws-durable-execution-sdk-python-otel - run: hatch build - - - name: Verify OTel wheel dependency contract - run: | - OTEL_WHEEL=$(find packages/aws-durable-execution-sdk-python-otel/dist \ - -name 'aws_durable_execution_sdk_python_otel-*.whl' -print -quit) - python .github/scripts/check_otel_wheel_dependencies.py "$OTEL_WHEEL" - - - name: Verify legal files - run: | - python .github/scripts/check_dist_legal_files.py \ - packages/aws-durable-execution-sdk-python \ - packages/aws-durable-execution-sdk-python-otel - - - name: Stage wheels - run: | - mkdir release-dists - cp packages/aws-durable-execution-sdk-python/dist/*.whl release-dists/ - cp packages/aws-durable-execution-sdk-python-otel/dist/*.whl release-dists/ - - - name: Upload wheels - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - with: - name: lambda-layer-distributions - path: release-dists/ - if-no-files-found: error - retention-days: 7 - - build-layer: - needs: build-distributions - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ env.SOURCE_REF }} - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.11" - - - name: Download wheels - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 - with: - name: lambda-layer-distributions - path: release-dists/ - - - name: Build layer zip - id: build-layer - env: - LAYER_ZIP: dist/${{ env.LAYER_NAME }}.zip - run: | - SDK_WHEEL=$(find release-dists -name 'aws_durable_execution_sdk_python-*.whl' -print -quit) - OTEL_WHEEL=$(find release-dists -name 'aws_durable_execution_sdk_python_otel-*.whl' -print -quit) - python .github/scripts/build_lambda_layer.py \ - --sdk-distribution "$SDK_WHEEL" \ - --otel-distribution "$OTEL_WHEEL" \ - --output "$LAYER_ZIP" - echo "layer_zip=${LAYER_ZIP}" >> "$GITHUB_OUTPUT" - - - name: Upload layer artifact - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - with: - name: otel-plugin-layer - path: ${{ steps.build-layer.outputs.layer_zip }} - if-no-files-found: error - retention-days: 30 - - publish-layer: - needs: [build-distributions, build-layer] - runs-on: ubuntu-latest - environment: - name: lambda-layer-publish - permissions: - contents: read - id-token: write - env: - LAYER_REGIONS: ${{ inputs.regions || vars.LAYER_PUBLISH_REGIONS }} - - steps: - - name: Download layer artifact - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 - with: - name: otel-plugin-layer - path: dist/ - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0 - with: - role-to-assume: ${{ secrets.LAYER_PUBLISH_ROLE_ARN }} - role-session-name: otelLayerPublish - aws-region: us-east-1 - - - name: Publish layer versions - env: - LAYER_ZIP: dist/${{ env.LAYER_NAME }}.zip - SDK_VERSION: ${{ needs.build-distributions.outputs.sdk_version }} - OTEL_VERSION: ${{ needs.build-distributions.outputs.otel_version }} - run: | - PUBLISHED=false - FAILED_REGIONS=() - LOCAL_CODE_SHA256=$(openssl dgst -sha256 -binary "$LAYER_ZIP" | openssl base64 -A) - REGION_LIST=${LAYER_REGIONS:-$DEFAULT_LAYER_REGIONS} - IFS=',' read -ra REGIONS <<< "$REGION_LIST" - for REGION in "${REGIONS[@]}"; do - REGION=$(echo "$REGION" | xargs) - if [ -z "$REGION" ]; then - continue - fi - - LAYER_DESCRIPTION="AWS Durable Execution SDK ${SDK_VERSION} OTel plugin ${OTEL_VERSION} sha256:${LOCAL_CODE_SHA256}" - if ! EXISTING_RESULT=$(aws lambda list-layer-versions \ - --layer-name "$LAYER_NAME" \ - --region "$REGION" \ - --query "LayerVersions[?Description=='${LAYER_DESCRIPTION}'] | [0].[LayerVersionArn,Version]" \ - --output text 2>&1); then - if [[ "$EXISTING_RESULT" == *"ResourceNotFoundException"* ]]; then - EXISTING_RESULT="" - else - echo "$EXISTING_RESULT" >&2 - echo "::warning::Failed to list existing layer versions in ${REGION}" - FAILED_REGIONS+=("$REGION") - continue - fi - fi - - if [ -n "$EXISTING_RESULT" ] && [ "$EXISTING_RESULT" != "None" ]; then - read -r LAYER_VERSION_ARN VERSION_NUMBER <<< "$EXISTING_RESULT" - if ! EXISTING_CODE_SHA256=$(aws lambda get-layer-version \ - --layer-name "$LAYER_NAME" \ - --version-number "$VERSION_NUMBER" \ - --region "$REGION" \ - --query 'Content.CodeSha256' \ - --output text 2>&1); then - echo "$EXISTING_CODE_SHA256" >&2 - echo "::warning::Failed to read ${LAYER_VERSION_ARN}" - FAILED_REGIONS+=("$REGION") - continue - fi - if [ "$EXISTING_CODE_SHA256" != "$LOCAL_CODE_SHA256" ]; then - echo "::error::Artifact hash mismatch for ${LAYER_VERSION_ARN}" - FAILED_REGIONS+=("$REGION") - continue - fi - echo "Reusing ${LAYER_VERSION_ARN}" - else - if ! PUBLISH_RESULT=$(aws lambda publish-layer-version \ - --layer-name "$LAYER_NAME" \ - --description "$LAYER_DESCRIPTION" \ - --zip-file "fileb://${LAYER_ZIP}" \ - --compatible-runtimes python3.11 python3.12 python3.13 python3.14 \ - --compatible-architectures x86_64 arm64 \ - --license-info Apache-2.0 \ - --region "$REGION" \ - --query '[LayerVersionArn,Version,Content.CodeSha256]' \ - --output text); then - echo "::warning::Failed to publish the layer in ${REGION}" - FAILED_REGIONS+=("$REGION") - continue - fi - read -r LAYER_VERSION_ARN VERSION_NUMBER PUBLISHED_CODE_SHA256 <<< "$PUBLISH_RESULT" - if [ "$PUBLISHED_CODE_SHA256" != "$LOCAL_CODE_SHA256" ]; then - echo "::error::Published artifact hash mismatch for ${LAYER_VERSION_ARN}" - FAILED_REGIONS+=("$REGION") - continue - fi - fi - - if ! PERMISSION_RESULT=$(aws lambda add-layer-version-permission \ - --layer-name "$LAYER_NAME" \ - --version-number "$VERSION_NUMBER" \ - --statement-id public-layer-access \ - --action lambda:GetLayerVersion \ - --principal "*" \ - --region "$REGION" \ - 2>&1); then - if [[ "$PERMISSION_RESULT" != *"ResourceConflictException"* ]]; then - echo "$PERMISSION_RESULT" >&2 - echo "::warning::Failed to grant public access to ${LAYER_VERSION_ARN}" - FAILED_REGIONS+=("$REGION") - continue - fi - fi - - echo "Available ${LAYER_VERSION_ARN}" - echo "- \`${LAYER_VERSION_ARN}\`" >> "$GITHUB_STEP_SUMMARY" - PUBLISHED=true - done - - if [ "${#FAILED_REGIONS[@]}" -gt 0 ]; then - echo "::error::Layer publishing failed in: ${FAILED_REGIONS[*]}" - exit 1 - fi - - if [ "$PUBLISHED" != true ]; then - echo "No AWS regions were configured for layer publishing." - exit 1 - fi diff --git a/.github/workflows/test-parser.yml b/.github/workflows/test-parser.yml index 68c26030d..5a2534088 100644 --- a/.github/workflows/test-parser.yml +++ b/.github/workflows/test-parser.yml @@ -3,7 +3,6 @@ name: Test GitHub Scripts on: pull_request: paths: - - '.github/scripts/build_lambda_layer.py' - '.github/scripts/check_otel_wheel_dependencies.py' - '.github/scripts/current_latest_version.py' - '.github/scripts/parse_sdk_branch.py' @@ -23,7 +22,6 @@ on: push: branches: [ main ] paths: - - '.github/scripts/build_lambda_layer.py' - '.github/scripts/check_otel_wheel_dependencies.py' - '.github/scripts/current_latest_version.py' - '.github/scripts/parse_sdk_branch.py' @@ -68,15 +66,12 @@ jobs: activate-environment: true - name: Install test dependencies - # build_lambda_layer.py shells out to `python -m pip`; a uv-created venv - # has no pip, so install it alongside the test dependencies. - run: uv pip install packaging pytest pip PyYAML==6.0.2 + run: uv pip install packaging pytest PyYAML==6.0.2 - name: Run script tests run: | python -m pytest \ .github/scripts/tests/test_ai_pr_review_workflow.py \ - .github/scripts/tests/test_build_lambda_layer.py \ .github/scripts/tests/test_check_otel_wheel_dependencies.py \ .github/scripts/tests/test_current_latest_version.py \ .github/scripts/tests/test_js_examples.py \ diff --git a/RELEASING.md b/RELEASING.md index 0a26c2c69..5b8aa1369 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -121,35 +121,6 @@ instead of being treated as a missing image. ECR visibility is polled for up to `latest` without a recognized version tag, or pointing to a prerelease ahead of the stable release being published, requires manual inspection. -### Lambda layers - -Releases containing an `otel-v` tag also trigger the -[`lambda-layer-publish.yml`](.github/workflows/lambda-layer-publish.yml) -workflow. It builds the SDK and OTel plugin into Lambda layers for each -supported Python runtime and architecture, then publishes public versions of the -`aws-durable-execution-sdk-python-otel-plugin` layer. -The archive resolves and includes the selected Core wheel's runtime dependencies, -including boto3/botocore, so it does not depend on the Lambda runtime already -having the required service model. The plugin wheel is installed without its -optional provider dependencies; ADOT/community layers continue to own those. -For OTel-only releases, the workflow downloads the exact SDK version pinned by -`layer.sdk-version` in `.github/lambda-layer-publish.toml`; that version must -already be published to PyPI. Combined SDK and OTel releases require the pin to -match the new SDK version and build both distributions from the tagged source. - -The publishing job uses the `lambda-layer-publish` GitHub environment and its -`LAYER_PUBLISH_ROLE_ARN` secret. Set the optional `LAYER_PUBLISH_REGIONS` -environment variable to a comma-separated list of AWS Regions. When unset, the -workflow publishes to every commercial AWS Region supported by Lambda. -The workflow can also be run manually from the Actions tab on `main`; its -optional `regions` input overrides `LAYER_PUBLISH_REGIONS` for that run. -Each runtime and architecture layer archive is built once and retained as a -workflow artifact so retries publish the exact same resolved dependencies. Its -SHA-256 is included in the layer description and verified before reuse. -The publishing role must allow `lambda:PublishLayerVersion` and -`lambda:AddLayerVersionPermission`, as well as `lambda:ListLayerVersions` and -`lambda:GetLayerVersion` for identity-checked, idempotent release retries. - ## Release Notes Format For the coordinated Core 3.0 / OTel 2.0 release, include the @@ -199,8 +170,6 @@ Before publishing a release: - [ ] For a first release of a new distribution name (for example `aws-durable-execution-sdk-python-telemetry-exporter`), PyPI trusted publishing is configured for that name -- [ ] OTel layer SDK pin identifies a compatible published SDK, or matches the - SDK version included in a combined release - [ ] Changes merged to `main` - [ ] CI checks pass on `main` - [ ] Release notes written with separate sections per package diff --git a/packages/aws-durable-execution-sdk-python-otel/README.md b/packages/aws-durable-execution-sdk-python-otel/README.md index 6ae1b7692..8e9ed0816 100644 --- a/packages/aws-durable-execution-sdk-python-otel/README.md +++ b/packages/aws-durable-execution-sdk-python-otel/README.md @@ -27,12 +27,6 @@ The base package intentionally does not install OpenTelemetry libraries. The Lambda layer supplies a version-aligned API, SDK, exporter, and propagators, preventing packages in the function artifact from shadowing parts of the layer. -The generated `aws-durable-execution-sdk-python-otel-plugin` Lambda layer includes -Core's runtime dependencies, including its required boto3/botocore service model. -It still leaves OpenTelemetry provider packages to the ADOT/community layer. -Dependencies bundled in the function artifact take precedence over layer packages; -those application-owned boto3/botocore versions must also meet Core's minimum. - For an application that configures its own OpenTelemetry provider instead of using a Lambda layer: diff --git a/packages/aws-durable-execution-sdk-python-otel/tests/test_package_metadata.py b/packages/aws-durable-execution-sdk-python-otel/tests/test_package_metadata.py index 265a37a5d..756cd1b37 100644 --- a/packages/aws-durable-execution-sdk-python-otel/tests/test_package_metadata.py +++ b/packages/aws-durable-execution-sdk-python-otel/tests/test_package_metadata.py @@ -154,23 +154,6 @@ def test_core_dependency_bound_matches_the_core_major_in_this_repository() -> No assert Version(lower_bound) <= Version(core_version) -def test_layer_sdk_pin_matches_the_core_version_in_this_repository() -> None: - """The OTel layer pin selects the core wheel bundled into the published layer. - - A combined SDK and OTel release fails outright when the pin disagrees with the - released SDK version, and an OTel-only release downloads exactly the pinned - version from PyPI. A stale pin therefore either blocks the release or ships a - layer whose core cannot run this plugin, so the pin tracks the core version - this repository builds. - """ - metadata_path = REPOSITORY_ROOT / ".github" / "lambda-layer-publish.toml" - - with metadata_path.open("rb") as metadata_file: - pinned_version = tomllib.load(metadata_file)["layer"]["sdk-version"] - - assert pinned_version == _core_version() - - def test_core_dependency_excludes_the_next_core_major() -> None: """A lower bound alone is the same defect one major later.