Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
6,566 changes: 6,566 additions & 0 deletions .github/model/lambda.json

Large diffs are not rendered by default.

172 changes: 172 additions & 0 deletions .github/workflows/deploy-examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
name: Deploy Python Examples

on:
pull_request:
branches: [ "main", "development"]
paths:
- 'src/aws_durable_execution_sdk_python_testing/**'
- 'examples/**'
- '.github/workflows/deploy-examples.yml'
workflow_dispatch:

env:
AWS_REGION: us-west-2

permissions:
id-token: write
contents: read

jobs:
setup:
runs-on: ubuntu-latest
outputs:
examples: ${{ steps.get-examples.outputs.examples }}
steps:
- uses: actions/checkout@v4

- name: Get examples from catalog
id: get-examples
working-directory: ./examples
run: |
echo "examples=$(jq -c '.examples | map(select(.integration == true))' examples-catalog.json)" >> $GITHUB_OUTPUT

integration-test:
needs: setup
runs-on: ubuntu-latest
name: ${{ matrix.example.name }}
strategy:
matrix:
example: ${{ fromJson(needs.setup.outputs.examples) }}
fail-fast: false

steps:
- uses: actions/checkout@v4

- name: Setup SSH Agent
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SDK_KEY }}

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.13'

- name: Configure AWS credentials
if: github.event_name != 'workflow_dispatch' || github.actor != 'nektos/act'
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: "${{ secrets.ACTIONS_INTEGRATION_ROLE_NAME }}"
role-session-name: pythonTestingLibraryGitHubIntegrationTest
aws-region: ${{ env.AWS_REGION }}

- name: Install custom Lambda model
run: |
aws configure add-model --service-model file://.github/model/lambda.json --service-name lambda

- name: Install Hatch
run: pip install hatch

- name: Build examples
run: hatch run examples:build

- name: Deploy Lambda function - ${{ matrix.example.name }}
env:
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
LAMBDA_ENDPOINT: ${{ secrets.LAMBDA_ENDPOINT }}
INVOKE_ACCOUNT_ID: ${{ secrets.INVOKE_ACCOUNT_ID }}
KMS_KEY_ARN: ${{ secrets.KMS_KEY_ARN }}
run: |
# Build function name
EXAMPLE_NAME_CLEAN=$(echo "${{ matrix.example.name }}" | sed 's/ //g')
if [ "${{ github.event_name }}" = "pull_request" ]; then
FUNCTION_NAME="${EXAMPLE_NAME_CLEAN}-Python-PR-${{ github.event.number }}"
else
FUNCTION_NAME="${EXAMPLE_NAME_CLEAN}-Python"
fi

# Extract handler file name
HANDLER_FILE=$(echo "${{ matrix.example.handler }}" | sed 's/\.handler$//')

echo "Deploying $HANDLER_FILE as $FUNCTION_NAME"
hatch run examples:deploy "$HANDLER_FILE" "$FUNCTION_NAME"

# Store function name for later steps
echo "FUNCTION_NAME=$FUNCTION_NAME" >> $GITHUB_ENV

- name: Invoke Lambda function - ${{ matrix.example.name }}
env:
LAMBDA_ENDPOINT: ${{ secrets.LAMBDA_ENDPOINT }}
run: |
echo "Testing function: $FUNCTION_NAME"
aws lambda invoke \
--function-name "$FUNCTION_NAME" \
--cli-binary-format raw-in-base64-out \
--payload '{"name": "World"}' \
--region "${{ env.AWS_REGION }}" \
--endpoint-url "$LAMBDA_ENDPOINT" \
/tmp/response.json \
> /tmp/invoke_response.json

echo "Response:"
cat /tmp/response.json

echo "Full Invoke Response:"
cat /tmp/invoke_response.json

echo "All Response Headers:"
jq -r '.ResponseMetadata.HTTPHeaders' /tmp/invoke_response.json || echo "No HTTPHeaders found"

# Extract invocation ID from response headers
INVOCATION_ID=$(jq -r '.ResponseMetadata.HTTPHeaders["x-amzn-invocation-id"] // empty' /tmp/invoke_response.json)
if [ -n "$INVOCATION_ID" ]; then
echo "INVOCATION_ID=$INVOCATION_ID" >> $GITHUB_ENV
echo "Captured Invocation ID: $INVOCATION_ID"
else
echo "Warning: Could not capture invocation ID from response"
fi

