Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion .github/workflows/cloud-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,19 @@ jobs:
hatch run examples:build
hatch run examples:generate-sam-template

- name: Ensure MicroVM image
# The worker runs in the MicroVM, not in the function. So one image
# serves every function runtime. Only the Python 3.13 job builds the
# image, deploys the MicroVM examples, and runs their tests. The other
# jobs pass no image, so their stacks leave the MicroVM examples out.
if: matrix.python-version == '3.13'
env:
TEST_ACCOUNT_ID: ${{ secrets.TEST_ACCOUNT_ID }}
# The script warns when the functions use a non-default endpoint,
# because the worker sends its callbacks to the default endpoint.
LAMBDA_ENDPOINT: ${{ github.event.inputs.lambda_endpoint || vars.TEST_LAMBDA_ENDPOINT }}
run: hatch run examples:ensure-microvm-image --output "$RUNNER_TEMP/microvm-image.json"

- name: Deploy all examples with SAM
id: deploy
env:
Expand All @@ -111,6 +124,7 @@ jobs:
STACK_NAME: ${{ matrix.python-prefix }}-python-examples
FUNCTION_NAME_PREFIX: ${{ matrix.python-prefix }}-
GH_TOKEN: ${{ github.token }}
MICROVM_EXAMPLES: ${{ matrix.python-version == '3.13' }}
run: |
echo "Building SAM application"
sam build --template-file packages/aws-durable-execution-sdk-python-examples/template.generated.json
Expand Down Expand Up @@ -154,9 +168,25 @@ jobs:
echo "Using custom Lambda endpoint override"
fi

# The MicroVM examples deploy only with an image. Without one, the
# template leaves them out, and the name map omits their tests.
NAME_MAP_ARGS=()
if [ "$MICROVM_EXAMPLES" = "true" ]; then
MICROVM_IMAGE_FILE="$RUNNER_TEMP/microvm-image.json"
MICROVM_IMAGE_ARN=$(python3 -c 'import json, sys; print(json.load(open(sys.argv[1]))["imageArn"])' "$MICROVM_IMAGE_FILE")
MICROVM_EXECUTION_ROLE_ARN=$(python3 -c 'import json, sys; print(json.load(open(sys.argv[1]))["executionRoleArn"])' "$MICROVM_IMAGE_FILE")
PARAMETER_OVERRIDES+=(
MicrovmImageArn="$MICROVM_IMAGE_ARN"
MicrovmExecutionRoleArn="$MICROVM_EXECUTION_ROLE_ARN"
)
NAME_MAP_ARGS+=(--include-microvm)
echo "Deploying the MicroVM examples with image $MICROVM_IMAGE_ARN"
fi

PYTEST_FUNCTION_NAME_MAP=$(
python packages/aws-durable-execution-sdk-python-examples/scripts/generate_function_name_map.py \
--function-name-prefix "$FUNCTION_NAME_PREFIX"
--function-name-prefix "$FUNCTION_NAME_PREFIX" \
"${NAME_MAP_ARGS[@]}"
)

# TODO: Remove this one-time cleanup after existing CI stacks adopt the
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dist/
**/build/
**/*.zip
.aws-sam/
.microvm-image-build/
**/template.generated.json

