From 3a9f1f7209e8cb2c9805398696046c04e600a2bd Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 11:47:50 -0800 Subject: [PATCH 01/52] feat: build emulator image --- .github/workflows/ecr-release.yml | 154 ++++++++++++++++++ .gitignore | 2 + emulator/DockerFile | 5 + emulator/README.md | 127 +++++++++++++++ emulator/pyproject.toml | 125 ++++++++++++++ .../__about__.py | 4 + .../__init__.py | 1 + .../__main__.py | 6 + .../config.py | 124 ++++++++++++++ .../factory.py | 93 +++++++++++ .../server.py | 125 ++++++++++++++ emulator/tests/__init__.py | 1 + emulator/tests/test_config.py | 73 +++++++++ emulator/tests/test_factory.py | 82 ++++++++++ emulator/tests/test_server.py | 35 ++++ 15 files changed, 957 insertions(+) create mode 100644 .github/workflows/ecr-release.yml create mode 100644 emulator/DockerFile create mode 100644 emulator/README.md create mode 100644 emulator/pyproject.toml create mode 100644 emulator/src/aws_lambda_durable_functions_emulator/__about__.py create mode 100644 emulator/src/aws_lambda_durable_functions_emulator/__init__.py create mode 100644 emulator/src/aws_lambda_durable_functions_emulator/__main__.py create mode 100644 emulator/src/aws_lambda_durable_functions_emulator/config.py create mode 100644 emulator/src/aws_lambda_durable_functions_emulator/factory.py create mode 100644 emulator/src/aws_lambda_durable_functions_emulator/server.py create mode 100644 emulator/tests/__init__.py create mode 100644 emulator/tests/test_config.py create mode 100644 emulator/tests/test_factory.py create mode 100644 emulator/tests/test_server.py diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml new file mode 100644 index 0000000..770a985 --- /dev/null +++ b/.github/workflows/ecr-release.yml @@ -0,0 +1,154 @@ +name: ecr-release.yml +on: + on: + push: + branches: [ "feat/publish-emulator-image" ] + release: + types: [published] + +permissions: + contents: read + id-token: write # This is required for requesting the JWT + +env: + path_to_dockerfile: "bin/DockerFile" + docker_build_dir: "bin" + aws_region: "us-east-1" + ecr_repository_name: "o4w4w0v6/aws-durable-execution-emulator" + +jobs: + build-and-upload-image-to-ecr: + runs-on: ubuntu-latest + outputs: + full_image_arm64: ${{ steps.build-publish.outputs.full_image_arm64 }} + full_image_x86_64: ${{ steps.build-publish.outputs.full_image_x86_64 }} + ecr_registry_repository: ${{ steps.build-publish.outputs.ecr_registry_repository }} + strategy: + matrix: + include: + - arch: x86_64 + - arch: arm64 + steps: + - name: Grab version from generate-version job + id: version + env: + VERSION: $${{ github.event.release.name }} + run: | + echo "$VERSION" + - uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install hatch + - name: Set up QEMU for multi-platform builds + if: matrix.arch == 'arm64' + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.ECR_UPLOAD_IAM_ROLE_ARN }} + aws-region: ${{ env.aws_region }} + - name: Login to Amazon ECR + id: login-ecr-public + uses: aws-actions/amazon-ecr-login@v2 + with: + registry-type: public + - name: Build, tag, and push image to Amazon ECR + id: build-publish + shell: bash + env: + ECR_REGISTRY: ${{ steps.login-ecr-public.outputs.registry }} + ECR_REPOSITORY: ${{ env.ecr_repository_name }} + IMAGE_TAG: "${{ env.image_tag }}${{ needs.generate-version.outputs.version }}" + PER_ARCH_IMAGE_TAG: "${{ matrix.arch }}${{ needs.generate-version.outputs.version }}" + run: | + if [ "${{ matrix.arch }}" = "x86_64" ]; then + docker build --platform linux/amd64 --provenance false "${{ env.docker_build_dir }}" -f "${{ env.path_to_dockerfile }}" -t "$ECR_REGISTRY/$ECR_REPOSITORY:$PER_ARCH_IMAGE_TAG" + else + docker build --platform linux/arm64 --provenance false "${{ env.docker_build_dir }}" -f "${{ env.path_to_dockerfile }}" -t "$ECR_REGISTRY/$ECR_REPOSITORY:$PER_ARCH_IMAGE_TAG" + fi + docker push "$ECR_REGISTRY/$ECR_REPOSITORY:$PER_ARCH_IMAGE_TAG" + echo "IMAGE $PER_ARCH_IMAGE_TAG is pushed to $ECR_REGISTRY/$ECR_REPOSITORY" + echo "image_tag=$PER_ARCH_IMAGE_TAG" + echo "full_image=$ECR_REGISTRY/$ECR_REPOSITORY:$PER_ARCH_IMAGE_TAG" + echo "ecr_registry_repository=$ECR_REGISTRY/$ECR_REPOSITORY" >> $GITHUB_OUTPUT + echo "full_image_${{ matrix.arch }}=$ECR_REGISTRY/$ECR_REPOSITORY:$PER_ARCH_IMAGE_TAG" >> $GITHUB_OUTPUT + create-ecr-manifest-per-arch: + runs-on: ubuntu-latest + needs: [build-and-upload-image-to-ecr] + steps: + - name: Grab image and registry/repository name from previous steps + id: ecr_names + env: + ECR_REGISTRY_REPOSITORY: ${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }} + FULL_IMAGE_ARM64: ${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }} + FULL_IMAGE_X86_64: ${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }} + run: | + echo "full_image_arm64=$FULL_IMAGE_ARM64" + echo "ecr_registry_repository=$ECR_REGISTRY_REPOSITORY" + echo "full_image_x86_64=$FULL_IMAGE_X86_64" + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.ECR_UPLOAD_IAM_ROLE_ARN }} + aws-region: ${{ env.aws_region }} + - name: Login to Amazon ECR + id: login-ecr-public + uses: aws-actions/amazon-ecr-login@v2 + with: + registry-type: public + - name: Create ECR manifest with explicit tag + id: create-ecr-manifest-explicit + run: | + docker manifest create "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:$${{ github.event.release.name }}" \ + "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ + "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" + - name: Annotate ECR manifest with explicit arm64 tag + id: annotate-ecr-manifest-explicit-arm64 + run: | + docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:$${{ github.event.release.name }}" \ + "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" \ + --arch arm64 \ + --os linux + - name: Annotate ECR manifest with explicit amd64 tag + id: annotate-ecr-manifest-explicit-amd64 + run: | + docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:$${{ github.event.release.name }}" \ + "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ + --arch amd64 \ + --os linux + - name: Push ECR manifest with explicit version + id: push-ecr-manifest-explicit + run: | + docker manifest push "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:$${{ github.event.release.name }}" + - name: Create ECR manifest with latest tag + id: create-ecr-manifest-latest + run: | + docker manifest create "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}" \ + "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" \ + "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" + - name: Annotate ECR manifest with latest tag arm64 + id: annotate-ecr-manifest-latest-arm64 + run: | + docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}" \ + "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" \ + --arch arm64 \ + --os linux + - name: Annotate ECR manifest with latest tag amd64 + id: annotate-ecr-manifest-latest-amd64 + run: | + docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}" \ + "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ + --arch amd64 \ + --os linux + - name: Push ECR manifest with latest + id: push-ecr-manifest-latest + run: | + docker manifest push "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index ea7c0c4..8194eaa 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ dist/ examples/build/* examples/*.zip + +durable-executions.db* diff --git a/emulator/DockerFile b/emulator/DockerFile new file mode 100644 index 0000000..a7522ed --- /dev/null +++ b/emulator/DockerFile @@ -0,0 +1,5 @@ +FROM public.ecr.aws/ubuntu/python:3.13-25.10_stable + +COPY emulator /emulator + +RUN hatch run pip install -e . \ No newline at end of file diff --git a/emulator/README.md b/emulator/README.md new file mode 100644 index 0000000..9ce26aa --- /dev/null +++ b/emulator/README.md @@ -0,0 +1,127 @@ +# AWS Durable Execution Emulator + +A local emulator for AWS Lambda durable functions that enables local development and testing of durable function applications. Powered by the AWS Durable Execution Testing SDK for Python. + +## Overview + +The AWS Lambda Durable Execution Emulator provides a local development environment for building and testing durable function applications before deploying to AWS Lambda. It uses the AWS Durable Execution Testing SDK for Python as its execution engine, providing robust durable execution capabilities with full AWS API compatibility. + +## Features + +- **Local Development**: Run durable functions locally without AWS infrastructure +- **API Compatibility**: Compatible with AWS Lambda Durable Functions APIs +- **Health Check Endpoint**: Built-in health monitoring +- **Logging**: Configurable logging levels for debugging +- **Testing Support**: Built-in test framework support + +## Installation + +### From source + +```bash +git clone https://github.com/aws/aws-lambda-durable-functions-emulator.git +cd aws-lambda-durable-functions-emulator +hatch run pip install -e . +``` + +## Usage + +### Starting the Emulator + +```bash +# Using the installed command +durable-functions-emulator + +# Or using hatch for development +hatch run dev + +# With custom host and port +durable-functions-emulator --host 0.0.0.0 --port 8080 +``` + +### Environment Variables + +- `HOST`: Server host (default: 0.0.0.0) +- `PORT`: Server port (default: 5000) +- `LOG`: Logging level (default: INFO) +- `STORAGE_DIR`: Directory for persistent storage +- `EXECUTION_STORE_TYPE`: Type of execution store (default: sqlite) + - `filesystem`: File-based storage + - `sqlite`: SQLite database storage (default) +- `LAMBDA_ENDPOINT`: Lambda endpoint URL for testing +- `LOCAL_RUNNER_ENDPOINT`: Local runner endpoint URL +- `LOCAL_RUNNER_REGION`: AWS region for local runner +- `LOCAL_RUNNER_MODE`: Runner mode (default: local) + +### Health Check + +The emulator provides a health check endpoint: + +```bash +curl http://localhost:5000/ping +``` + +## Development + +### Prerequisites + +- Python 3.13+ +- [Hatch](https://hatch.pypa.io/) for project management + +### Setup + +```bash +git clone https://github.com/aws/aws-lambda-durable-functions-emulator.git +cd aws-lambda-durable-functions-emulator +hatch run pip install -e . +``` + +### Running Tests + +```bash +# Run all tests +hatch run test + +# Run with coverage +hatch run test:cov + +# Type checking +hatch run types:check +``` + +### Building + +```bash +hatch build +``` + +## API Reference + +### Health Check + +- **GET** `/ping` - Returns emulator status + +### Durable Execution APIs + +- **POST** `/2025-12-01/durable-execution-state//checkpoint` - Checkpoint execution state +- **GET** `/2025-12-01/durable-execution-state//getState` - Get execution state +- **GET** `/2025-12-01/durable-executions/` - Get execution details +- **GET** `/2025-12-01/durable-executions//history` - Get execution history + +### Callback APIs + +- **POST** `/2025-12-01/durable-execution-callbacks//succeed` - Send success callback +- **POST** `/2025-12-01/durable-execution-callbacks//fail` - Send failure callback +- **POST** `/2025-12-01/durable-execution-callbacks//heartbeat` - Send heartbeat callback + +## Contributing + +We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details. + +## License + +This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. + +## Security + +See [CONTRIBUTING.md](CONTRIBUTING.md#security-issue-notifications) for more information. diff --git a/emulator/pyproject.toml b/emulator/pyproject.toml new file mode 100644 index 0000000..c0c4fd5 --- /dev/null +++ b/emulator/pyproject.toml @@ -0,0 +1,125 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "aws-lambda-durable-functions-emulator" +dynamic = ["version"] +description = "Local emulator for AWS Lambda Durable Functions" +readme = "README.md" +requires-python = ">=3.13" +license = "Apache-2.0" +keywords = ["aws", "lambda", "durable", "functions", "emulator"] +authors = [ + { name = "AWS Lambda Team" }, +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.13", +] +dependencies = [ + "aws-durable-execution-sdk-python-testing @ git+ssh://git@github.com/aws/aws-durable-execution-sdk-python-testing.git", + "aws_durable_execution_sdk_python @ git+ssh://git@github.com/aws/aws-durable-execution-sdk-python.git", + "requests>=2.31.0,<3.0.0", + "boto3>=1.34.0,<2.0.0", +] + +[project.urls] +Documentation = "https://github.com/aws/aws-lambda-durable-functions-emulator#readme" +Issues = "https://github.com/aws/aws-lambda-durable-functions-emulator/issues" +Source = "https://github.com/aws/aws-lambda-durable-functions-emulator" + +[project.scripts] +durable-functions-emulator = "aws_lambda_durable_functions_emulator.server:main" + +[tool.hatch.build.targets.sdist] +packages = ["src/aws_lambda_durable_functions_emulator"] + +[tool.hatch.build.targets.wheel] +packages = ["src/aws_lambda_durable_functions_emulator"] + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.version] +path = "src/aws_lambda_durable_functions_emulator/__about__.py" + +[tool.hatch.envs.default] +dependencies = [ + "pytest", +] +dev-mode = true + +[tool.hatch.envs.default.scripts] +test = "pytest {args:tests}" +dev = "python -m aws_lambda_durable_functions_emulator.server --host 127.0.0.1 --port 5000" +emulator = "python -m aws_lambda_durable_functions_emulator.server {args}" + +[tool.hatch.envs.test] +dependencies = [ + "coverage[toml]", + "pytest", + "pytest-cov", +] + +[tool.hatch.envs.test.scripts] +test = "pytest {args:tests}" +cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=src/aws_lambda_durable_functions_emulator --cov=tests --cov-fail-under=60" + +[tool.hatch.envs.types] +extra-dependencies = [ + "mypy>=1.0.0", + "pytest", + "types-boto3", + "boto3-stubs[essential]", +] +[tool.hatch.envs.types.scripts] +check = "mypy --install-types --non-interactive {args:src/aws_lambda_durable_functions_emulator tests}" + +[tool.hatch.envs.lint] +extra-dependencies = [ + "ruff>=0.1.0", +] +[tool.hatch.envs.lint.scripts] +check = "ruff check {args:src tests}" +format = "ruff format {args:src tests}" +fix = "ruff check --fix {args:src tests}" + +[tool.ruff] +target-version = "py313" +line-length = 120 + +[tool.ruff.lint.isort] +known-first-party = ["aws_lambda_durable_functions_emulator"] + +[tool.ruff.lint.flake8-tidy-imports] +ban-relative-imports = "all" + +[tool.ruff.lint.per-file-ignores] +# Tests can use magic values, assertions, and relative imports +"tests/**/*" = ["PLR2004", "S101", "TID252", "S104"] +# Allow binding to all interfaces for server configuration +"src/aws_lambda_durable_functions_emulator/config.py" = ["S104"] + +[tool.coverage.run] +source_pkgs = ["aws_lambda_durable_functions_emulator", "tests"] +branch = true +parallel = true +omit = [ + "src/aws_lambda_durable_functions_emulator/__about__.py", +] + +[tool.coverage.paths] +aws_lambda_durable_functions_emulator = ["src/aws_lambda_durable_functions_emulator", "*/aws-durable-execution-sdk-python-testing/emulator/src/aws_lambda_durable_functions_emulator"] +tests = ["tests", "*/aws-durable-execution-sdk-python-testing/emulator/tests"] + +[tool.coverage.report] +exclude_lines = [ + "no cov", + "if __name__ == .__main__.:", + "if TYPE_CHECKING:", +] \ No newline at end of file diff --git a/emulator/src/aws_lambda_durable_functions_emulator/__about__.py b/emulator/src/aws_lambda_durable_functions_emulator/__about__.py new file mode 100644 index 0000000..1c35361 --- /dev/null +++ b/emulator/src/aws_lambda_durable_functions_emulator/__about__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2025-present Amazon.com, Inc. or its affiliates. +# +# SPDX-License-Identifier: Apache-2.0 +__version__ = "0.1.0" diff --git a/emulator/src/aws_lambda_durable_functions_emulator/__init__.py b/emulator/src/aws_lambda_durable_functions_emulator/__init__.py new file mode 100644 index 0000000..27d6498 --- /dev/null +++ b/emulator/src/aws_lambda_durable_functions_emulator/__init__.py @@ -0,0 +1 @@ +"""AWS Lambda Durable Functions Emulator""" diff --git a/emulator/src/aws_lambda_durable_functions_emulator/__main__.py b/emulator/src/aws_lambda_durable_functions_emulator/__main__.py new file mode 100644 index 0000000..63ac5b1 --- /dev/null +++ b/emulator/src/aws_lambda_durable_functions_emulator/__main__.py @@ -0,0 +1,6 @@ +"""CLI entry point for the durable functions emulator""" + +from aws_lambda_durable_functions_emulator.server import main + +if __name__ == "__main__": + main() diff --git a/emulator/src/aws_lambda_durable_functions_emulator/config.py b/emulator/src/aws_lambda_durable_functions_emulator/config.py new file mode 100644 index 0000000..77bf177 --- /dev/null +++ b/emulator/src/aws_lambda_durable_functions_emulator/config.py @@ -0,0 +1,124 @@ +import logging +import os +from dataclasses import dataclass, field +from pathlib import Path +from urllib.parse import urlparse + +from aws_durable_execution_sdk_python_testing.stores.base import StoreType +from aws_durable_execution_sdk_python_testing.web.server import WebServiceConfig + +# Constants +MAX_PORT = 65535 + + +def get_host() -> str: + """Get the server host from environment variable or default.""" + return os.environ.get("HOST", "0.0.0.0") + + +def get_port() -> int: + """Get the server port from environment variable or default.""" + return int(os.environ.get("PORT", "5000")) + + +def get_log_level() -> int: + """Get the logging level from environment variable or default.""" + log_level_str = os.environ.get("LOG", "INFO").upper() + return getattr(logging, log_level_str, logging.INFO) + + +def get_lambda_endpoint() -> str: + """Get the Lambda endpoint from environment variable.""" + return os.environ.get("LAMBDA_ENDPOINT", "http://localhost:3001") + + +def get_storage_dir() -> str | None: + """Get the storage directory from environment variable.""" + return os.environ.get("STORAGE_DIR") + + +def get_execution_store_type() -> str: + """Get the execution store type from environment variable.""" + return os.environ.get("EXECUTION_STORE_TYPE", StoreType.SQLITE.value).lower() + + +@dataclass +class EmulatorConfig: + """Configuration for the AWS Lambda Durable Functions Emulator.""" + + host: str = field(default_factory=get_host) + port: int = field(default_factory=get_port) + log_level: int = field(default_factory=get_log_level) + lambda_endpoint: str = field(default_factory=get_lambda_endpoint) + storage_dir: str | None = field(default_factory=get_storage_dir) + execution_store_type: str = field(default_factory=get_execution_store_type) + + def __post_init__(self): + """Validate configuration after initialization.""" + self._validate_config() + + def to_web_service_config(self): + """Convert to testing library web service config.""" + return WebServiceConfig(host=self.host, port=self.port, log_level=self.log_level) + + def _validate_config(self): + """Validate all configuration parameters.""" + + # Validate Lambda endpoint URL + def _raise_invalid_endpoint(endpoint: str, cause: Exception | None = None) -> None: + msg = f"Invalid Lambda endpoint URL: {endpoint}" + raise ValueError(msg) from cause + + try: + parsed = urlparse(self.lambda_endpoint) + if not parsed.scheme or not parsed.netloc: + _raise_invalid_endpoint(self.lambda_endpoint) + except (ValueError, TypeError) as e: + _raise_invalid_endpoint(self.lambda_endpoint, e) + + # Validate storage directory if specified + if self.storage_dir: + + def _raise_storage_error(storage_dir: str, cause: Exception | None = None) -> None: + msg = f"Storage directory is not writable: {storage_dir}" + raise ValueError(msg) from cause + + def _raise_access_error(storage_dir: str, cause: Exception | None = None) -> None: + msg = f"Cannot access storage directory: {storage_dir}" + raise ValueError(msg) from cause + + try: + storage_path = Path(self.storage_dir) + if storage_path.exists(): + if not storage_path.is_dir(): + msg = f"Storage path is not a directory: {self.storage_dir}" + raise ValueError(msg) + # Test write permissions + test_file = storage_path / ".write_test" + try: + test_file.write_text("test") + test_file.unlink() + except (OSError, PermissionError) as e: + _raise_storage_error(self.storage_dir, e) + else: + # Try to create the directory + storage_path.mkdir(parents=True, exist_ok=True) + except (OSError, PermissionError) as e: + _raise_access_error(self.storage_dir, e) + + # Validate log level + valid_log_levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL] + if self.log_level not in valid_log_levels: + msg = f"Invalid log level: {self.log_level}. Must be one of {valid_log_levels}" + raise ValueError(msg) + + # Validate port range + if not (1 <= self.port <= MAX_PORT): + msg = f"Invalid port: {self.port}. Must be between 1 and {MAX_PORT}" + raise ValueError(msg) + + # Validate execution store type + valid_store_types = [StoreType.FILESYSTEM.value, StoreType.SQLITE.value] + if self.execution_store_type not in valid_store_types: + msg = f"Invalid execution store type: {self.execution_store_type}. Must be one of {valid_store_types}" + raise ValueError(msg) diff --git a/emulator/src/aws_lambda_durable_functions_emulator/factory.py b/emulator/src/aws_lambda_durable_functions_emulator/factory.py new file mode 100644 index 0000000..e96d1d7 --- /dev/null +++ b/emulator/src/aws_lambda_durable_functions_emulator/factory.py @@ -0,0 +1,93 @@ +"""Factory for creating testing library components with emulator configuration.""" + +import logging +import os +from pathlib import Path +from typing import TYPE_CHECKING + +import aws_durable_execution_sdk_python +import botocore.loaders +from aws_durable_execution_sdk_python_testing.checkpoint.processor import CheckpointProcessor +from aws_durable_execution_sdk_python_testing.executor import Executor +from aws_durable_execution_sdk_python_testing.invoker import LambdaInvoker +from aws_durable_execution_sdk_python_testing.scheduler import Scheduler +from aws_durable_execution_sdk_python_testing.stores.base import ( + StoreType, +) +from aws_durable_execution_sdk_python_testing.stores.filesystem import ( + FileSystemExecutionStore, +) +from aws_durable_execution_sdk_python_testing.stores.sqlite import ( + SQLiteExecutionStore, +) + +if TYPE_CHECKING: + from aws_lambda_durable_functions_emulator.config import EmulatorConfig + +logger = logging.getLogger(__name__) + + +class TestingLibraryComponentFactory: + """Factory for creating testing library components.""" + + @staticmethod + def create_store(config: "EmulatorConfig"): + """Create execution store based on emulator configuration.""" + store_type = config.execution_store_type + + if store_type == StoreType.SQLITE.value: + logger.info("Creating SQLite execution store") + if config.storage_dir: + db_path = Path(config.storage_dir) / "durable-executions.db" + else: + db_path = Path("durable-executions.db") + return SQLiteExecutionStore.create_and_initialize(db_path) + + logger.info("Creating file-system execution store") + return FileSystemExecutionStore.create(config.storage_dir or ".") + + @staticmethod + def create_scheduler(): + """Create scheduler for timer and event management.""" + logger.info("Creating scheduler") + scheduler = Scheduler() + logger.info("Starting scheduler") + scheduler.start() + return scheduler + + @staticmethod + def create_invoker(config: "EmulatorConfig"): + """Create Lambda invoker with emulator configuration.""" + logger.info("Creating Lambda invoker with endpoint: %s", config.lambda_endpoint) + + # Load lambdainternal service model + package_path = os.path.dirname(aws_durable_execution_sdk_python.__file__) + data_path = f"{package_path}/botocore/data" + os.environ["AWS_DATA_PATH"] = data_path + + loader = botocore.loaders.Loader() + loader.search_paths.append(data_path) + + return LambdaInvoker.create(config.lambda_endpoint, "us-east-1") + + @staticmethod + def create_checkpoint_processor(store, scheduler): + logger.info("Creating checkpoint processor") + checkpoint_processor = CheckpointProcessor(store, scheduler) + logger.info("Created checkpoint processor") + return checkpoint_processor + + @staticmethod + def create_executor(config: "EmulatorConfig"): + """Create complete executor with all components.""" + logger.info("Creating executor with all components") + + store = TestingLibraryComponentFactory.create_store(config) + scheduler = TestingLibraryComponentFactory.create_scheduler() + invoker = TestingLibraryComponentFactory.create_invoker(config) + checkpoint_processor = TestingLibraryComponentFactory.create_checkpoint_processor(store, scheduler) + + executor = Executor(store, scheduler, invoker, checkpoint_processor) + checkpoint_processor.add_execution_observer(executor) + + return executor diff --git a/emulator/src/aws_lambda_durable_functions_emulator/server.py b/emulator/src/aws_lambda_durable_functions_emulator/server.py new file mode 100644 index 0000000..4ffd4ec --- /dev/null +++ b/emulator/src/aws_lambda_durable_functions_emulator/server.py @@ -0,0 +1,125 @@ +"""Main server for the AWS Lambda Durable Functions Emulator using testing library""" + +import argparse +import logging +import sys +from typing import TYPE_CHECKING + +from aws_durable_execution_sdk_python_testing.web.server import WebServer + +from aws_lambda_durable_functions_emulator.config import ( + EmulatorConfig, + get_host, + get_lambda_endpoint, + get_log_level, + get_port, + get_storage_dir, +) +from aws_lambda_durable_functions_emulator.factory import TestingLibraryComponentFactory + +if TYPE_CHECKING: + from aws_durable_execution_sdk_python_testing.executor import Executor + +# Configure logging +log_level = get_log_level() +logging.basicConfig(level=log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") +logger = logging.getLogger("durable_functions_emulator") +logger.setLevel(log_level) + +# Suppress third-party debug logging +logging.getLogger("botocore").setLevel(logging.WARNING) +logging.getLogger("boto3").setLevel(logging.WARNING) +logging.getLogger("urllib3").setLevel(logging.WARNING) + +logger.info("Logging level set to: %s", logging.getLevelName(log_level)) + + +class EmulatorServer: + """Emulator server that wraps the testing library's WebServer.""" + + def __init__(self, config: EmulatorConfig) -> None: + self.config = config + + logger.info("Configuration validation passed") + + # Create testing library components using emulator config + self.executor = self._create_executor(config) + + # Convert emulator config to testing library config + web_config = self._create_web_config(config) + + # Use testing library's WebServer directly + self.web_server: WebServer = WebServer(web_config, self.executor) + + logger.info("EmulatorServer initialized on %s:%s", config.host, config.port) + + def _create_executor(self, config: EmulatorConfig) -> "Executor": + """Create executor with emulator configuration using factory.""" + return TestingLibraryComponentFactory.create_executor(config) + + def _create_web_config(self, config: EmulatorConfig): + """Convert emulator config to testing library config.""" + return config.to_web_service_config() + + def start(self): + """Start the emulator server.""" + try: + logger.info("Starting emulator server...") + with self.web_server: + logger.info("Server listening on %s:%s", self.config.host, self.config.port) + self.web_server.serve_forever() + except KeyboardInterrupt: + logger.info("Server shutdown requested by user") + except Exception: + logger.exception("Server error") + raise + finally: + logger.info("Server shutdown complete") + + +def main(): + """Main entry point for the emulator server.""" + parser = argparse.ArgumentParser(description="AWS Lambda Durable Functions Emulator (powered by testing library)") + parser.add_argument("--host", type=str, help="Host to bind to (default: from HOST env var or 0.0.0.0)") + parser.add_argument("--port", type=int, help="Port to bind to (default: from PORT env var or 5000)") + args = parser.parse_args() + + try: + # Create emulator configuration + config = EmulatorConfig(host=args.host or get_host(), port=args.port or get_port()) + + # Create and start emulator server + logger.info("Starting AWS Lambda Durable Functions Emulator on %s:%s", config.host, config.port) + server = EmulatorServer(config) + server.start() + + except ValueError: + logger.exception("Configuration error") + logger.info("Please check your configuration and try again.") + logger.info("Environment variables:") + logger.info(" HOST=%s", config.host if "config" in locals() else get_host()) + logger.info(" PORT=%s", config.port if "config" in locals() else get_port()) + logger.info(" LAMBDA_ENDPOINT=%s", get_lambda_endpoint()) + logger.info(" STORAGE_DIR=%s", get_storage_dir()) + sys.exit(1) + + except ImportError: + logger.exception("Missing dependency") + logger.info("Please install the aws-durable-execution-sdk-python-testing package:") + logger.info(" pip install aws-durable-execution-sdk-python-testing") + sys.exit(1) + + except OSError: + logger.exception("Network error") + logger.info("Failed to bind to %s:%s", config.host, config.port) + logger.info("Please check that the port is not already in use and try again.") + sys.exit(1) + + except Exception: + logger.exception("Unexpected error during startup") + logger.exception("Full error details:") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/emulator/tests/__init__.py b/emulator/tests/__init__.py new file mode 100644 index 0000000..fb3c937 --- /dev/null +++ b/emulator/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for the durable functions emulator""" diff --git a/emulator/tests/test_config.py b/emulator/tests/test_config.py new file mode 100644 index 0000000..cd4648d --- /dev/null +++ b/emulator/tests/test_config.py @@ -0,0 +1,73 @@ +"""Tests for the emulator configuration""" + +import os + +import pytest +from aws_durable_execution_sdk_python_testing.stores.base import StoreType + +from aws_lambda_durable_functions_emulator.config import EmulatorConfig + + +def test_execution_store_type_default(): + """Test that default execution store type is sqlite""" + # Clean up any existing env vars + if "EXECUTION_STORE_TYPE" in os.environ: + del os.environ["EXECUTION_STORE_TYPE"] + + config = EmulatorConfig() + assert config.execution_store_type == StoreType.SQLITE.value + + +def test_execution_store_type_filesystem(): + """Test filesystem execution store type""" + os.environ["EXECUTION_STORE_TYPE"] = StoreType.FILESYSTEM.value + + config = EmulatorConfig() + assert config.execution_store_type == StoreType.FILESYSTEM.value + + # Clean up + del os.environ["EXECUTION_STORE_TYPE"] + + +def test_execution_store_type_sqlite(): + """Test SQLite execution store type""" + os.environ["EXECUTION_STORE_TYPE"] = StoreType.SQLITE.value + + config = EmulatorConfig() + assert config.execution_store_type == StoreType.SQLITE.value + + # Clean up + del os.environ["EXECUTION_STORE_TYPE"] + + +def test_execution_store_type_case_insensitive(): + """Test that execution store type is case insensitive""" + os.environ["EXECUTION_STORE_TYPE"] = "SQLITE" + + config = EmulatorConfig() + assert config.execution_store_type == StoreType.SQLITE.value + + # Clean up + del os.environ["EXECUTION_STORE_TYPE"] + + +def test_execution_store_type_invalid(): + """Test that invalid execution store type raises ValueError""" + os.environ["EXECUTION_STORE_TYPE"] = "invalid" + + with pytest.raises(ValueError, match="Invalid execution store type"): + EmulatorConfig() + + # Clean up + del os.environ["EXECUTION_STORE_TYPE"] + + +def test_execution_store_type_validation(): + """Test that only valid store types are accepted""" + valid_types = [StoreType.FILESYSTEM.value, StoreType.SQLITE.value] + + for store_type in valid_types: + os.environ["EXECUTION_STORE_TYPE"] = store_type + config = EmulatorConfig() + assert config.execution_store_type == store_type + del os.environ["EXECUTION_STORE_TYPE"] diff --git a/emulator/tests/test_factory.py b/emulator/tests/test_factory.py new file mode 100644 index 0000000..6bc51bd --- /dev/null +++ b/emulator/tests/test_factory.py @@ -0,0 +1,82 @@ +"""Tests for the component factory""" + +import os +import tempfile +from pathlib import Path + +from aws_durable_execution_sdk_python_testing.stores.base import StoreType + +from aws_lambda_durable_functions_emulator.config import EmulatorConfig +from aws_lambda_durable_functions_emulator.factory import TestingLibraryComponentFactory + + +def test_create_store_filesystem(): + """Test that filesystem store can be created""" + with tempfile.TemporaryDirectory() as temp_dir: + os.environ["EXECUTION_STORE_TYPE"] = StoreType.FILESYSTEM.value + os.environ["STORAGE_DIR"] = temp_dir + + config = EmulatorConfig() + store = TestingLibraryComponentFactory.create_store(config) + + assert store is not None + assert "FileSystemExecutionStore" in str(type(store)) + + # Clean up + del os.environ["EXECUTION_STORE_TYPE"] + del os.environ["STORAGE_DIR"] + + +def test_create_store_sqlite(): + """Test that SQLite store can be created""" + with tempfile.TemporaryDirectory() as temp_dir: + os.environ["EXECUTION_STORE_TYPE"] = StoreType.SQLITE.value + os.environ["STORAGE_DIR"] = temp_dir + + config = EmulatorConfig() + store = TestingLibraryComponentFactory.create_store(config) + + assert store is not None + assert "SQLiteExecutionStore" in str(type(store)) + + # Verify database file was created + db_path = Path(temp_dir) / "durable-executions.db" + assert db_path.exists() + + # Clean up + del os.environ["EXECUTION_STORE_TYPE"] + del os.environ["STORAGE_DIR"] + + +def test_create_store_default(): + """Test that default store type is sqlite""" + # Clean up any existing env vars + for key in ["EXECUTION_STORE_TYPE", "STORAGE_DIR"]: + if key in os.environ: + del os.environ[key] + + config = EmulatorConfig() + store = TestingLibraryComponentFactory.create_store(config) + + assert store is not None + assert "SQLiteExecutionStore" in str(type(store)) + + +def test_create_scheduler(): + """Test that scheduler can be created""" + scheduler = TestingLibraryComponentFactory.create_scheduler() + assert scheduler is not None + + +def test_create_invoker(): + """Test that invoker can be created""" + config = EmulatorConfig() + invoker = TestingLibraryComponentFactory.create_invoker(config) + assert invoker is not None + + +def test_create_executor(): + """Test that executor can be created with all components""" + config = EmulatorConfig() + executor = TestingLibraryComponentFactory.create_executor(config) + assert executor is not None diff --git a/emulator/tests/test_server.py b/emulator/tests/test_server.py new file mode 100644 index 0000000..a9fbe5f --- /dev/null +++ b/emulator/tests/test_server.py @@ -0,0 +1,35 @@ +"""Tests for the emulator server""" + +import pytest + +from aws_lambda_durable_functions_emulator.config import EmulatorConfig +from aws_lambda_durable_functions_emulator.server import EmulatorServer + + +def test_emulator_config_creation(): + """Test that EmulatorConfig can be created with defaults""" + config = EmulatorConfig() + assert config.host == "0.0.0.0" + assert config.port == 5000 + assert config.lambda_endpoint == "http://localhost:3001" + assert config.storage_dir is None + + +def test_emulator_config_validation(): + """Test that EmulatorConfig validates parameters""" + # Test invalid port + with pytest.raises(ValueError, match="Invalid port"): + EmulatorConfig(port=0) + + # Test invalid Lambda endpoint + with pytest.raises(ValueError, match="Invalid Lambda endpoint URL"): + EmulatorConfig(lambda_endpoint="not-a-url") + + +def test_emulator_server_creation(): + """Test that EmulatorServer can be created""" + config = EmulatorConfig() + server = EmulatorServer(config) + assert server.config == config + assert server.executor is not None + assert server.web_server is not None From 354f0b511eb7d1941912652a2e734e06305a1ca0 Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 12:02:14 -0800 Subject: [PATCH 02/52] chore: fix typo in workflows file ecr-release.yml --- .github/workflows/ecr-release.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 770a985..fedc8d5 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -1,7 +1,6 @@ name: ecr-release.yml on: - on: - push: + push: branches: [ "feat/publish-emulator-image" ] release: types: [published] From 11227c2bea411d0bde856baeae8f8e1e22dadcb6 Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 12:21:31 -0800 Subject: [PATCH 03/52] chore: change paths --- .github/workflows/ecr-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index fedc8d5..d90c68b 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -10,8 +10,8 @@ permissions: id-token: write # This is required for requesting the JWT env: - path_to_dockerfile: "bin/DockerFile" - docker_build_dir: "bin" + path_to_dockerfile: "/emulator/DockerFile" + docker_build_dir: "/bin" aws_region: "us-east-1" ecr_repository_name: "o4w4w0v6/aws-durable-execution-emulator" From 2c92361313527a3ca66c5461a03cd9b20e337de2 Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 12:23:43 -0800 Subject: [PATCH 04/52] chore: try changing to relative paths --- .github/workflows/ecr-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index d90c68b..11c144f 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -10,8 +10,8 @@ permissions: id-token: write # This is required for requesting the JWT env: - path_to_dockerfile: "/emulator/DockerFile" - docker_build_dir: "/bin" + path_to_dockerfile: "emulator/DockerFile" + docker_build_dir: "bin" aws_region: "us-east-1" ecr_repository_name: "o4w4w0v6/aws-durable-execution-emulator" From a38e2a41e7308a14b910c3f1bd71e5bd041a1307 Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 12:25:56 -0800 Subject: [PATCH 05/52] chore: use root directory for build --- .github/workflows/ecr-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 11c144f..ba2fea3 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -11,7 +11,7 @@ permissions: env: path_to_dockerfile: "emulator/DockerFile" - docker_build_dir: "bin" + docker_build_dir: "" aws_region: "us-east-1" ecr_repository_name: "o4w4w0v6/aws-durable-execution-emulator" From 9500f533b5a52c220bedea91e0ae6d988788b26b Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 12:57:06 -0800 Subject: [PATCH 06/52] chore: specify build dir as emulator sub folder --- .github/workflows/ecr-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index ba2fea3..ac05531 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -11,7 +11,7 @@ permissions: env: path_to_dockerfile: "emulator/DockerFile" - docker_build_dir: "" + docker_build_dir: "emulator/" aws_region: "us-east-1" ecr_repository_name: "o4w4w0v6/aws-durable-execution-emulator" From 42366c1b8ee9c960596b8e06b79d228cea3f5da7 Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 13:01:08 -0800 Subject: [PATCH 07/52] chore: copy files to root of container --- emulator/DockerFile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index a7522ed..19c5b1f 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -1,5 +1,5 @@ FROM public.ecr.aws/ubuntu/python:3.13-25.10_stable -COPY emulator /emulator +COPY emulator . RUN hatch run pip install -e . \ No newline at end of file From 8658345cec8de6ec8111b8e337f064434cb57e20 Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 13:38:22 -0800 Subject: [PATCH 08/52] chore: try copy all project files --- emulator/DockerFile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index 19c5b1f..2c99f7b 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -1,5 +1,9 @@ FROM public.ecr.aws/ubuntu/python:3.13-25.10_stable -COPY emulator . +# Install Hatch +RUN pip install --no-cache-dir hatch + +# Copy project files +COPY * ./ RUN hatch run pip install -e . \ No newline at end of file From 7010faed67dbc6182f8f4e4ec58f628697b1fe4c Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 13:48:40 -0800 Subject: [PATCH 09/52] chore: try different image --- .github/workflows/ecr-release.yml | 4 ++-- emulator/DockerFile | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index ac05531..775a4e0 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -65,8 +65,8 @@ jobs: env: ECR_REGISTRY: ${{ steps.login-ecr-public.outputs.registry }} ECR_REPOSITORY: ${{ env.ecr_repository_name }} - IMAGE_TAG: "${{ env.image_tag }}${{ needs.generate-version.outputs.version }}" - PER_ARCH_IMAGE_TAG: "${{ matrix.arch }}${{ needs.generate-version.outputs.version }}" + IMAGE_TAG: "${{ env.image_tag }}${{ ngithub.event.release.name }}" + PER_ARCH_IMAGE_TAG: "${{ matrix.arch }}${{ github.event.release.name }}" run: | if [ "${{ matrix.arch }}" = "x86_64" ]; then docker build --platform linux/amd64 --provenance false "${{ env.docker_build_dir }}" -f "${{ env.path_to_dockerfile }}" -t "$ECR_REGISTRY/$ECR_REPOSITORY:$PER_ARCH_IMAGE_TAG" diff --git a/emulator/DockerFile b/emulator/DockerFile index 2c99f7b..c05d238 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -1,9 +1,10 @@ -FROM public.ecr.aws/ubuntu/python:3.13-25.10_stable +FROM public.ecr.aws/docker/library/python:3.13 +WORKDIR /emulator # Install Hatch RUN pip install --no-cache-dir hatch # Copy project files -COPY * ./ +COPY * . RUN hatch run pip install -e . \ No newline at end of file From da9e837df7d85c2b665aede76fd31f1513580f6a Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 13:49:16 -0800 Subject: [PATCH 10/52] chore: fix typo in ecr-release.yml --- .github/workflows/ecr-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 775a4e0..202d66b 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -65,7 +65,7 @@ jobs: env: ECR_REGISTRY: ${{ steps.login-ecr-public.outputs.registry }} ECR_REPOSITORY: ${{ env.ecr_repository_name }} - IMAGE_TAG: "${{ env.image_tag }}${{ ngithub.event.release.name }}" + IMAGE_TAG: "${{ env.image_tag }}${{ github.event.release.name }}" PER_ARCH_IMAGE_TAG: "${{ matrix.arch }}${{ github.event.release.name }}" run: | if [ "${{ matrix.arch }}" = "x86_64" ]; then From 890c4b90b39b3c7dd77989e93b961818b8281ebf Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 13:57:16 -0800 Subject: [PATCH 11/52] chore: try different copy command --- emulator/DockerFile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index c05d238..f5f2e67 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -5,6 +5,8 @@ WORKDIR /emulator RUN pip install --no-cache-dir hatch # Copy project files -COPY * . +COPY . . + +RUN ls RUN hatch run pip install -e . \ No newline at end of file From da5365aa81b7ce57afa8b239acdc301f4cb5e5d9 Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 14:04:18 -0800 Subject: [PATCH 12/52] chore: try allowing ssh to github --- emulator/DockerFile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index f5f2e67..06576ee 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -7,6 +7,8 @@ RUN pip install --no-cache-dir hatch # Copy project files COPY . . -RUN ls +# allow ssh to github +RUN mkdir -p ~/.ssh && chmod 0700 ~/.ssh +RUN ssh-keyscan github.com >> ~/.ssh/known_hosts RUN hatch run pip install -e . \ No newline at end of file From df1184d7a430a50aa15e351df01df4c1e0fb1d61 Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 14:09:15 -0800 Subject: [PATCH 13/52] chore: try --- emulator/DockerFile | 1 + 1 file changed, 1 insertion(+) diff --git a/emulator/DockerFile b/emulator/DockerFile index 06576ee..84a9f0b 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -8,6 +8,7 @@ RUN pip install --no-cache-dir hatch COPY . . # allow ssh to github +RUN apk add -update ca-certificates && update-ca-certificates RUN mkdir -p ~/.ssh && chmod 0700 ~/.ssh RUN ssh-keyscan github.com >> ~/.ssh/known_hosts From 0be85266720098264efd96f8abd7532a6bb11ff1 Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 14:16:03 -0800 Subject: [PATCH 14/52] chore: use versions released to pip --- emulator/DockerFile | 1 - emulator/pyproject.toml | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index 84a9f0b..06576ee 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -8,7 +8,6 @@ RUN pip install --no-cache-dir hatch COPY . . # allow ssh to github -RUN apk add -update ca-certificates && update-ca-certificates RUN mkdir -p ~/.ssh && chmod 0700 ~/.ssh RUN ssh-keyscan github.com >> ~/.ssh/known_hosts diff --git a/emulator/pyproject.toml b/emulator/pyproject.toml index c0c4fd5..bc7893e 100644 --- a/emulator/pyproject.toml +++ b/emulator/pyproject.toml @@ -22,8 +22,8 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ - "aws-durable-execution-sdk-python-testing @ git+ssh://git@github.com/aws/aws-durable-execution-sdk-python-testing.git", - "aws_durable_execution_sdk_python @ git+ssh://git@github.com/aws/aws-durable-execution-sdk-python.git", + "aws-durable-execution-sdk-python-testing>=1.1.1", + "aws_durable_execution_sdk_python>=1.3.0", "requests>=2.31.0,<3.0.0", "boto3>=1.34.0,<2.0.0", ] From d964e53e926169fbe0c7b3b708587053be303cfd Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 14:24:06 -0800 Subject: [PATCH 15/52] chore: remove typo from exr-release.yml --- .github/workflows/ecr-release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 202d66b..afdd200 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -106,20 +106,20 @@ jobs: - name: Create ECR manifest with explicit tag id: create-ecr-manifest-explicit run: | - docker manifest create "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:$${{ github.event.release.name }}" \ + docker manifest create "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ github.event.release.name }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" - name: Annotate ECR manifest with explicit arm64 tag id: annotate-ecr-manifest-explicit-arm64 run: | - docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:$${{ github.event.release.name }}" \ + docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ github.event.release.name }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" \ --arch arm64 \ --os linux - name: Annotate ECR manifest with explicit amd64 tag id: annotate-ecr-manifest-explicit-amd64 run: | - docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:$${{ github.event.release.name }}" \ + docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ github.event.release.name }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ --arch amd64 \ --os linux From 98c65d60c50ae21a10d6d56b14929de40f6dec39 Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 14:31:59 -0800 Subject: [PATCH 16/52] chore: only use explicit tag on release --- .github/workflows/ecr-release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index afdd200..e36b0b4 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -104,12 +104,14 @@ jobs: with: registry-type: public - name: Create ECR manifest with explicit tag + if: github.event.release.name != '' id: create-ecr-manifest-explicit run: | docker manifest create "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ github.event.release.name }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" - name: Annotate ECR manifest with explicit arm64 tag + if: github.event.release.name != '' id: annotate-ecr-manifest-explicit-arm64 run: | docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ github.event.release.name }}" \ @@ -117,6 +119,7 @@ jobs: --arch arm64 \ --os linux - name: Annotate ECR manifest with explicit amd64 tag + if: github.event.release.name != '' id: annotate-ecr-manifest-explicit-amd64 run: | docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ github.event.release.name }}" \ @@ -124,6 +127,7 @@ jobs: --arch amd64 \ --os linux - name: Push ECR manifest with explicit version + if: github.event.release.name != '' id: push-ecr-manifest-explicit run: | docker manifest push "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:$${{ github.event.release.name }}" From d1f3a2d33b717bd1b6aba385de6b0116aa290068 Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 15:37:52 -0800 Subject: [PATCH 17/52] chore: enable hatch executable --- emulator/DockerFile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index 06576ee..89099cd 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -7,8 +7,10 @@ RUN pip install --no-cache-dir hatch # Copy project files COPY . . -# allow ssh to github -RUN mkdir -p ~/.ssh && chmod 0700 ~/.ssh -RUN ssh-keyscan github.com >> ~/.ssh/known_hosts +# install emulator +RUN hatch run pip install -e . -RUN hatch run pip install -e . \ No newline at end of file +# enable hatch executable +RUN chmod +x /usr/local/bin/hatch + +CMD /usr/local/bin/hatch run durable-functions-emulator --host 0.0.0.0 --port 9014 \ No newline at end of file From a600eccee4fa86a313f4dc35cf8787db543d0a4c Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 15:59:21 -0800 Subject: [PATCH 18/52] chore: check the path of hatch executable --- emulator/DockerFile | 5 ++++- pyproject.toml | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index 89099cd..fcb4915 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -10,7 +10,10 @@ COPY . . # install emulator RUN hatch run pip install -e . +# where is the hatch executable installed +RUN where hatch + # enable hatch executable RUN chmod +x /usr/local/bin/hatch -CMD /usr/local/bin/hatch run durable-functions-emulator --host 0.0.0.0 --port 9014 \ No newline at end of file +CMD ["/usr/local/bin/hatch", "run", "durable-functions-emulator", "--host", "0.0.0.0", "--port", "9014"] \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f3652d0..2fb83f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,6 +116,9 @@ exclude_lines = [ [tool.ruff] line-length = 88 target-version = "py313" +exclude = [ + "emulator" +] [tool.ruff.lint] preview = false From 7d4e0f1948220942f4080df3ba0c5d304101a35a Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 16:02:38 -0800 Subject: [PATCH 19/52] chore: whereis hatch --- emulator/DockerFile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index fcb4915..34ef3b8 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -11,7 +11,7 @@ COPY . . RUN hatch run pip install -e . # where is the hatch executable installed -RUN where hatch +RUN whereis hatch # enable hatch executable RUN chmod +x /usr/local/bin/hatch From 8cf681431afc82280f5996c319382092deffd73c Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 16:48:58 -0800 Subject: [PATCH 20/52] chore: use python3.13-slim --- emulator/DockerFile | 3 ++- emulator/README.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index 34ef3b8..66c7d81 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -1,4 +1,4 @@ -FROM public.ecr.aws/docker/library/python:3.13 +FROM python:3.11-slim WORKDIR /emulator # Install Hatch @@ -8,6 +8,7 @@ RUN pip install --no-cache-dir hatch COPY . . # install emulator +RUN hatch env create RUN hatch run pip install -e . # where is the hatch executable installed diff --git a/emulator/README.md b/emulator/README.md index 9ce26aa..de85ae4 100644 --- a/emulator/README.md +++ b/emulator/README.md @@ -58,7 +58,7 @@ durable-functions-emulator --host 0.0.0.0 --port 8080 The emulator provides a health check endpoint: ```bash -curl http://localhost:5000/ping +curl http://localhost:5000/health ``` ## Development From 942d191655530a97208fff6e74e292cc4e074d18 Mon Sep 17 00:00:00 2001 From: hsilan Date: Tue, 24 Feb 2026 16:49:48 -0800 Subject: [PATCH 21/52] chore: remove chmod +x --- emulator/DockerFile | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index 66c7d81..d7b2d4b 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -1,4 +1,4 @@ -FROM python:3.11-slim +FROM python:3.13-slim WORKDIR /emulator # Install Hatch @@ -8,13 +8,6 @@ RUN pip install --no-cache-dir hatch COPY . . # install emulator -RUN hatch env create RUN hatch run pip install -e . -# where is the hatch executable installed -RUN whereis hatch - -# enable hatch executable -RUN chmod +x /usr/local/bin/hatch - -CMD ["/usr/local/bin/hatch", "run", "durable-functions-emulator", "--host", "0.0.0.0", "--port", "9014"] \ No newline at end of file +CMD ["hatch", "run", "durable-functions-emulator", "--host", "0.0.0.0", "--port", "9014"] \ No newline at end of file From 73dafa5ccf73bf04cda3f809a8ee8b926e3a9f47 Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 11:46:45 -0800 Subject: [PATCH 22/52] chore: try directly using pip install and not hatch --- emulator/DockerFile | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index d7b2d4b..e78c14c 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -1,13 +1,16 @@ FROM python:3.13-slim WORKDIR /emulator -# Install Hatch -RUN pip install --no-cache-dir hatch # Copy project files COPY . . +# check that correct files are copied over +RUN ls + # install emulator -RUN hatch run pip install -e . +RUN pip install --no-cache-dir -e . + +ENTRYPOINT ["durable-functions-emulator"] -CMD ["hatch", "run", "durable-functions-emulator", "--host", "0.0.0.0", "--port", "9014"] \ No newline at end of file +CMD ["--host", "0.0.0.0", "--port", "9014"] \ No newline at end of file From 18e935611d8c8fab3e95a03f53d33a0c4653eae8 Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 13:38:22 -0800 Subject: [PATCH 23/52] chore: try older version of sdk and testing sdk --- emulator/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emulator/pyproject.toml b/emulator/pyproject.toml index bc7893e..1d22a3d 100644 --- a/emulator/pyproject.toml +++ b/emulator/pyproject.toml @@ -22,8 +22,8 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ - "aws-durable-execution-sdk-python-testing>=1.1.1", - "aws_durable_execution_sdk_python>=1.3.0", + "aws-durable-execution-sdk-python-testing~=1.1.0", + "aws_durable_execution_sdk_python~=1.1.1", "requests>=2.31.0,<3.0.0", "boto3>=1.34.0,<2.0.0", ] From ce09c726e26a24ab9db9c768dba1be84963d1061 Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 15:28:32 -0800 Subject: [PATCH 24/52] chore: try using github dependencies --- .gitignore | 1 + emulator/pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8194eaa..5fb723c 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ examples/build/* examples/*.zip durable-executions.db* +.coverage diff --git a/emulator/pyproject.toml b/emulator/pyproject.toml index 1d22a3d..a913dc7 100644 --- a/emulator/pyproject.toml +++ b/emulator/pyproject.toml @@ -22,8 +22,8 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ - "aws-durable-execution-sdk-python-testing~=1.1.0", - "aws_durable_execution_sdk_python~=1.1.1", + "aws-durable-execution-sdk-python-testing @ git+https://github.com/aws/aws-durable-execution-sdk-python-testing.git", + "aws_durable_execution_sdk_python @ git+https://github.com/aws/aws-durable-execution-sdk-python.git", "requests>=2.31.0,<3.0.0", "boto3>=1.34.0,<2.0.0", ] From 2e6f3ffd32054d1087d1865723069d41e78921d9 Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 15:32:19 -0800 Subject: [PATCH 25/52] chore: try installing git into image --- emulator/DockerFile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/emulator/DockerFile b/emulator/DockerFile index e78c14c..f299c82 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -8,6 +8,10 @@ COPY . . # check that correct files are copied over RUN ls +# Update the package lists and install Git +# The commands are combined to reduce the final image size +RUN apt-get update && apt-get install -y --no-install-recommends git && apt-get purge -y --auto-remove && rm -rf /var/lib/apt/lists/* + # install emulator RUN pip install --no-cache-dir -e . From 30fdc786364b7e374499d8c280e036cc7d74316e Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 15:39:35 -0800 Subject: [PATCH 26/52] chore: try apt-get upgrade --- emulator/DockerFile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index f299c82..0e3e15e 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -10,7 +10,7 @@ RUN ls # Update the package lists and install Git # The commands are combined to reduce the final image size -RUN apt-get update && apt-get install -y --no-install-recommends git && apt-get purge -y --auto-remove && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get upgrade && apt-get install -y --no-install-recommends git && apt-get purge -y --auto-remove && rm -rf /var/lib/apt/lists/* # install emulator RUN pip install --no-cache-dir -e . From 1855f380e34529f0926fde6173ee80a8144f9818 Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 15:52:34 -0800 Subject: [PATCH 27/52] chore: remove on push trigger for the ecr-release.yml workflow --- .github/workflows/ecr-release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index e36b0b4..c1ccad2 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -1,7 +1,5 @@ name: ecr-release.yml on: - push: - branches: [ "feat/publish-emulator-image" ] release: types: [published] From 72096f208bfdf1104d839a0abe5ad753c70b9bbf Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 15:55:16 -0800 Subject: [PATCH 28/52] chore: depend on specific sdk and testing sdk version for clarity --- emulator/DockerFile | 3 ++- emulator/pyproject.toml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/emulator/DockerFile b/emulator/DockerFile index 0e3e15e..87146ad 100644 --- a/emulator/DockerFile +++ b/emulator/DockerFile @@ -10,7 +10,8 @@ RUN ls # Update the package lists and install Git # The commands are combined to reduce the final image size -RUN apt-get update && apt-get upgrade && apt-get install -y --no-install-recommends git && apt-get purge -y --auto-remove && rm -rf /var/lib/apt/lists/* +# uncomment if we need to depend directly on main branch +# RUN apt-get update && apt-get upgrade && apt-get install -y --no-install-recommends git && apt-get purge -y --auto-remove && rm -rf /var/lib/apt/lists/* # install emulator RUN pip install --no-cache-dir -e . diff --git a/emulator/pyproject.toml b/emulator/pyproject.toml index a913dc7..acb8d50 100644 --- a/emulator/pyproject.toml +++ b/emulator/pyproject.toml @@ -22,8 +22,8 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ - "aws-durable-execution-sdk-python-testing @ git+https://github.com/aws/aws-durable-execution-sdk-python-testing.git", - "aws_durable_execution_sdk_python @ git+https://github.com/aws/aws-durable-execution-sdk-python.git", + "aws-durable-execution-sdk-python-testing~=1.1.1", + "aws_durable_execution_sdk_python~=1.3.0", "requests>=2.31.0,<3.0.0", "boto3>=1.34.0,<2.0.0", ] From 4476f4ec4168d098cf1428f696bbb23748e98f49 Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 15:58:26 -0800 Subject: [PATCH 29/52] chore: update README with installation instructions --- emulator/README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/emulator/README.md b/emulator/README.md index de85ae4..40c8048 100644 --- a/emulator/README.md +++ b/emulator/README.md @@ -21,7 +21,7 @@ The AWS Lambda Durable Execution Emulator provides a local development environme ```bash git clone https://github.com/aws/aws-lambda-durable-functions-emulator.git cd aws-lambda-durable-functions-emulator -hatch run pip install -e . +pip install --no-cache-dir -e . ``` ## Usage @@ -30,10 +30,8 @@ hatch run pip install -e . ```bash # Using the installed command -durable-functions-emulator -# Or using hatch for development -hatch run dev +durable-functions-emulator # With custom host and port durable-functions-emulator --host 0.0.0.0 --port 8080 @@ -99,7 +97,7 @@ hatch build ### Health Check -- **GET** `/ping` - Returns emulator status +- **GET** `/health` - Returns emulator status ### Durable Execution APIs From 781d053f60662d30a11fe7883d7b922a8608b329 Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 16:02:23 -0800 Subject: [PATCH 30/52] chore: try lower version of hatch --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 041d76c..91136b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install Hatch run: | - python -m pip install hatch==1.15.0 + python -m pip install hatch==1.14.1 - uses: webfactory/ssh-agent@v0.9.1 with: ssh-private-key: ${{ secrets.SDK_KEY }} From 11718e80c05701f36c35df42d64134d56ba184f4 Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 16:04:55 -0800 Subject: [PATCH 31/52] chore: update hatch version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91136b2..6601a61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install Hatch run: | - python -m pip install hatch==1.14.1 + python -m pip install hatch==1.16.4 - uses: webfactory/ssh-agent@v0.9.1 with: ssh-private-key: ${{ secrets.SDK_KEY }} From 172568f83eb2848d82ba38df23f0be7100f0fb75 Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 16:07:54 -0800 Subject: [PATCH 32/52] chore: try upgrading virtual env --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6601a61..a1dab9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,8 @@ jobs: - name: Install Hatch run: | python -m pip install hatch==1.16.4 + - name: Upgrade virtual env + run: pip install --upgrade virtualenv - uses: webfactory/ssh-agent@v0.9.1 with: ssh-private-key: ${{ secrets.SDK_KEY }} From e64541c146f2641fc06ad069ad3a14f60d98a158 Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 16:10:52 -0800 Subject: [PATCH 33/52] chore: remove emulator pr workflow --- .github/workflows/create-emulator-pr.yml | 189 ---------------------- .github/workflows/emulator-pr-template.md | 11 -- 2 files changed, 200 deletions(-) delete mode 100644 .github/workflows/create-emulator-pr.yml delete mode 100644 .github/workflows/emulator-pr-template.md diff --git a/.github/workflows/create-emulator-pr.yml b/.github/workflows/create-emulator-pr.yml deleted file mode 100644 index ff53a88..0000000 --- a/.github/workflows/create-emulator-pr.yml +++ /dev/null @@ -1,189 +0,0 @@ -name: Create Emulator PR - -on: - pull_request: - branches: [ main ] - types: [opened, synchronize, closed] - -permissions: - contents: read - pull-requests: write - issues: write - -jobs: - cleanup-emulator-pr: - if: github.event.action == 'closed' - runs-on: ubuntu-latest - steps: - - uses: webfactory/ssh-agent@v0.9.1 - with: - ssh-private-key: ${{ secrets.EMULATOR_KEY }} - - - name: Delete emulator branch - run: | - PR_NUMBER="${{ github.event.pull_request.number }}" - EMULATOR_BRANCH="testing-sdk-pr-${PR_NUMBER}-sync" - - git clone git@github.com:aws/aws-durable-execution-emulator.git - cd aws-durable-execution-emulator - git push origin --delete "$EMULATOR_BRANCH" || echo "Branch may not exist" - - create-emulator-pr: - if: github.event.action == 'opened' || github.event.action == 'synchronize' - runs-on: ubuntu-latest - steps: - - name: Checkout testing SDK repo - uses: actions/checkout@v5 - with: - path: testing-sdk - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.13" - - - name: Install uv - uses: astral-sh/setup-uv@v4 - - - uses: webfactory/ssh-agent@v0.9.1 - with: - ssh-private-key: | - ${{ secrets.EMULATOR_PRIVATE_KEY }} - ${{ secrets.SDK_KEY }} - - - name: Checkout emulator repo - run: | - git clone git@github.com:aws/aws-durable-execution-emulator.git emulator - - - name: Create branch and update uv.lock - working-directory: emulator - run: | - # Configure git - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - # Get PR info - BRANCH_NAME="${{ github.event.pull_request.head.ref }}" - PR_NUMBER="${{ github.event.pull_request.number }}" - EMULATOR_BRANCH="testing-sdk-pr-${PR_NUMBER}-sync" - - # Create or update branch - git fetch origin - if git show-ref --verify --quiet refs/remotes/origin/"$EMULATOR_BRANCH"; then - git checkout "$EMULATOR_BRANCH" - git reset --hard origin/main - else - git checkout -b "$EMULATOR_BRANCH" - fi - - # Update pyproject.toml to use local testing SDK (temporary, not committed) - TESTING_SDK_PATH="$(realpath ../testing-sdk)" - sed -i.bak "s|aws-durable-execution-sdk-python-testing @ git+ssh://git@github.com/aws/aws-durable-execution-sdk-python-testing.git|aws-durable-execution-sdk-python-testing @ file://${TESTING_SDK_PATH}|" pyproject.toml - rm pyproject.toml.bak - - # Generate new uv.lock with the specific testing SDK commit - uv lock - - # Show what changed - echo "=== Changes to be committed ===" - git diff --name-status - git diff uv.lock || echo "uv.lock is a new file" - - # Restore original pyproject.toml (don't commit the temporary change) - git checkout pyproject.toml - - # Commit and push only the uv.lock file - git add uv.lock - if git commit -m "Lock testing SDK branch: $BRANCH_NAME (PR #$PR_NUMBER)"; then - echo "Changes committed successfully" - git push --force-with-lease origin "$EMULATOR_BRANCH" - echo "Branch pushed successfully" - else - echo "No changes to commit" - # Still need to push the branch even if no changes - git push --force-with-lease origin "$EMULATOR_BRANCH" || git push origin "$EMULATOR_BRANCH" - fi - - - name: Create or update PR in emulator repo - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.EMULATOR_REPO_TOKEN }} - script: | - const fs = require('fs'); - const pr = context.payload.pull_request; - const branch_name = pr.head.ref; - const emulator_branch = `testing-sdk-pr-${pr.number}-sync`; - - // Wait a moment for branch to be available - await new Promise(resolve => setTimeout(resolve, 2000)); - - // Read and populate PR template - const template = fs.readFileSync('testing-sdk/.github/workflows/emulator-pr-template.md', 'utf8'); - const pr_body = template - .replace(/{{PR_NUMBER}}/g, pr.number) - .replace(/{{BRANCH_NAME}}/g, branch_name); - - try { - // Check if PR already exists - let existingPR = null; - try { - const prs = await github.rest.pulls.list({ - owner: 'aws', - repo: 'aws-durable-execution-emulator', - head: `aws:${emulator_branch}`, - state: 'open' - }); - existingPR = prs.data[0]; - } catch (e) { - console.log('No existing PR found'); - } - - if (existingPR) { - // Update existing PR - await github.rest.pulls.update({ - owner: 'aws', - repo: 'aws-durable-execution-emulator', - pull_number: existingPR.number, - title: `Lock testing SDK branch: ${branch_name} (PR #${pr.number})`, - body: pr_body - }); - - console.log(`Updated emulator PR: ${existingPR.html_url}`); - - // Comment on original PR about update - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: `🔄 **Emulator PR Updated**\n\nThe emulator PR has been updated with locked dependencies:\n\n➡️ ${existingPR.html_url}` - }); - } else { - // Create new PR - console.log("Creating an emulator PR") - const response = await github.rest.pulls.create({ - owner: 'aws', - repo: 'aws-durable-execution-emulator', - title: `Lock testing SDK branch: ${branch_name} (PR #${pr.number})`, - head: emulator_branch, - base: 'main', - body: pr_body, - draft: true - }); - - console.log(`Created emulator PR: ${response.data.html_url}`); - - // Comment on original PR - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: `🤖 **Emulator PR Created**\n\nA draft PR has been created with locked dependencies:\n\n➡️ ${response.data.html_url}\n\nThe emulator will build binaries using the exact testing SDK commit locked in uv.lock.` - }); - } - - } catch (error) { - console.log(`Error managing PR: ${error.message}`); - console.log(`Error status: ${error.status}`); - console.log(`Error response: ${JSON.stringify(error.response?.data)}`); - core.setFailed(`Failed to manage emulator PR: ${error.message}`); - } diff --git a/.github/workflows/emulator-pr-template.md b/.github/workflows/emulator-pr-template.md deleted file mode 100644 index 96fd09f..0000000 --- a/.github/workflows/emulator-pr-template.md +++ /dev/null @@ -1,11 +0,0 @@ -*Issue #, if available:* Related to aws/aws-durable-execution-sdk-python-testing#{{PR_NUMBER}} - -*Description of changes:* Testing changes from testing SDK branch `{{BRANCH_NAME}}` using locked dependencies in uv.lock - -## Dependencies -This PR locks the testing SDK to a specific commit from branch `{{BRANCH_NAME}}` using uv.lock for reproducible builds. - -PYTHON_LANGUAGE_SDK_BRANCH: main -PYTHON_TESTING_SDK_BRANCH: {{BRANCH_NAME}} - -By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. From 63ce50cecc4fe9a3ca6b149b070064be6a7badeb Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 16:13:54 -0800 Subject: [PATCH 34/52] chore: revert hatch upgrade --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1dab9f..041d76c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,9 +28,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install Hatch run: | - python -m pip install hatch==1.16.4 - - name: Upgrade virtual env - run: pip install --upgrade virtualenv + python -m pip install hatch==1.15.0 - uses: webfactory/ssh-agent@v0.9.1 with: ssh-private-key: ${{ secrets.SDK_KEY }} From 43ad4ca825cd3831ab9eda7471912bf8ef3e40c6 Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 16:17:38 -0800 Subject: [PATCH 35/52] chore: remove exclude on emulator folder --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2fb83f7..f3652d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,9 +116,6 @@ exclude_lines = [ [tool.ruff] line-length = 88 target-version = "py313" -exclude = [ - "emulator" -] [tool.ruff.lint] preview = false From 1a2ad021b0663b2c0dd605d236adc62742f58ea7 Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 16:21:42 -0800 Subject: [PATCH 36/52] chore: downgrade virtualenv version --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 041d76c..88448bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,9 @@ jobs: - name: Install Hatch run: | python -m pip install hatch==1.15.0 + - name: Install Hatch + run: | + python -m pip install virtualenv==20.39.0 - uses: webfactory/ssh-agent@v0.9.1 with: ssh-private-key: ${{ secrets.SDK_KEY }} From 91c0e16449a62918d95d3f95a1acda56116a136f Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 16:23:46 -0800 Subject: [PATCH 37/52] chore: reformatted 3 files --- .../config.py | 24 ++++++++++--- .../factory.py | 8 +++-- .../server.py | 36 ++++++++++++++----- 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/emulator/src/aws_lambda_durable_functions_emulator/config.py b/emulator/src/aws_lambda_durable_functions_emulator/config.py index 77bf177..49b6232 100644 --- a/emulator/src/aws_lambda_durable_functions_emulator/config.py +++ b/emulator/src/aws_lambda_durable_functions_emulator/config.py @@ -59,13 +59,17 @@ def __post_init__(self): def to_web_service_config(self): """Convert to testing library web service config.""" - return WebServiceConfig(host=self.host, port=self.port, log_level=self.log_level) + return WebServiceConfig( + host=self.host, port=self.port, log_level=self.log_level + ) def _validate_config(self): """Validate all configuration parameters.""" # Validate Lambda endpoint URL - def _raise_invalid_endpoint(endpoint: str, cause: Exception | None = None) -> None: + def _raise_invalid_endpoint( + endpoint: str, cause: Exception | None = None + ) -> None: msg = f"Invalid Lambda endpoint URL: {endpoint}" raise ValueError(msg) from cause @@ -79,11 +83,15 @@ def _raise_invalid_endpoint(endpoint: str, cause: Exception | None = None) -> No # Validate storage directory if specified if self.storage_dir: - def _raise_storage_error(storage_dir: str, cause: Exception | None = None) -> None: + def _raise_storage_error( + storage_dir: str, cause: Exception | None = None + ) -> None: msg = f"Storage directory is not writable: {storage_dir}" raise ValueError(msg) from cause - def _raise_access_error(storage_dir: str, cause: Exception | None = None) -> None: + def _raise_access_error( + storage_dir: str, cause: Exception | None = None + ) -> None: msg = f"Cannot access storage directory: {storage_dir}" raise ValueError(msg) from cause @@ -107,7 +115,13 @@ def _raise_access_error(storage_dir: str, cause: Exception | None = None) -> Non _raise_access_error(self.storage_dir, e) # Validate log level - valid_log_levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL] + valid_log_levels = [ + logging.DEBUG, + logging.INFO, + logging.WARNING, + logging.ERROR, + logging.CRITICAL, + ] if self.log_level not in valid_log_levels: msg = f"Invalid log level: {self.log_level}. Must be one of {valid_log_levels}" raise ValueError(msg) diff --git a/emulator/src/aws_lambda_durable_functions_emulator/factory.py b/emulator/src/aws_lambda_durable_functions_emulator/factory.py index e96d1d7..1d29912 100644 --- a/emulator/src/aws_lambda_durable_functions_emulator/factory.py +++ b/emulator/src/aws_lambda_durable_functions_emulator/factory.py @@ -7,7 +7,9 @@ import aws_durable_execution_sdk_python import botocore.loaders -from aws_durable_execution_sdk_python_testing.checkpoint.processor import CheckpointProcessor +from aws_durable_execution_sdk_python_testing.checkpoint.processor import ( + CheckpointProcessor, +) from aws_durable_execution_sdk_python_testing.executor import Executor from aws_durable_execution_sdk_python_testing.invoker import LambdaInvoker from aws_durable_execution_sdk_python_testing.scheduler import Scheduler @@ -85,7 +87,9 @@ def create_executor(config: "EmulatorConfig"): store = TestingLibraryComponentFactory.create_store(config) scheduler = TestingLibraryComponentFactory.create_scheduler() invoker = TestingLibraryComponentFactory.create_invoker(config) - checkpoint_processor = TestingLibraryComponentFactory.create_checkpoint_processor(store, scheduler) + checkpoint_processor = ( + TestingLibraryComponentFactory.create_checkpoint_processor(store, scheduler) + ) executor = Executor(store, scheduler, invoker, checkpoint_processor) checkpoint_processor.add_execution_observer(executor) diff --git a/emulator/src/aws_lambda_durable_functions_emulator/server.py b/emulator/src/aws_lambda_durable_functions_emulator/server.py index 4ffd4ec..0c8df4f 100644 --- a/emulator/src/aws_lambda_durable_functions_emulator/server.py +++ b/emulator/src/aws_lambda_durable_functions_emulator/server.py @@ -22,7 +22,9 @@ # Configure logging log_level = get_log_level() -logging.basicConfig(level=log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") +logging.basicConfig( + level=log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) logger = logging.getLogger("durable_functions_emulator") logger.setLevel(log_level) @@ -66,7 +68,9 @@ def start(self): try: logger.info("Starting emulator server...") with self.web_server: - logger.info("Server listening on %s:%s", self.config.host, self.config.port) + logger.info( + "Server listening on %s:%s", self.config.host, self.config.port + ) self.web_server.serve_forever() except KeyboardInterrupt: logger.info("Server shutdown requested by user") @@ -79,17 +83,31 @@ def start(self): def main(): """Main entry point for the emulator server.""" - parser = argparse.ArgumentParser(description="AWS Lambda Durable Functions Emulator (powered by testing library)") - parser.add_argument("--host", type=str, help="Host to bind to (default: from HOST env var or 0.0.0.0)") - parser.add_argument("--port", type=int, help="Port to bind to (default: from PORT env var or 5000)") + parser = argparse.ArgumentParser( + description="AWS Lambda Durable Functions Emulator (powered by testing library)" + ) + parser.add_argument( + "--host", + type=str, + help="Host to bind to (default: from HOST env var or 0.0.0.0)", + ) + parser.add_argument( + "--port", type=int, help="Port to bind to (default: from PORT env var or 5000)" + ) args = parser.parse_args() try: # Create emulator configuration - config = EmulatorConfig(host=args.host or get_host(), port=args.port or get_port()) + config = EmulatorConfig( + host=args.host or get_host(), port=args.port or get_port() + ) # Create and start emulator server - logger.info("Starting AWS Lambda Durable Functions Emulator on %s:%s", config.host, config.port) + logger.info( + "Starting AWS Lambda Durable Functions Emulator on %s:%s", + config.host, + config.port, + ) server = EmulatorServer(config) server.start() @@ -105,7 +123,9 @@ def main(): except ImportError: logger.exception("Missing dependency") - logger.info("Please install the aws-durable-execution-sdk-python-testing package:") + logger.info( + "Please install the aws-durable-execution-sdk-python-testing package:" + ) logger.info(" pip install aws-durable-execution-sdk-python-testing") sys.exit(1) From 26e4cc9101ce54859079d37988c5c0acdc380f0a Mon Sep 17 00:00:00 2001 From: hsilan Date: Wed, 25 Feb 2026 16:24:40 -0800 Subject: [PATCH 38/52] chore: update ci.yml step name for virtual env install --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88448bb..f347299 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - name: Install Hatch run: | python -m pip install hatch==1.15.0 - - name: Install Hatch + - name: Install specific version of Virtual Env due to bug with hatch run: | python -m pip install virtualenv==20.39.0 - uses: webfactory/ssh-agent@v0.9.1 From c1e1e8221e9ae0c7cbd8d1decae42cbd9e177ffd Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 11:39:37 -0800 Subject: [PATCH 39/52] chore: try directly using dex-local-runner start-server command --- .github/workflows/ecr-release.yml | 23 +-- DockerFile | 23 +++ emulator/DockerFile | 21 --- emulator/README.md | 125 --------------- emulator/pyproject.toml | 125 --------------- .../__about__.py | 4 - .../__init__.py | 1 - .../__main__.py | 6 - .../config.py | 138 ----------------- .../factory.py | 97 ------------ .../server.py | 145 ------------------ emulator/tests/__init__.py | 1 - emulator/tests/test_config.py | 73 --------- emulator/tests/test_factory.py | 82 ---------- emulator/tests/test_server.py | 35 ----- 15 files changed, 36 insertions(+), 863 deletions(-) create mode 100644 DockerFile delete mode 100644 emulator/DockerFile delete mode 100644 emulator/README.md delete mode 100644 emulator/pyproject.toml delete mode 100644 emulator/src/aws_lambda_durable_functions_emulator/__about__.py delete mode 100644 emulator/src/aws_lambda_durable_functions_emulator/__init__.py delete mode 100644 emulator/src/aws_lambda_durable_functions_emulator/__main__.py delete mode 100644 emulator/src/aws_lambda_durable_functions_emulator/config.py delete mode 100644 emulator/src/aws_lambda_durable_functions_emulator/factory.py delete mode 100644 emulator/src/aws_lambda_durable_functions_emulator/server.py delete mode 100644 emulator/tests/__init__.py delete mode 100644 emulator/tests/test_config.py delete mode 100644 emulator/tests/test_factory.py delete mode 100644 emulator/tests/test_server.py diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index c1ccad2..01da57d 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -2,14 +2,16 @@ name: ecr-release.yml on: release: types: [published] + push: + branches: [feat/publish-emulator-image] permissions: contents: read id-token: write # This is required for requesting the JWT env: - path_to_dockerfile: "emulator/DockerFile" - docker_build_dir: "emulator/" + path_to_dockerfile: "/DockerFile" + docker_build_dir: "/" aws_region: "us-east-1" ecr_repository_name: "o4w4w0v6/aws-durable-execution-emulator" @@ -26,12 +28,11 @@ jobs: - arch: x86_64 - arch: arm64 steps: - - name: Grab version from generate-version job + - name: Get version from __about__.py id: version - env: - VERSION: $${{ github.event.release.name }} run: | - echo "$VERSION" + VERSION=$(python -c "from src.aws_durable_execution_sdk_python_testing.__about__ import __version__; print(__version__)") + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 @@ -47,6 +48,8 @@ jobs: uses: docker/setup-qemu-action@v3 with: platforms: arm64 + - name: Build distribution + run: hatch build - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: @@ -105,14 +108,14 @@ jobs: if: github.event.release.name != '' id: create-ecr-manifest-explicit run: | - docker manifest create "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ github.event.release.name }}" \ + docker manifest create "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ steps.version.outputs.VERSION }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" - name: Annotate ECR manifest with explicit arm64 tag if: github.event.release.name != '' id: annotate-ecr-manifest-explicit-arm64 run: | - docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ github.event.release.name }}" \ + docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ steps.version.outputs.VERSION }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" \ --arch arm64 \ --os linux @@ -120,7 +123,7 @@ jobs: if: github.event.release.name != '' id: annotate-ecr-manifest-explicit-amd64 run: | - docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ github.event.release.name }}" \ + docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ steps.version.outputs.VERSION }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ --arch amd64 \ --os linux @@ -128,7 +131,7 @@ jobs: if: github.event.release.name != '' id: push-ecr-manifest-explicit run: | - docker manifest push "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:$${{ github.event.release.name }}" + docker manifest push "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ steps.version.outputs.VERSION }}" - name: Create ECR manifest with latest tag id: create-ecr-manifest-latest run: | diff --git a/DockerFile b/DockerFile new file mode 100644 index 0000000..ea04654 --- /dev/null +++ b/DockerFile @@ -0,0 +1,23 @@ +FROM python:3.13-slim + +# Copy and install the wheel +COPY dist/*.whl /tmp/ +RUN pip install --no-cache-dir /tmp/*.whl && rm -rf /tmp/*.whl + +# AWS credentials (required for boto3) +ENV AWS_ACCESS_KEY_ID=foo \ + AWS_SECRET_ACCESS_KEY=bar \ + AWS_DEFAULT_REGION=us-east-1 + +EXPOSE 9014 + +HEALTHCHECK --interval=10s --timeout=3s --start-period=5s \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:9014/health').read()" || exit 1 + +CMD ["dex-local-runner", "start-server", \ + "--host", "0.0.0.0", \ + "--port", "9014", \ + "--log-level", "DEBUG", \ + "--lambda-endpoint", "http://host.docker.internal:3001", \ + "--store-type", "sqlite", \ + "--store-path", "/tmp/.durable-executions-local/durable-executions.db"] diff --git a/emulator/DockerFile b/emulator/DockerFile deleted file mode 100644 index 87146ad..0000000 --- a/emulator/DockerFile +++ /dev/null @@ -1,21 +0,0 @@ -FROM python:3.13-slim - -WORKDIR /emulator - -# Copy project files -COPY . . - -# check that correct files are copied over -RUN ls - -# Update the package lists and install Git -# The commands are combined to reduce the final image size -# uncomment if we need to depend directly on main branch -# RUN apt-get update && apt-get upgrade && apt-get install -y --no-install-recommends git && apt-get purge -y --auto-remove && rm -rf /var/lib/apt/lists/* - -# install emulator -RUN pip install --no-cache-dir -e . - -ENTRYPOINT ["durable-functions-emulator"] - -CMD ["--host", "0.0.0.0", "--port", "9014"] \ No newline at end of file diff --git a/emulator/README.md b/emulator/README.md deleted file mode 100644 index 40c8048..0000000 --- a/emulator/README.md +++ /dev/null @@ -1,125 +0,0 @@ -# AWS Durable Execution Emulator - -A local emulator for AWS Lambda durable functions that enables local development and testing of durable function applications. Powered by the AWS Durable Execution Testing SDK for Python. - -## Overview - -The AWS Lambda Durable Execution Emulator provides a local development environment for building and testing durable function applications before deploying to AWS Lambda. It uses the AWS Durable Execution Testing SDK for Python as its execution engine, providing robust durable execution capabilities with full AWS API compatibility. - -## Features - -- **Local Development**: Run durable functions locally without AWS infrastructure -- **API Compatibility**: Compatible with AWS Lambda Durable Functions APIs -- **Health Check Endpoint**: Built-in health monitoring -- **Logging**: Configurable logging levels for debugging -- **Testing Support**: Built-in test framework support - -## Installation - -### From source - -```bash -git clone https://github.com/aws/aws-lambda-durable-functions-emulator.git -cd aws-lambda-durable-functions-emulator -pip install --no-cache-dir -e . -``` - -## Usage - -### Starting the Emulator - -```bash -# Using the installed command - -durable-functions-emulator - -# With custom host and port -durable-functions-emulator --host 0.0.0.0 --port 8080 -``` - -### Environment Variables - -- `HOST`: Server host (default: 0.0.0.0) -- `PORT`: Server port (default: 5000) -- `LOG`: Logging level (default: INFO) -- `STORAGE_DIR`: Directory for persistent storage -- `EXECUTION_STORE_TYPE`: Type of execution store (default: sqlite) - - `filesystem`: File-based storage - - `sqlite`: SQLite database storage (default) -- `LAMBDA_ENDPOINT`: Lambda endpoint URL for testing -- `LOCAL_RUNNER_ENDPOINT`: Local runner endpoint URL -- `LOCAL_RUNNER_REGION`: AWS region for local runner -- `LOCAL_RUNNER_MODE`: Runner mode (default: local) - -### Health Check - -The emulator provides a health check endpoint: - -```bash -curl http://localhost:5000/health -``` - -## Development - -### Prerequisites - -- Python 3.13+ -- [Hatch](https://hatch.pypa.io/) for project management - -### Setup - -```bash -git clone https://github.com/aws/aws-lambda-durable-functions-emulator.git -cd aws-lambda-durable-functions-emulator -hatch run pip install -e . -``` - -### Running Tests - -```bash -# Run all tests -hatch run test - -# Run with coverage -hatch run test:cov - -# Type checking -hatch run types:check -``` - -### Building - -```bash -hatch build -``` - -## API Reference - -### Health Check - -- **GET** `/health` - Returns emulator status - -### Durable Execution APIs - -- **POST** `/2025-12-01/durable-execution-state//checkpoint` - Checkpoint execution state -- **GET** `/2025-12-01/durable-execution-state//getState` - Get execution state -- **GET** `/2025-12-01/durable-executions/` - Get execution details -- **GET** `/2025-12-01/durable-executions//history` - Get execution history - -### Callback APIs - -- **POST** `/2025-12-01/durable-execution-callbacks//succeed` - Send success callback -- **POST** `/2025-12-01/durable-execution-callbacks//fail` - Send failure callback -- **POST** `/2025-12-01/durable-execution-callbacks//heartbeat` - Send heartbeat callback - -## Contributing - -We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details. - -## License - -This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. - -## Security - -See [CONTRIBUTING.md](CONTRIBUTING.md#security-issue-notifications) for more information. diff --git a/emulator/pyproject.toml b/emulator/pyproject.toml deleted file mode 100644 index acb8d50..0000000 --- a/emulator/pyproject.toml +++ /dev/null @@ -1,125 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "aws-lambda-durable-functions-emulator" -dynamic = ["version"] -description = "Local emulator for AWS Lambda Durable Functions" -readme = "README.md" -requires-python = ">=3.13" -license = "Apache-2.0" -keywords = ["aws", "lambda", "durable", "functions", "emulator"] -authors = [ - { name = "AWS Lambda Team" }, -] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.13", -] -dependencies = [ - "aws-durable-execution-sdk-python-testing~=1.1.1", - "aws_durable_execution_sdk_python~=1.3.0", - "requests>=2.31.0,<3.0.0", - "boto3>=1.34.0,<2.0.0", -] - -[project.urls] -Documentation = "https://github.com/aws/aws-lambda-durable-functions-emulator#readme" -Issues = "https://github.com/aws/aws-lambda-durable-functions-emulator/issues" -Source = "https://github.com/aws/aws-lambda-durable-functions-emulator" - -[project.scripts] -durable-functions-emulator = "aws_lambda_durable_functions_emulator.server:main" - -[tool.hatch.build.targets.sdist] -packages = ["src/aws_lambda_durable_functions_emulator"] - -[tool.hatch.build.targets.wheel] -packages = ["src/aws_lambda_durable_functions_emulator"] - -[tool.hatch.metadata] -allow-direct-references = true - -[tool.hatch.version] -path = "src/aws_lambda_durable_functions_emulator/__about__.py" - -[tool.hatch.envs.default] -dependencies = [ - "pytest", -] -dev-mode = true - -[tool.hatch.envs.default.scripts] -test = "pytest {args:tests}" -dev = "python -m aws_lambda_durable_functions_emulator.server --host 127.0.0.1 --port 5000" -emulator = "python -m aws_lambda_durable_functions_emulator.server {args}" - -[tool.hatch.envs.test] -dependencies = [ - "coverage[toml]", - "pytest", - "pytest-cov", -] - -[tool.hatch.envs.test.scripts] -test = "pytest {args:tests}" -cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=src/aws_lambda_durable_functions_emulator --cov=tests --cov-fail-under=60" - -[tool.hatch.envs.types] -extra-dependencies = [ - "mypy>=1.0.0", - "pytest", - "types-boto3", - "boto3-stubs[essential]", -] -[tool.hatch.envs.types.scripts] -check = "mypy --install-types --non-interactive {args:src/aws_lambda_durable_functions_emulator tests}" - -[tool.hatch.envs.lint] -extra-dependencies = [ - "ruff>=0.1.0", -] -[tool.hatch.envs.lint.scripts] -check = "ruff check {args:src tests}" -format = "ruff format {args:src tests}" -fix = "ruff check --fix {args:src tests}" - -[tool.ruff] -target-version = "py313" -line-length = 120 - -[tool.ruff.lint.isort] -known-first-party = ["aws_lambda_durable_functions_emulator"] - -[tool.ruff.lint.flake8-tidy-imports] -ban-relative-imports = "all" - -[tool.ruff.lint.per-file-ignores] -# Tests can use magic values, assertions, and relative imports -"tests/**/*" = ["PLR2004", "S101", "TID252", "S104"] -# Allow binding to all interfaces for server configuration -"src/aws_lambda_durable_functions_emulator/config.py" = ["S104"] - -[tool.coverage.run] -source_pkgs = ["aws_lambda_durable_functions_emulator", "tests"] -branch = true -parallel = true -omit = [ - "src/aws_lambda_durable_functions_emulator/__about__.py", -] - -[tool.coverage.paths] -aws_lambda_durable_functions_emulator = ["src/aws_lambda_durable_functions_emulator", "*/aws-durable-execution-sdk-python-testing/emulator/src/aws_lambda_durable_functions_emulator"] -tests = ["tests", "*/aws-durable-execution-sdk-python-testing/emulator/tests"] - -[tool.coverage.report] -exclude_lines = [ - "no cov", - "if __name__ == .__main__.:", - "if TYPE_CHECKING:", -] \ No newline at end of file diff --git a/emulator/src/aws_lambda_durable_functions_emulator/__about__.py b/emulator/src/aws_lambda_durable_functions_emulator/__about__.py deleted file mode 100644 index 1c35361..0000000 --- a/emulator/src/aws_lambda_durable_functions_emulator/__about__.py +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-FileCopyrightText: 2025-present Amazon.com, Inc. or its affiliates. -# -# SPDX-License-Identifier: Apache-2.0 -__version__ = "0.1.0" diff --git a/emulator/src/aws_lambda_durable_functions_emulator/__init__.py b/emulator/src/aws_lambda_durable_functions_emulator/__init__.py deleted file mode 100644 index 27d6498..0000000 --- a/emulator/src/aws_lambda_durable_functions_emulator/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""AWS Lambda Durable Functions Emulator""" diff --git a/emulator/src/aws_lambda_durable_functions_emulator/__main__.py b/emulator/src/aws_lambda_durable_functions_emulator/__main__.py deleted file mode 100644 index 63ac5b1..0000000 --- a/emulator/src/aws_lambda_durable_functions_emulator/__main__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""CLI entry point for the durable functions emulator""" - -from aws_lambda_durable_functions_emulator.server import main - -if __name__ == "__main__": - main() diff --git a/emulator/src/aws_lambda_durable_functions_emulator/config.py b/emulator/src/aws_lambda_durable_functions_emulator/config.py deleted file mode 100644 index 49b6232..0000000 --- a/emulator/src/aws_lambda_durable_functions_emulator/config.py +++ /dev/null @@ -1,138 +0,0 @@ -import logging -import os -from dataclasses import dataclass, field -from pathlib import Path -from urllib.parse import urlparse - -from aws_durable_execution_sdk_python_testing.stores.base import StoreType -from aws_durable_execution_sdk_python_testing.web.server import WebServiceConfig - -# Constants -MAX_PORT = 65535 - - -def get_host() -> str: - """Get the server host from environment variable or default.""" - return os.environ.get("HOST", "0.0.0.0") - - -def get_port() -> int: - """Get the server port from environment variable or default.""" - return int(os.environ.get("PORT", "5000")) - - -def get_log_level() -> int: - """Get the logging level from environment variable or default.""" - log_level_str = os.environ.get("LOG", "INFO").upper() - return getattr(logging, log_level_str, logging.INFO) - - -def get_lambda_endpoint() -> str: - """Get the Lambda endpoint from environment variable.""" - return os.environ.get("LAMBDA_ENDPOINT", "http://localhost:3001") - - -def get_storage_dir() -> str | None: - """Get the storage directory from environment variable.""" - return os.environ.get("STORAGE_DIR") - - -def get_execution_store_type() -> str: - """Get the execution store type from environment variable.""" - return os.environ.get("EXECUTION_STORE_TYPE", StoreType.SQLITE.value).lower() - - -@dataclass -class EmulatorConfig: - """Configuration for the AWS Lambda Durable Functions Emulator.""" - - host: str = field(default_factory=get_host) - port: int = field(default_factory=get_port) - log_level: int = field(default_factory=get_log_level) - lambda_endpoint: str = field(default_factory=get_lambda_endpoint) - storage_dir: str | None = field(default_factory=get_storage_dir) - execution_store_type: str = field(default_factory=get_execution_store_type) - - def __post_init__(self): - """Validate configuration after initialization.""" - self._validate_config() - - def to_web_service_config(self): - """Convert to testing library web service config.""" - return WebServiceConfig( - host=self.host, port=self.port, log_level=self.log_level - ) - - def _validate_config(self): - """Validate all configuration parameters.""" - - # Validate Lambda endpoint URL - def _raise_invalid_endpoint( - endpoint: str, cause: Exception | None = None - ) -> None: - msg = f"Invalid Lambda endpoint URL: {endpoint}" - raise ValueError(msg) from cause - - try: - parsed = urlparse(self.lambda_endpoint) - if not parsed.scheme or not parsed.netloc: - _raise_invalid_endpoint(self.lambda_endpoint) - except (ValueError, TypeError) as e: - _raise_invalid_endpoint(self.lambda_endpoint, e) - - # Validate storage directory if specified - if self.storage_dir: - - def _raise_storage_error( - storage_dir: str, cause: Exception | None = None - ) -> None: - msg = f"Storage directory is not writable: {storage_dir}" - raise ValueError(msg) from cause - - def _raise_access_error( - storage_dir: str, cause: Exception | None = None - ) -> None: - msg = f"Cannot access storage directory: {storage_dir}" - raise ValueError(msg) from cause - - try: - storage_path = Path(self.storage_dir) - if storage_path.exists(): - if not storage_path.is_dir(): - msg = f"Storage path is not a directory: {self.storage_dir}" - raise ValueError(msg) - # Test write permissions - test_file = storage_path / ".write_test" - try: - test_file.write_text("test") - test_file.unlink() - except (OSError, PermissionError) as e: - _raise_storage_error(self.storage_dir, e) - else: - # Try to create the directory - storage_path.mkdir(parents=True, exist_ok=True) - except (OSError, PermissionError) as e: - _raise_access_error(self.storage_dir, e) - - # Validate log level - valid_log_levels = [ - logging.DEBUG, - logging.INFO, - logging.WARNING, - logging.ERROR, - logging.CRITICAL, - ] - if self.log_level not in valid_log_levels: - msg = f"Invalid log level: {self.log_level}. Must be one of {valid_log_levels}" - raise ValueError(msg) - - # Validate port range - if not (1 <= self.port <= MAX_PORT): - msg = f"Invalid port: {self.port}. Must be between 1 and {MAX_PORT}" - raise ValueError(msg) - - # Validate execution store type - valid_store_types = [StoreType.FILESYSTEM.value, StoreType.SQLITE.value] - if self.execution_store_type not in valid_store_types: - msg = f"Invalid execution store type: {self.execution_store_type}. Must be one of {valid_store_types}" - raise ValueError(msg) diff --git a/emulator/src/aws_lambda_durable_functions_emulator/factory.py b/emulator/src/aws_lambda_durable_functions_emulator/factory.py deleted file mode 100644 index 1d29912..0000000 --- a/emulator/src/aws_lambda_durable_functions_emulator/factory.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Factory for creating testing library components with emulator configuration.""" - -import logging -import os -from pathlib import Path -from typing import TYPE_CHECKING - -import aws_durable_execution_sdk_python -import botocore.loaders -from aws_durable_execution_sdk_python_testing.checkpoint.processor import ( - CheckpointProcessor, -) -from aws_durable_execution_sdk_python_testing.executor import Executor -from aws_durable_execution_sdk_python_testing.invoker import LambdaInvoker -from aws_durable_execution_sdk_python_testing.scheduler import Scheduler -from aws_durable_execution_sdk_python_testing.stores.base import ( - StoreType, -) -from aws_durable_execution_sdk_python_testing.stores.filesystem import ( - FileSystemExecutionStore, -) -from aws_durable_execution_sdk_python_testing.stores.sqlite import ( - SQLiteExecutionStore, -) - -if TYPE_CHECKING: - from aws_lambda_durable_functions_emulator.config import EmulatorConfig - -logger = logging.getLogger(__name__) - - -class TestingLibraryComponentFactory: - """Factory for creating testing library components.""" - - @staticmethod - def create_store(config: "EmulatorConfig"): - """Create execution store based on emulator configuration.""" - store_type = config.execution_store_type - - if store_type == StoreType.SQLITE.value: - logger.info("Creating SQLite execution store") - if config.storage_dir: - db_path = Path(config.storage_dir) / "durable-executions.db" - else: - db_path = Path("durable-executions.db") - return SQLiteExecutionStore.create_and_initialize(db_path) - - logger.info("Creating file-system execution store") - return FileSystemExecutionStore.create(config.storage_dir or ".") - - @staticmethod - def create_scheduler(): - """Create scheduler for timer and event management.""" - logger.info("Creating scheduler") - scheduler = Scheduler() - logger.info("Starting scheduler") - scheduler.start() - return scheduler - - @staticmethod - def create_invoker(config: "EmulatorConfig"): - """Create Lambda invoker with emulator configuration.""" - logger.info("Creating Lambda invoker with endpoint: %s", config.lambda_endpoint) - - # Load lambdainternal service model - package_path = os.path.dirname(aws_durable_execution_sdk_python.__file__) - data_path = f"{package_path}/botocore/data" - os.environ["AWS_DATA_PATH"] = data_path - - loader = botocore.loaders.Loader() - loader.search_paths.append(data_path) - - return LambdaInvoker.create(config.lambda_endpoint, "us-east-1") - - @staticmethod - def create_checkpoint_processor(store, scheduler): - logger.info("Creating checkpoint processor") - checkpoint_processor = CheckpointProcessor(store, scheduler) - logger.info("Created checkpoint processor") - return checkpoint_processor - - @staticmethod - def create_executor(config: "EmulatorConfig"): - """Create complete executor with all components.""" - logger.info("Creating executor with all components") - - store = TestingLibraryComponentFactory.create_store(config) - scheduler = TestingLibraryComponentFactory.create_scheduler() - invoker = TestingLibraryComponentFactory.create_invoker(config) - checkpoint_processor = ( - TestingLibraryComponentFactory.create_checkpoint_processor(store, scheduler) - ) - - executor = Executor(store, scheduler, invoker, checkpoint_processor) - checkpoint_processor.add_execution_observer(executor) - - return executor diff --git a/emulator/src/aws_lambda_durable_functions_emulator/server.py b/emulator/src/aws_lambda_durable_functions_emulator/server.py deleted file mode 100644 index 0c8df4f..0000000 --- a/emulator/src/aws_lambda_durable_functions_emulator/server.py +++ /dev/null @@ -1,145 +0,0 @@ -"""Main server for the AWS Lambda Durable Functions Emulator using testing library""" - -import argparse -import logging -import sys -from typing import TYPE_CHECKING - -from aws_durable_execution_sdk_python_testing.web.server import WebServer - -from aws_lambda_durable_functions_emulator.config import ( - EmulatorConfig, - get_host, - get_lambda_endpoint, - get_log_level, - get_port, - get_storage_dir, -) -from aws_lambda_durable_functions_emulator.factory import TestingLibraryComponentFactory - -if TYPE_CHECKING: - from aws_durable_execution_sdk_python_testing.executor import Executor - -# Configure logging -log_level = get_log_level() -logging.basicConfig( - level=log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger("durable_functions_emulator") -logger.setLevel(log_level) - -# Suppress third-party debug logging -logging.getLogger("botocore").setLevel(logging.WARNING) -logging.getLogger("boto3").setLevel(logging.WARNING) -logging.getLogger("urllib3").setLevel(logging.WARNING) - -logger.info("Logging level set to: %s", logging.getLevelName(log_level)) - - -class EmulatorServer: - """Emulator server that wraps the testing library's WebServer.""" - - def __init__(self, config: EmulatorConfig) -> None: - self.config = config - - logger.info("Configuration validation passed") - - # Create testing library components using emulator config - self.executor = self._create_executor(config) - - # Convert emulator config to testing library config - web_config = self._create_web_config(config) - - # Use testing library's WebServer directly - self.web_server: WebServer = WebServer(web_config, self.executor) - - logger.info("EmulatorServer initialized on %s:%s", config.host, config.port) - - def _create_executor(self, config: EmulatorConfig) -> "Executor": - """Create executor with emulator configuration using factory.""" - return TestingLibraryComponentFactory.create_executor(config) - - def _create_web_config(self, config: EmulatorConfig): - """Convert emulator config to testing library config.""" - return config.to_web_service_config() - - def start(self): - """Start the emulator server.""" - try: - logger.info("Starting emulator server...") - with self.web_server: - logger.info( - "Server listening on %s:%s", self.config.host, self.config.port - ) - self.web_server.serve_forever() - except KeyboardInterrupt: - logger.info("Server shutdown requested by user") - except Exception: - logger.exception("Server error") - raise - finally: - logger.info("Server shutdown complete") - - -def main(): - """Main entry point for the emulator server.""" - parser = argparse.ArgumentParser( - description="AWS Lambda Durable Functions Emulator (powered by testing library)" - ) - parser.add_argument( - "--host", - type=str, - help="Host to bind to (default: from HOST env var or 0.0.0.0)", - ) - parser.add_argument( - "--port", type=int, help="Port to bind to (default: from PORT env var or 5000)" - ) - args = parser.parse_args() - - try: - # Create emulator configuration - config = EmulatorConfig( - host=args.host or get_host(), port=args.port or get_port() - ) - - # Create and start emulator server - logger.info( - "Starting AWS Lambda Durable Functions Emulator on %s:%s", - config.host, - config.port, - ) - server = EmulatorServer(config) - server.start() - - except ValueError: - logger.exception("Configuration error") - logger.info("Please check your configuration and try again.") - logger.info("Environment variables:") - logger.info(" HOST=%s", config.host if "config" in locals() else get_host()) - logger.info(" PORT=%s", config.port if "config" in locals() else get_port()) - logger.info(" LAMBDA_ENDPOINT=%s", get_lambda_endpoint()) - logger.info(" STORAGE_DIR=%s", get_storage_dir()) - sys.exit(1) - - except ImportError: - logger.exception("Missing dependency") - logger.info( - "Please install the aws-durable-execution-sdk-python-testing package:" - ) - logger.info(" pip install aws-durable-execution-sdk-python-testing") - sys.exit(1) - - except OSError: - logger.exception("Network error") - logger.info("Failed to bind to %s:%s", config.host, config.port) - logger.info("Please check that the port is not already in use and try again.") - sys.exit(1) - - except Exception: - logger.exception("Unexpected error during startup") - logger.exception("Full error details:") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/emulator/tests/__init__.py b/emulator/tests/__init__.py deleted file mode 100644 index fb3c937..0000000 --- a/emulator/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the durable functions emulator""" diff --git a/emulator/tests/test_config.py b/emulator/tests/test_config.py deleted file mode 100644 index cd4648d..0000000 --- a/emulator/tests/test_config.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Tests for the emulator configuration""" - -import os - -import pytest -from aws_durable_execution_sdk_python_testing.stores.base import StoreType - -from aws_lambda_durable_functions_emulator.config import EmulatorConfig - - -def test_execution_store_type_default(): - """Test that default execution store type is sqlite""" - # Clean up any existing env vars - if "EXECUTION_STORE_TYPE" in os.environ: - del os.environ["EXECUTION_STORE_TYPE"] - - config = EmulatorConfig() - assert config.execution_store_type == StoreType.SQLITE.value - - -def test_execution_store_type_filesystem(): - """Test filesystem execution store type""" - os.environ["EXECUTION_STORE_TYPE"] = StoreType.FILESYSTEM.value - - config = EmulatorConfig() - assert config.execution_store_type == StoreType.FILESYSTEM.value - - # Clean up - del os.environ["EXECUTION_STORE_TYPE"] - - -def test_execution_store_type_sqlite(): - """Test SQLite execution store type""" - os.environ["EXECUTION_STORE_TYPE"] = StoreType.SQLITE.value - - config = EmulatorConfig() - assert config.execution_store_type == StoreType.SQLITE.value - - # Clean up - del os.environ["EXECUTION_STORE_TYPE"] - - -def test_execution_store_type_case_insensitive(): - """Test that execution store type is case insensitive""" - os.environ["EXECUTION_STORE_TYPE"] = "SQLITE" - - config = EmulatorConfig() - assert config.execution_store_type == StoreType.SQLITE.value - - # Clean up - del os.environ["EXECUTION_STORE_TYPE"] - - -def test_execution_store_type_invalid(): - """Test that invalid execution store type raises ValueError""" - os.environ["EXECUTION_STORE_TYPE"] = "invalid" - - with pytest.raises(ValueError, match="Invalid execution store type"): - EmulatorConfig() - - # Clean up - del os.environ["EXECUTION_STORE_TYPE"] - - -def test_execution_store_type_validation(): - """Test that only valid store types are accepted""" - valid_types = [StoreType.FILESYSTEM.value, StoreType.SQLITE.value] - - for store_type in valid_types: - os.environ["EXECUTION_STORE_TYPE"] = store_type - config = EmulatorConfig() - assert config.execution_store_type == store_type - del os.environ["EXECUTION_STORE_TYPE"] diff --git a/emulator/tests/test_factory.py b/emulator/tests/test_factory.py deleted file mode 100644 index 6bc51bd..0000000 --- a/emulator/tests/test_factory.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Tests for the component factory""" - -import os -import tempfile -from pathlib import Path - -from aws_durable_execution_sdk_python_testing.stores.base import StoreType - -from aws_lambda_durable_functions_emulator.config import EmulatorConfig -from aws_lambda_durable_functions_emulator.factory import TestingLibraryComponentFactory - - -def test_create_store_filesystem(): - """Test that filesystem store can be created""" - with tempfile.TemporaryDirectory() as temp_dir: - os.environ["EXECUTION_STORE_TYPE"] = StoreType.FILESYSTEM.value - os.environ["STORAGE_DIR"] = temp_dir - - config = EmulatorConfig() - store = TestingLibraryComponentFactory.create_store(config) - - assert store is not None - assert "FileSystemExecutionStore" in str(type(store)) - - # Clean up - del os.environ["EXECUTION_STORE_TYPE"] - del os.environ["STORAGE_DIR"] - - -def test_create_store_sqlite(): - """Test that SQLite store can be created""" - with tempfile.TemporaryDirectory() as temp_dir: - os.environ["EXECUTION_STORE_TYPE"] = StoreType.SQLITE.value - os.environ["STORAGE_DIR"] = temp_dir - - config = EmulatorConfig() - store = TestingLibraryComponentFactory.create_store(config) - - assert store is not None - assert "SQLiteExecutionStore" in str(type(store)) - - # Verify database file was created - db_path = Path(temp_dir) / "durable-executions.db" - assert db_path.exists() - - # Clean up - del os.environ["EXECUTION_STORE_TYPE"] - del os.environ["STORAGE_DIR"] - - -def test_create_store_default(): - """Test that default store type is sqlite""" - # Clean up any existing env vars - for key in ["EXECUTION_STORE_TYPE", "STORAGE_DIR"]: - if key in os.environ: - del os.environ[key] - - config = EmulatorConfig() - store = TestingLibraryComponentFactory.create_store(config) - - assert store is not None - assert "SQLiteExecutionStore" in str(type(store)) - - -def test_create_scheduler(): - """Test that scheduler can be created""" - scheduler = TestingLibraryComponentFactory.create_scheduler() - assert scheduler is not None - - -def test_create_invoker(): - """Test that invoker can be created""" - config = EmulatorConfig() - invoker = TestingLibraryComponentFactory.create_invoker(config) - assert invoker is not None - - -def test_create_executor(): - """Test that executor can be created with all components""" - config = EmulatorConfig() - executor = TestingLibraryComponentFactory.create_executor(config) - assert executor is not None diff --git a/emulator/tests/test_server.py b/emulator/tests/test_server.py deleted file mode 100644 index a9fbe5f..0000000 --- a/emulator/tests/test_server.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Tests for the emulator server""" - -import pytest - -from aws_lambda_durable_functions_emulator.config import EmulatorConfig -from aws_lambda_durable_functions_emulator.server import EmulatorServer - - -def test_emulator_config_creation(): - """Test that EmulatorConfig can be created with defaults""" - config = EmulatorConfig() - assert config.host == "0.0.0.0" - assert config.port == 5000 - assert config.lambda_endpoint == "http://localhost:3001" - assert config.storage_dir is None - - -def test_emulator_config_validation(): - """Test that EmulatorConfig validates parameters""" - # Test invalid port - with pytest.raises(ValueError, match="Invalid port"): - EmulatorConfig(port=0) - - # Test invalid Lambda endpoint - with pytest.raises(ValueError, match="Invalid Lambda endpoint URL"): - EmulatorConfig(lambda_endpoint="not-a-url") - - -def test_emulator_server_creation(): - """Test that EmulatorServer can be created""" - config = EmulatorConfig() - server = EmulatorServer(config) - assert server.config == config - assert server.executor is not None - assert server.web_server is not None From 9898fba40a0972e51fb26a203c9e509c310dadeb Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 11:41:48 -0800 Subject: [PATCH 40/52] chore: fetch package version after checkout and build --- .github/workflows/ecr-release.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 01da57d..9cefd62 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -28,17 +28,11 @@ jobs: - arch: x86_64 - arch: arm64 steps: - - name: Get version from __about__.py - id: version - run: | - VERSION=$(python -c "from src.aws_durable_execution_sdk_python_testing.__about__ import __version__; print(__version__)") - echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.13" - - name: Install dependencies run: | python -m pip install --upgrade pip @@ -50,6 +44,11 @@ jobs: platforms: arm64 - name: Build distribution run: hatch build + - name: Get version from __about__.py + id: version + run: | + VERSION=$(python -c "from src.aws_durable_execution_sdk_python_testing.__about__ import __version__; print(__version__)") + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: From 2cb3a70490a88be9b74b9bbbc3f9d9f9281e7165 Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 11:44:58 -0800 Subject: [PATCH 41/52] chore: downgrade virtualenv version --- .github/workflows/ecr-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 9cefd62..6e7f2b9 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -42,6 +42,9 @@ jobs: uses: docker/setup-qemu-action@v3 with: platforms: arm64 + - name: Install specific version of Virtual Env due to bug with hatch + run: | + python -m pip install virtualenv==20.39.0 - name: Build distribution run: hatch build - name: Get version from __about__.py From 64d521caacd90f905695b5ea0aa9e3d47ce8b66e Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 11:49:03 -0800 Subject: [PATCH 42/52] chore: export __version__ in __init__.py --- src/aws_durable_execution_sdk_python_testing/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aws_durable_execution_sdk_python_testing/__init__.py b/src/aws_durable_execution_sdk_python_testing/__init__.py index 88b125f..c25db1c 100644 --- a/src/aws_durable_execution_sdk_python_testing/__init__.py +++ b/src/aws_durable_execution_sdk_python_testing/__init__.py @@ -9,6 +9,8 @@ WebRunnerConfig, ) +from aws_durable_execution_sdk_python_testing.__about__ import __version__ + __all__ = [ "DurableChildContextTestRunner", @@ -17,4 +19,5 @@ "DurableFunctionTestRunner", "WebRunner", "WebRunnerConfig", + "__version__", ] From 63de33275e6730399fa37e46567a360c426782ee Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 11:58:30 -0800 Subject: [PATCH 43/52] chore: try fetching version from init --- .github/workflows/ecr-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 6e7f2b9..6d485c4 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -50,7 +50,7 @@ jobs: - name: Get version from __about__.py id: version run: | - VERSION=$(python -c "from src.aws_durable_execution_sdk_python_testing.__about__ import __version__; print(__version__)") + VERSION=$(python -c "python -c "from src.aws_durable_execution_sdk_python_testing import __version__; print(__version__)") echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 From 2796bcb23b9875093a1d14d25d2184e78722352a Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 11:59:33 -0800 Subject: [PATCH 44/52] chore: fix typo in version command --- .github/workflows/ecr-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 6d485c4..98b605c 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -50,7 +50,7 @@ jobs: - name: Get version from __about__.py id: version run: | - VERSION=$(python -c "python -c "from src.aws_durable_execution_sdk_python_testing import __version__; print(__version__)") + VERSION=$(python -c "from src.aws_durable_execution_sdk_python_testing import __version__; print(__version__)") echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 From 44d97f9c65c1eb8fbaada6747a043a9d8405e5ba Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 12:07:21 -0800 Subject: [PATCH 45/52] chore: try pip installing the project as well as fixing the path to the vrsion --- .github/workflows/ecr-release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 98b605c..9379c98 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -47,10 +47,13 @@ jobs: python -m pip install virtualenv==20.39.0 - name: Build distribution run: hatch build + - name: pip install + run: | + pip install -e . - name: Get version from __about__.py id: version run: | - VERSION=$(python -c "from src.aws_durable_execution_sdk_python_testing import __version__; print(__version__)") + VERSION=$(python -c "from aws_durable_execution_sdk_python_testing import __version__; print(__version__)") echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 From 08d30c45a9ea88ded3933b490271cbc445f3be1d Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 12:10:07 -0800 Subject: [PATCH 46/52] chore: set docker build dir and path_to_dockerfile --- .github/workflows/ecr-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 9379c98..cb2a94e 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -10,8 +10,8 @@ permissions: id-token: write # This is required for requesting the JWT env: - path_to_dockerfile: "/DockerFile" - docker_build_dir: "/" + path_to_dockerfile: "DockerFile" + docker_build_dir: "." aws_region: "us-east-1" ecr_repository_name: "o4w4w0v6/aws-durable-execution-emulator" From 0c1f8b9fe899ff0c5fdb7b33710db21ac4749227 Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 12:12:51 -0800 Subject: [PATCH 47/52] chore: temporary fix for virtualenv issue with hatch --- .github/workflows/deploy-examples.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-examples.yml b/.github/workflows/deploy-examples.yml index 754fb01..dfbb752 100644 --- a/.github/workflows/deploy-examples.yml +++ b/.github/workflows/deploy-examples.yml @@ -62,7 +62,9 @@ jobs: - name: Install Hatch run: pip install hatch - + - name: Install specific version of Virtual Env due to bug with hatch + run: | + python -m pip install virtualenv==20.39.0 - name: Build examples run: hatch run examples:build From 5e8cbc7763c6c2bb7d8cd4e226a76899e7b347be Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 12:15:44 -0800 Subject: [PATCH 48/52] chore: try printing version --- .github/workflows/ecr-release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index cb2a94e..570cf3e 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -54,7 +54,11 @@ jobs: id: version run: | VERSION=$(python -c "from aws_durable_execution_sdk_python_testing import __version__; print(__version__)") + echo "VERSION=$VERSION" echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT + - name: Print Version + id: version-print + run: echo ${{ steps.version.outputs.VERSION }} - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: From d31fa7f70dff88433d73a4ec6707a87e4d5478d9 Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 12:29:05 -0800 Subject: [PATCH 49/52] chore: remove condition on explicit version tag manifest and images --- .github/workflows/ecr-release.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 570cf3e..996503c 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -75,8 +75,8 @@ jobs: env: ECR_REGISTRY: ${{ steps.login-ecr-public.outputs.registry }} ECR_REPOSITORY: ${{ env.ecr_repository_name }} - IMAGE_TAG: "${{ env.image_tag }}${{ github.event.release.name }}" - PER_ARCH_IMAGE_TAG: "${{ matrix.arch }}${{ github.event.release.name }}" + IMAGE_TAG: "${{ env.image_tag }}${{ steps.version.outputs.VERSION }}" + PER_ARCH_IMAGE_TAG: "${{ matrix.arch }}${{ steps.version.outputs.VERSION }}" run: | if [ "${{ matrix.arch }}" = "x86_64" ]; then docker build --platform linux/amd64 --provenance false "${{ env.docker_build_dir }}" -f "${{ env.path_to_dockerfile }}" -t "$ECR_REGISTRY/$ECR_REPOSITORY:$PER_ARCH_IMAGE_TAG" @@ -114,14 +114,12 @@ jobs: with: registry-type: public - name: Create ECR manifest with explicit tag - if: github.event.release.name != '' id: create-ecr-manifest-explicit run: | docker manifest create "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ steps.version.outputs.VERSION }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" - name: Annotate ECR manifest with explicit arm64 tag - if: github.event.release.name != '' id: annotate-ecr-manifest-explicit-arm64 run: | docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ steps.version.outputs.VERSION }}" \ @@ -129,7 +127,6 @@ jobs: --arch arm64 \ --os linux - name: Annotate ECR manifest with explicit amd64 tag - if: github.event.release.name != '' id: annotate-ecr-manifest-explicit-amd64 run: | docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ steps.version.outputs.VERSION }}" \ @@ -137,7 +134,6 @@ jobs: --arch amd64 \ --os linux - name: Push ECR manifest with explicit version - if: github.event.release.name != '' id: push-ecr-manifest-explicit run: | docker manifest push "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ steps.version.outputs.VERSION }}" From a4446a322bf65e2058b0afdd9f997b37a296b6c9 Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 12:56:57 -0800 Subject: [PATCH 50/52] chore: version is fetched from previous job --- .github/workflows/ecr-release.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 996503c..3cb587a 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -22,6 +22,7 @@ jobs: full_image_arm64: ${{ steps.build-publish.outputs.full_image_arm64 }} full_image_x86_64: ${{ steps.build-publish.outputs.full_image_x86_64 }} ecr_registry_repository: ${{ steps.build-publish.outputs.ecr_registry_repository }} + version: ${{ steps.version.outputs.VERSION }} strategy: matrix: include: @@ -56,9 +57,6 @@ jobs: VERSION=$(python -c "from aws_durable_execution_sdk_python_testing import __version__; print(__version__)") echo "VERSION=$VERSION" echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT - - name: Print Version - id: version-print - run: echo ${{ steps.version.outputs.VERSION }} - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: @@ -75,8 +73,8 @@ jobs: env: ECR_REGISTRY: ${{ steps.login-ecr-public.outputs.registry }} ECR_REPOSITORY: ${{ env.ecr_repository_name }} - IMAGE_TAG: "${{ env.image_tag }}${{ steps.version.outputs.VERSION }}" - PER_ARCH_IMAGE_TAG: "${{ matrix.arch }}${{ steps.version.outputs.VERSION }}" + IMAGE_TAG: "${{ env.image_tag }}v${{ steps.version.outputs.VERSION }}" + PER_ARCH_IMAGE_TAG: "${{ matrix.arch }}v${{ steps.version.outputs.VERSION }}" run: | if [ "${{ matrix.arch }}" = "x86_64" ]; then docker build --platform linux/amd64 --provenance false "${{ env.docker_build_dir }}" -f "${{ env.path_to_dockerfile }}" -t "$ECR_REGISTRY/$ECR_REPOSITORY:$PER_ARCH_IMAGE_TAG" @@ -93,16 +91,18 @@ jobs: runs-on: ubuntu-latest needs: [build-and-upload-image-to-ecr] steps: - - name: Grab image and registry/repository name from previous steps + - name: Grab image, registry/repository name, version from previous steps id: ecr_names env: ECR_REGISTRY_REPOSITORY: ${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }} FULL_IMAGE_ARM64: ${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }} FULL_IMAGE_X86_64: ${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }} + VERSION: ${{ needs.build-and-upload-image-to-ecr.outputs.version }} run: | echo "full_image_arm64=$FULL_IMAGE_ARM64" echo "ecr_registry_repository=$ECR_REGISTRY_REPOSITORY" echo "full_image_x86_64=$FULL_IMAGE_X86_64" + echo "version=$VERSION" - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: @@ -116,27 +116,27 @@ jobs: - name: Create ECR manifest with explicit tag id: create-ecr-manifest-explicit run: | - docker manifest create "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ steps.version.outputs.VERSION }}" \ + docker manifest create "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ needs.build-and-upload-image-to-ecr.outputs.version }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" - name: Annotate ECR manifest with explicit arm64 tag id: annotate-ecr-manifest-explicit-arm64 run: | - docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ steps.version.outputs.VERSION }}" \ + docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ needs.build-and-upload-image-to-ecr.outputs.version }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" \ --arch arm64 \ --os linux - name: Annotate ECR manifest with explicit amd64 tag id: annotate-ecr-manifest-explicit-amd64 run: | - docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ steps.version.outputs.VERSION }}" \ + docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ needs.build-and-upload-image-to-ecr.outputs.version }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ --arch amd64 \ --os linux - name: Push ECR manifest with explicit version id: push-ecr-manifest-explicit run: | - docker manifest push "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ steps.version.outputs.VERSION }}" + docker manifest push "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ needs.build-and-upload-image-to-ecr.outputs.version }}" - name: Create ECR manifest with latest tag id: create-ecr-manifest-latest run: | From fb05a5d4951fdc5f23c96a455469e5284e168420 Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 13:01:20 -0800 Subject: [PATCH 51/52] chore: add v prefix to the version --- .github/workflows/ecr-release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ecr-release.yml b/.github/workflows/ecr-release.yml index 3cb587a..4f6fb1a 100644 --- a/.github/workflows/ecr-release.yml +++ b/.github/workflows/ecr-release.yml @@ -116,27 +116,27 @@ jobs: - name: Create ECR manifest with explicit tag id: create-ecr-manifest-explicit run: | - docker manifest create "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ needs.build-and-upload-image-to-ecr.outputs.version }}" \ + docker manifest create "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:v${{ needs.build-and-upload-image-to-ecr.outputs.version }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" - name: Annotate ECR manifest with explicit arm64 tag id: annotate-ecr-manifest-explicit-arm64 run: | - docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ needs.build-and-upload-image-to-ecr.outputs.version }}" \ + docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:v${{ needs.build-and-upload-image-to-ecr.outputs.version }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_arm64 }}" \ --arch arm64 \ --os linux - name: Annotate ECR manifest with explicit amd64 tag id: annotate-ecr-manifest-explicit-amd64 run: | - docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ needs.build-and-upload-image-to-ecr.outputs.version }}" \ + docker manifest annotate "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:v${{ needs.build-and-upload-image-to-ecr.outputs.version }}" \ "${{ needs.build-and-upload-image-to-ecr.outputs.full_image_x86_64 }}" \ --arch amd64 \ --os linux - name: Push ECR manifest with explicit version id: push-ecr-manifest-explicit run: | - docker manifest push "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:${{ needs.build-and-upload-image-to-ecr.outputs.version }}" + docker manifest push "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}:v${{ needs.build-and-upload-image-to-ecr.outputs.version }}" - name: Create ECR manifest with latest tag id: create-ecr-manifest-latest run: | From 1ebdee6008ce3a486d75130c683b483d12bc2d3f Mon Sep 17 00:00:00 2001 From: hsilan Date: Thu, 26 Feb 2026 13:23:06 -0800 Subject: [PATCH 52/52] chore: try with no docker HEALTHCHECK --- DockerFile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/DockerFile b/DockerFile index ea04654..da35bb2 100644 --- a/DockerFile +++ b/DockerFile @@ -9,11 +9,6 @@ ENV AWS_ACCESS_KEY_ID=foo \ AWS_SECRET_ACCESS_KEY=bar \ AWS_DEFAULT_REGION=us-east-1 -EXPOSE 9014 - -HEALTHCHECK --interval=10s --timeout=3s --start-period=5s \ - CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:9014/health').read()" || exit 1 - CMD ["dex-local-runner", "start-server", \ "--host", "0.0.0.0", \ "--port", "9014", \