- name: Find Durable Execution - ${{ matrix.example.name }}
env:
LAMBDA_ENDPOINT: ${{ secrets.LAMBDA_ENDPOINT }}
run: |
echo "Listing durable executions for function: $FUNCTION_NAME"
aws lambda list-durable-executions \
--function-name "$FUNCTION_NAME" \
--region "${{ env.AWS_REGION }}" \
--endpoint-url "$LAMBDA_ENDPOINT" \
--cli-binary-format raw-in-base64-out \
--status-filter SUCCEEDED \
> /tmp/executions.json
echo "Durable Executions:"
cat /tmp/executions.json

# Extract the first execution ARN for history retrieval
EXECUTION_ARN=$(jq -r '.DurableExecutions[0].DurableExecutionArn // empty' /tmp/executions.json)
echo "EXECUTION_ARN=$EXECUTION_ARN" >> $GITHUB_ENV

- name: Get Durable Execution History - ${{ matrix.example.name }}
if: env.EXECUTION_ARN != ''
env:
LAMBDA_ENDPOINT: ${{ secrets.LAMBDA_ENDPOINT }}
run: |
echo "Getting execution history for: $EXECUTION_ARN"
aws lambda get-durable-execution-history \
--durable-execution-arn "$EXECUTION_ARN" \
--region "${{ env.AWS_REGION }}" \
--endpoint-url "$LAMBDA_ENDPOINT" \
--cli-binary-format raw-in-base64-out \
> /tmp/history.json
echo "Execution History:"
cat /tmp/history.json

# - name: Cleanup Lambda function
# if: always()
# env:
# LAMBDA_ENDPOINT: ${{ secrets.LAMBDA_ENDPOINT }}
# run: |
# echo "Deleting function: $FUNCTION_NAME"
# aws lambda delete-function \
# --function-name "$FUNCTION_NAME" \
# --endpoint-url "$LAMBDA_ENDPOINT" \
# --region "${{ env.AWS_REGION }}" || echo "Function already deleted or doesn't exist"
6 changes: 6 additions & 0 deletions examples/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# AWS Configuration for Lambda Deployment
AWS_REGION=us-west-2
AWS_ACCOUNT_ID=123456789012
LAMBDA_ENDPOINT=https://lambda.us-west-2.amazonaws.com
INVOKE_ACCOUNT_ID=123456789012
KMS_KEY_ARN=arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012
4 changes: 4 additions & 0 deletions examples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build/
*.zip
.env
.aws-sam/
44 changes: 44 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Python Durable Functions Examples

## Local Testing with SAM

Test functions locally:
```bash
sam local invoke HelloWorldFunction
```

Test with custom event:
```bash
sam local invoke HelloWorldFunction -e event.json
```

## Deploy Functions

Deploy with Python script:
```bash
python3 deploy.py hello_world
```

Deploy with SAM:
```bash
sam build
sam deploy --guided
```

## Environment Variables

- `AWS_ACCOUNT_ID`: Your AWS account ID
- `LAMBDA_ENDPOINT`: Your Lambda service endpoint
- `INVOKE_ACCOUNT_ID`: Account ID allowed to invoke functions
- `AWS_REGION`: AWS region (default: us-west-2)
- `KMS_KEY_ARN`: KMS key for encryption (optional)

## Available Examples

- **hello_world**: Simple hello world function

## Adding New Examples

1. Add your Python function to `src/`
2. Update `examples-catalog.json` and `template.yaml`
3. Deploy using either script above
49 changes: 49 additions & 0 deletions examples/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python3

import shutil
import site
from pathlib import Path


def build():
"""Build examples with SDK dependencies from current environment."""
examples_dir = Path(__file__).parent
build_dir = examples_dir / "build"

# Clean build directory
if build_dir.exists():
shutil.rmtree(build_dir)
build_dir.mkdir()

print("Copying SDK from current environment...")

# Copy the SDK from current environment (hatch installs it)
for site_dir in site.getsitepackages():
sdk_path = Path(site_dir) / "aws_durable_execution_sdk_python"
if sdk_path.exists():
shutil.copytree(sdk_path, build_dir / "aws_durable_execution_sdk_python")
print(f"Copied SDK from {sdk_path}")
break
else:
print("SDK not found in site-packages")

print("Copying testing SDK source...")

# Copy testing SDK source
sdk_src = examples_dir.parent / "src" / "aws_durable_execution_sdk_python_testing"
if sdk_src.exists():
shutil.copytree(sdk_src, build_dir / "aws_durable_execution_sdk_python_testing")

print("Copying example functions...")

# Copy example source files
src_dir = examples_dir / "src"
for py_file in src_dir.glob("*.py"):
if py_file.name != "__init__.py":
shutil.copy2(py_file, build_dir)

print(f"Build complete: {build_dir}")


if __name__ == "__main__":
build()
Loading