Skip to content

ci: deploy examples with SAM stack#517

Open
zhongkechen wants to merge 1 commit into
mainfrom
codex/sam-deploy-examples
Open

ci: deploy examples with SAM stack#517
zhongkechen wants to merge 1 commit into
mainfrom
codex/sam-deploy-examples

Conversation

@zhongkechen

@zhongkechen zhongkechen commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace the old per-function example deployment experience with a SAM-stack based flow: developers now build once, generate a template, and deploy the full examples stack.
  • Remove the single-function deploy CLI path so manual and CI workflows match how we actually test examples in cloud.
  • Remove the checked-in generated SAM template and update workflow; developers no longer need to maintain or review generated template churn.
  • Keep local example tests simple with hatch run dev-examples:test, while cloud tests use the deployed stack and an automatically generated function-name map.
  • Make each cloud-test matrix leg deploy Lambda functions with the matching Python runtime, so the 3.11, 3.12, 3.13, and 3.14 jobs actually exercise cloud resources on that runtime.
  • Add optional Lambda endpoint override support for preprod testing without hard-coding an endpoint into the workflow.

Workflow changes

  • Add .github/workflows/cloud-tests.yml with workflow display name Cloud tests.
  • Run the workflow for every PR targeting main and via workflow_dispatch.
  • Rename the cloud test job to example-tests.
  • Run cloud tests across Python 3.11, 3.12, 3.13, and 3.14.
  • Use Python-version prefixes for deployed resources: Py311, Py312, Py313, and Py314.
  • Prefix stack names as ${PYTHON_PREFIX}-python-examples and Lambda function names as ${PYTHON_PREFIX}-.
  • Add a concurrency group per Python version, cloud-tests-${{ matrix.python-prefix }}, with cancel-in-progress: false, so workflows for the same Python version do not overlap.
  • Configure AWS credentials with secrets.TEST_ROLE_ARN.
  • Verify the assumed account matches secrets.TEST_ACCOUNT_ID.
  • Deploy all examples with SAM using sam build and sam deploy.
  • Pass PythonRuntime=python${{ matrix.python-version }} to SAM so each matrix leg deploys functions on the same Python version used by the test runner.
  • Use secrets.TEST_LAMBDA_EXECUTION_ROLE_ARN as the pre-existing Lambda execution role for every generated function.
  • Add optional Lambda endpoint override support through the manual lambda_endpoint input or the TEST_LAMBDA_ENDPOINT repository/org variable.
  • Pass the optional Lambda endpoint override to both SAM (LambdaEndpoint) and pytest (LAMBDA_ENDPOINT) so deployed functions and test clients target the same endpoint.
  • Only export LAMBDA_ENDPOINT to later workflow steps when an override is configured, so normal PR runs do not pass an empty endpoint to boto3.
  • Generate PYTEST_FUNCTION_NAME_MAP after deployment and pass it to the test runner.
  • Run the integration tests concurrently with pytest-xdist using -n 8 --dist loadfile.
  • Leave deployed stacks in place after cloud test runs so failed runs can be inspected and debugged.

Removed workflows and legacy deployment path

  • Delete .github/workflows/deploy-examples.yml.
  • Delete .github/workflows/integration-tests.yml.
  • Delete .github/workflows/update-sam-template.yml.
  • Delete the checked-in packages/aws-durable-execution-sdk-python-examples/template.yaml.
  • Delete the old packages/aws-durable-execution-sdk-python-examples/cli.py.
  • Remove Hatch scripts for the deleted example CLI commands: cli, bootstrap, deploy, invoke, get, history, policy, and list.

SAM/template generation

  • Update generate_sam_template.py to build template.generated.json from examples-catalog.json.
  • Add reusable helpers for loading the catalog, converting handler names to CloudFormation logical IDs, and building the template object.
  • Add a --output option to write the generated template to a custom path.
  • Add FunctionNamePrefix and LambdaExecutionRoleArn template parameters.
  • Add a PythonRuntime template parameter with default python3.13 and allowed values for python3.11, python3.12, python3.13, and python3.14.
  • Set Globals.Function.Runtime from the PythonRuntime parameter instead of hard-coding python3.13.
  • Set each generated function's FunctionName from FunctionNamePrefix plus the logical ID.
  • Set each generated function's Role from the LambdaExecutionRoleArn parameter.
  • Preserve per-example DurableConfig, LoggingConfig, Layers, Tracing, and environment settings from the catalog.
  • Keep LambdaEndpoint as a SAM parameter with the standard Lambda endpoint default and wire it to AWS_ENDPOINT_URL_LAMBDA for generated functions.
  • Stop creating an IAM role in the generated template.