.env
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,31 @@
"ExecutionTimeout": 300
},
"path": "./src/error_handling/catch_typed_step_error.py"
},
{
"name": "MicroVM Run Job",
"description": "Runs one job in a Lambda MicroVM and returns the result that the MicroVM reports",
"handler": "microvm_run_job.handler",
"integration": true,
"usesMicrovm": true,
"durableConfig": {
"RetentionPeriodInDays": 7,
"ExecutionTimeout": 900
},
"path": "./src/microvm/microvm_run_job.py"
},
{
"name": "MicroVM Session",
"description": "Runs two jobs in one Lambda MicroVM, with a durable wait between them",
"handler": "microvm_session.handler",
"integration": true,
"usesMicrovm": true,
"timeout": 120,
"durableConfig": {
"RetentionPeriodInDays": 7,
"ExecutionTimeout": 900
},
"path": "./src/microvm/microvm_session.py"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM public.ecr.aws/docker/library/python:3.13-alpine
WORKDIR /app
# scripts/ensure_microvm_image.py puts the worker wheel, built from this
# repo, in wheels/. pip installs boto3 from PyPI, at the versions that
# constraints.txt pins.
COPY constraints.txt wheels/ /tmp/build/
RUN pip install --no-cache-dir --root-user-action=ignore \
--constraint /tmp/build/constraints.txt /tmp/build/*.whl \
&& rm -rf /tmp/build
COPY app.py .
EXPOSE 8080
# Lambda snapshots the running process after the ready hook.
CMD ["python", "-u", "app.py"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""The application in the MicroVM image that the MicroVM examples run.

Lambda starts this process when it builds the image, and snapshots it. A
MicroVM launched from the image resumes the snapshot. The worker then receives
each job, runs it, and reports the result to the durable function's callback.

scripts/ensure_microvm_image.py builds the image from this file, the
Dockerfile, and the worker wheel. The cloud tests pass the image ARN to the
example functions.
"""

import hashlib
import logging
from pathlib import Path
from typing import Any

from aws_durable_execution_sdk_python_microvm_worker import (
MicrovmJobContext,
start_microvm_worker,
)


# Only the MicroVM's own jobs write here. A session's jobs run in one MicroVM.
# So a later job of the session reads what an earlier job wrote.
SESSION_DIR = Path("/tmp/session")


def count_words(job_input: dict[str, Any], context: MicrovmJobContext) -> dict:
"""Serve the run-job example. A job that names no route runs this handler."""
text: str = job_input["text"]
return {
"wordCount": len(text.split()),
"sha256": hashlib.sha256(text.encode()).hexdigest(),
"microvmId": context.microvm_id,
}


def _session_file(key: str) -> Path:
# A key with a directory part could name a file outside SESSION_DIR. So
# the handlers accept only a plain file name.
if not key or Path(key).name != key or key in {".", ".."}:
msg = f"invalid key: {key!r}"
raise ValueError(msg)
return SESSION_DIR / key


def write_value(job_input: dict[str, Any], context: MicrovmJobContext) -> dict:
"""Serve the session example's first job. It writes a file."""
path = _session_file(job_input["key"])
SESSION_DIR.mkdir(parents=True, exist_ok=True)
path.write_text(job_input["value"], encoding="utf-8")
return {"microvmId": context.microvm_id}


def read_value(job_input: dict[str, Any], context: MicrovmJobContext) -> dict:
"""Serve the session example's second job. It reads the file."""
path = _session_file(job_input["key"])
return {
"microvmId": context.microvm_id,
"value": path.read_text(encoding="utf-8"),
}


def main() -> None:
# The worker logs to the standard library logger. INFO makes its lines
# reach stdout.
logging.basicConfig(level=logging.INFO)
worker = start_microvm_worker(
handler=count_words,
routes={"/write": write_value, "/read": read_value},
)
# start_microvm_worker serves in a daemon thread and returns. So the
# process waits here.
worker.wait()


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# The image installs the worker wheel and its dependencies from PyPI.
# scripts/ensure_microvm_image.py hashes this file into the image name.
# These pins fix the installed versions. So an image with a given name always
# holds the same dependency versions. A change to a pin builds a new image.
#
# The worker needs boto3 1.43.35 or later, the first release with the
# lambda-microvms client. These are the versions that the repo's examples
# environment resolved on 2026-10-22.
boto3==1.43.118
botocore==1.43.118
jmespath==1.1.0
python-dateutil==2.9.0.post0
s3transfer==0.19.3
six==1.17.0
urllib3==2.8.0
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ def build_examples() -> None:

runtime_packages = [
packages_dir / "aws-durable-execution-sdk-python",
# The MicroVM examples import the extras. The examples package does
# not depend on the extras, so pip installs the extras only from here.
packages_dir / "aws-durable-execution-sdk-python-extras",
packages_dir / "aws-durable-execution-sdk-python-otel",
packages_dir / "aws-durable-execution-sdk-python-testing",
]
Expand Down
Loading
Loading