Build/test tooling

  • Add scripts/build_examples.py to build a shared example bundle for SAM packaging.
  • The build script cleans the examples build/ directory, installs runtime packages into it, and copies example source files into the Lambda bundle.
  • Add scripts/generate_function_name_map.py to create the JSON mapping from example names to qualified $LATEST function names.
  • Update hatch run examples:build to use build_examples.py.
  • Update hatch run examples:generate-sam-template to generate template.generated.json.
  • Update hatch run examples:clean to remove the examples build directory, generated template, and root .aws-sam build output.
  • Add pytest-xdist to the shared test Hatch environment for concurrent cloud test execution.

Cloud test runner changes

  • Replace the single-function cloud-test environment variables with PYTEST_FUNCTION_NAME_MAP.
  • Update cloud test lookup to parse the JSON function map and match test marker names case-insensitively.
  • Skip cloud tests whose example name is not present in PYTEST_FUNCTION_NAME_MAP.
  • Keep support for optional LAMBDA_ENDPOINT in the cloud runner.
  • Treat a blank LAMBDA_ENDPOINT value as unset to avoid boto3 Invalid endpoint errors.

Documentation and ignore updates

  • Update CONTRIBUTING.md to document the SAM build/deploy flow instead of the old example CLI deploy flow.
  • Update the examples test README to describe deploying the full SAM stack before cloud tests.
  • Document the optional PythonRuntime SAM parameter in manual deploy examples.
  • Document PYTEST_FUNCTION_NAME_MAP as the required cloud-mode test mapping.
  • Point CI documentation to cloud-tests.yml.
  • Ignore root .aws-sam/ output and generated template.generated.json files.

Testing

  • hatch fmt --check
  • python3 -m py_compile packages/aws-durable-execution-sdk-python-examples/scripts/generate_sam_template.py packages/aws-durable-execution-sdk-python-examples/scripts/generate_function_name_map.py packages/aws-durable-execution-sdk-python-examples/scripts/build_examples.py packages/aws-durable-execution-sdk-python-examples/test/conftest.py
  • hatch run examples:generate-sam-template
  • Generated-template assertion: 61 functions use LambdaExecutionRoleArn and no DurableFunctionRole resource is present.
  • Generated-template assertion: Globals.Function.Runtime resolves from PythonRuntime, whose default remains python3.13.
  • python3 packages/aws-durable-execution-sdk-python-examples/scripts/generate_function_name_map.py --function-name-prefix PythonPr517-
  • sam validate --template-file packages/aws-durable-execution-sdk-python-examples/template.generated.json --region us-west-2
  • sam build --template-file packages/aws-durable-execution-sdk-python-examples/template.generated.json
  • hatch run test:examples-integration --help
  • YAML parse check for .github/workflows/cloud-tests.yml
  • git diff --check

Previously also ran:

  • hatch run dev-examples:test -k test_hello_world
  • hatch run examples:build

Did not run an actual AWS deploy locally.

@zhongkechen zhongkechen force-pushed the codex/sam-deploy-examples branch 2 times, most recently from 00edfd0 to 65c6d03 Compare July 7, 2026 21:47
@zhongkechen zhongkechen self-assigned this Jul 7, 2026
@zhongkechen zhongkechen force-pushed the codex/sam-deploy-examples branch 11 times, most recently from 9dfbb75 to bfab814 Compare July 7, 2026 22:54
@zhongkechen zhongkechen marked this pull request as ready for review July 7, 2026 23:30
@zhongkechen zhongkechen force-pushed the codex/sam-deploy-examples branch from bfab814 to 92f8b42 Compare July 7, 2026 23:42
@zhongkechen zhongkechen force-pushed the codex/sam-deploy-examples branch from 92f8b42 to 3ae645a Compare July 7, 2026 23:51
@zhongkechen zhongkechen requested a review from a team July 8, 2026 00:03

@yaythomas yaythomas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a massive improvement on the ad-hoc per-function CLI deploy path to a SAM stack, and it deletes a lot of code. Thank you!

The refactor of generate_sam_template.py is really cool too!

which is to say review comments are largely nits and I'm pretty much happy to approve :-)

the broader conceptual question I have is whether we want per-PR, or per-runtime-version stacks. If we want per-runtime, which is what is currently implement, shared per-version stack then only the most recent run is left behind for debugging. (so 1 PR could overwrite another PRs). but if we go per-PR, then we have to think about cleaning up stacks.

I'm not all that opinionated about this either, just flagging this for discussion and happy to go with your preference :-)

name: Cloud tests

on:
pull_request:

@yaythomas yaythomas Jul 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no paths:...

The old deploy-examples.yml had a paths filter so it only ran when

  • packages/aws-durable-execution-sdk-python/src/**
  • packages/aws-durable-execution-sdk-python-examples/**,

or the workflow itself changed.

And the old integration-tests.yml was Python 3.13 only.

This new workflow runs on every PR to main regardless of path, on all four Python versions. Not the end of the world, but it could result in unnecessary deployments/actions running.

STACK_NAME="${PYTHON_PREFIX}-python-examples"
FUNCTION_NAME_PREFIX="${PYTHON_PREFIX}-"

STACK_NAME=$(echo "$STACK_NAME" | tr -cd '[:alnum:]-' | cut -c1-128)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some of dynamic scripting here we could possibly do in env via format?

The stack name is only keyed on Python version, so all PRs share the same four stacks.

group + cancel-in-progress: false will make it queue rather than clobber existing for simultaneous PRs.

but this means the next PR for that Python version will overwrite everything.

If we want per-PR isolation, then "PR--Python or something"), the small change is to compose the names in env: instead of the run: shell step,
using the pull_request context.

    jobs:
      example-tests:
        env:
          # PR runs get PrN; workflow_dispatch and pushes get RunN.
          PR_TAG: ${{ github.event.pull_request.number && format('Pr{0}', github.event.pull_request.number) || format('Run{0}', github.run_id) }}
        steps:
          ...
          - name: Deploy all examples with SAM
            env:
              STACK_NAME: ${{ matrix.python-prefix }}-${{ env.PR_TAG }}-python-examples
              FUNCTION_NAME_PREFIX: ${{ matrix.python-prefix }}-${{ env.PR_TAG }}-
              ...

but this also has the negative that per-PR stacks accumulate until they get cleaned up.

We could have a a companion on: pull_request types: [closed] workflow that runs aws cloudformation delete-stack across the four python-prefix values?

runs-on: ubuntu-latest
concurrency:
group: cloud-tests-${{ matrix.python-prefix }}
cancel-in-progress: false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about per-PR rather than per-runtime?

concurrency:
  group: cloud-tests-${{ matrix.python-prefix }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

this also cancels predecessors for the same PR.

},
}
},
"FunctionNamePrefix": {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

maybe make this one required rather than empty, or set a distinctive default like "PythonExamples-"??

if someone (definitely future me!) runs sam deploy manually without setting FunctionNamePrefix, I'd get functions named HelloWorld, Timer, Step, etc.

short, generic, easy to collide with other things in an accountThe CI workflow always sets the prefix, so
CI is safe; the risk is a human running the CONTRIBUTING.md recipe.

)

logger.info("Copying examples from %s", src_dir)
for file_path in src_dir.rglob("*"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flattening step copies every file under src/ to build/ and the comment already explains why. But if basenames collide, then ordering is rglob("*") order. When two files collide, whichever comes later wins. Not deterministic across filesystems.

e.g src/foo/step.py alongside src/step/step.py.

could raise on collision?

    if not file_path.is_file():
        continue
    dst = build_dir / file_path.name
    if dst.exists() and not dst.samefile(file_path):
        raise RuntimeError(
            f"Duplicate basename would flatten to {dst}: {file_path}"
        )
    shutil.copy2(file_path, dst)


- name: Run Integration Tests
env:
AWS_REGION: ${{ env.AWS_REGION }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: already set at top-level

Comment thread CONTRIBUTING.md
# Examples deployment (from repo root)
hatch run examples:build
hatch run examples:deploy "Hello World"
hatch run examples:generate-sam-template

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you so much for this! awesome to have the CONTRIBUTING guide and the test/README get an update alongside the change :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants