Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
# We pass the list of examples here, but we can't pass an array as argument
# Instead, we pass a String with a valid JSON array.
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
examples: "[ 'APIGatewayV1', 'APIGatewayV2', 'APIGatewayV2+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'HelloWorldNoTraits', 'HummingbirdLambda', 'MultiSourceAPI', 'MultiTenant', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming+APIGateway', 'Streaming+FunctionUrl', 'Streaming+Codable', 'ServiceLifecycle+Postgres', 'Testing', 'Tutorial' ]"
examples: "[ 'APIGatewayV1', 'APIGatewayV2', 'APIGatewayV2+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'HelloWorldNoTraits', 'HummingbirdLambda', 'ManagedInstances', 'MultiSourceAPI', 'MultiTenant', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming+APIGateway', 'Streaming+FunctionUrl', 'Streaming+Codable', 'ServiceLifecycle+Postgres', 'Testing', 'Tutorial' ]"
archive_plugin_examples: "[ 'HelloWorld', 'ResourcesPackaging' ]"
archive_plugin_enabled: true

Expand Down
3 changes: 3 additions & 0 deletions Examples/ManagedInstances/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
response.json
samconfig.toml
Makefile
46 changes: 46 additions & 0 deletions Examples/ManagedInstances/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// swift-tools-version:6.2

import PackageDescription

let package = Package(
name: "swift-aws-lambda-runtime-example",
platforms: [.macOS(.v15)],
products: [
.executable(name: "HelloJSON", targets: ["HelloJSON"]),
.executable(name: "Streaming", targets: ["Streaming"]),
.executable(name: "BackgroundTasks", targets: ["BackgroundTasks"]),
],
dependencies: [
// For local development (default)
.package(name: "swift-aws-lambda-runtime", path: "../.."),

// For standalone usage, comment the line above and uncomment below:
// .package(url: "https://github.com/awslabs/swift-aws-lambda-runtime.git", from: "2.0.0"),

.package(url: "https://github.com/awslabs/swift-aws-lambda-events.git", from: "1.0.0"),
],
targets: [
.executableTarget(
name: "HelloJSON",
dependencies: [
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime")
],
path: "Sources/HelloJSON"
),
.executableTarget(
name: "Streaming",
dependencies: [
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
],
path: "Sources/Streaming"
),
.executableTarget(
name: "BackgroundTasks",
dependencies: [
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime")
],
path: "Sources/BackgroundTasks"
),
]
)
126 changes: 126 additions & 0 deletions Examples/ManagedInstances/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Lambda Managed Instances Example

This example demonstrates deploying Swift Lambda functions to Lambda Managed Instances using AWS SAM. Lambda Managed Instances provide serverless simplicity with EC2 flexibility and cost optimization by running your functions on customer-owned EC2 instances.

## Functions Included

1. **HelloJSON** - JSON input/output with structured data types
2. **Streaming** - Demonstrates response streaming capabilities
3. **BackgroundTasks** - Handles long-running background processing

## Prerequisites

- AWS CLI configured with appropriate permissions
- SAM CLI installed
- Swift 6.0+ installed
- An existing [Lambda Managed Instances capacity provider](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances-capacity-providers.html)

## Capacity Provider Configuration

[Create your own capacity provider](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances-capacity-providers.html#lambda-managed-instances-creating-capacity-provider) before deploying this example.

This example uses a pre-configured capacity provider with the ARN:
```
arn:aws:lambda:us-west-2:486652066693:capacity-provider:TestEC2
```

## Deployment

```bash
# Build the Swift packages
swift package archive --allow-network-access docker

# Change the values below to match your setup
REGION=us-west-2
CAPACITY_PROVIDER=arn:aws:lambda:us-west-2:<YOUR ACCOUNT ID>:capacity-provider:<YOUR CAPACITY PROVIDER NAME>

# Deploy using SAM
sam deploy \
--resolve-s3 \
--template-file template.yaml \
--stack-name swift-lambda-managed-instances \
--capabilities CAPABILITY_IAM \
--region ${REGION} \
--parameter-overrides \
CapacityProviderArn=${CAPACITY_PROVIDER}
```

## Function Details

### HelloJSON Function
- **Timeout**: 15 seconds (default)
- **Concurrency**: 8 per execution environment (default)
- **Input**: JSON `{"name": "string", "age": number}`
- **Output**: JSON `{"greetings": "string"}`

### Streaming Function
- **Timeout**: 60 seconds
- **Concurrency**: 8 per execution environment (default)
- **Features**: Response streaming enabled
- **Output**: Streams numbers with pauses

### BackgroundTasks Function
- **Timeout**: 300 seconds (5 minutes)
- **Concurrency**: 8 per execution environment (default)
- **Input**: JSON `{"message": "string"}`
- **Features**: Long-running background processing after response

## Testing with AWS CLI

After deployment, invoke each function with the AWS CLI:

### Test HelloJSON Function
```bash
REGION=us-west-2
aws lambda invoke \
--region ${REGION} \
--function-name swift-lambda-managed-instances-HelloJSON \
--payload $(echo '{ "name" : "Swift Developer", "age" : 50 }' | base64) \
out.txt && cat out.txt && rm out.txt

# Expected output: {"greetings": "Hello Swift Developer. You look older than your age."}
```

### Test Streaming Function
```bash
# Get the Streaming URL
REGION=us-west-2
STREAMING_URL=$(aws cloudformation describe-stacks \
--stack-name swift-lambda-managed-instances \
--region ${REGION} \
--query 'Stacks[0].Outputs[?OutputKey==`StreamingFunctionUrl`].OutputValue' \
--output text)

# Set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN environment variables
eval $(aws configure export-credentials --format env)

# Test with curl (streaming response)
curl "$STREAMING_URL" \
--user "${AWS_ACCESS_KEY_ID}":"${AWS_SECRET_ACCESS_KEY}" \
--aws-sigv4 "aws:amz:${REGION}:lambda" \
-H "x-amz-security-token: ${AWS_SESSION_TOKEN}" \
--no-buffer

# Expected output: Numbers streaming with pauses
```

### Test BackgroundTasks Function
```bash
# Test with AWS CLI
REGION=us-west-2
aws lambda invoke \
--region ${REGION} \
--function-name swift-lambda-managed-instances-BackgroundTasks \
--payload $(echo '{ "message" : "Additional processing in the background" }' | base64) \
out.txt && cat out.txt && rm out.txt

# Expected output: {"echoedMessage": "Additional processing in the background"}
# Note: Background processing continues after response is sent
```

## Cleanup

To remove all resources:
```bash
sam delete --stack-name swift-lambda-managed-instances --region ${REGION}
```
60 changes: 60 additions & 0 deletions Examples/ManagedInstances/Sources/BackgroundTasks/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright SwiftAWSLambdaRuntime project authors
// Copyright (c) Amazon.com, Inc. or its affiliates.
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import AWSLambdaRuntime

#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif

// for a simple struct as this one, the compiler automatically infers Sendable
// With Lambda Managed Instances, your handler struct MUST be Sendable
struct BackgroundProcessingHandler: LambdaWithBackgroundProcessingHandler, Sendable {
struct Input: Decodable {
let message: String
}

struct Greeting: Encodable {
let echoedMessage: String
}

typealias Event = Input
typealias Output = Greeting

func handle(
_ event: Event,
outputWriter: some LambdaResponseWriter<Output>,
context: LambdaContext
) async throws {
// Return result to the Lambda control plane
context.logger.debug("BackgroundProcessingHandler - message received")
try await outputWriter.write(Greeting(echoedMessage: event.message))

// Perform some background work, e.g:
context.logger.debug("BackgroundProcessingHandler - response sent. Performing background tasks.")
try await Task.sleep(for: .seconds(10))

// Exit the function. All asynchronous work has been executed before exiting the scope of this function.
// Follows structured concurrency principles.
context.logger.debug("BackgroundProcessingHandler - Background tasks completed. Returning")
return
}
}

let adapter = LambdaCodableAdapterSendable(handler: BackgroundProcessingHandler())
let runtime = LambdaManagedRuntime(handler: adapter)
try await runtime.run()
41 changes: 41 additions & 0 deletions Examples/ManagedInstances/Sources/HelloJSON/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright SwiftAWSLambdaRuntime project authors
// Copyright (c) Amazon.com, Inc. or its affiliates.
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import AWSLambdaRuntime

// in this example we are receiving and responding with JSON structures

// the data structure to represent the input parameter
struct HelloRequest: Decodable {
let name: String
let age: Int
}

// the data structure to represent the output response
struct HelloResponse: Encodable {
let greetings: String
}

// the Lambda runtime
let runtime = LambdaManagedRuntime {
(event: HelloRequest, context: LambdaContext) in

HelloResponse(
greetings: "Hello \(event.name). You look \(event.age > 30 ? "younger" : "older") than your age."
)
}

// start the loop
try await runtime.run()
69 changes: 69 additions & 0 deletions Examples/ManagedInstances/Sources/Streaming/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright SwiftAWSLambdaRuntime project authors
// Copyright (c) Amazon.com, Inc. or its affiliates.
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import AWSLambdaEvents
import AWSLambdaRuntime
import NIOCore

#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif

// for a simple struct as this one, the compiler automatically infers Sendable
// With Lambda Managed Instances, your handler struct MUST be Sendable
struct SendNumbersWithPause: StreamingLambdaHandler, Sendable {
func handle(
_ event: ByteBuffer,
responseWriter: some LambdaResponseStreamWriter,
context: LambdaContext
) async throws {

// The payload here is a Lambda Function URL request
// Check the body of the Function URL request to extract the business event
let payload = try JSONDecoder().decode(FunctionURLRequest.self, from: Data(event.readableBytesView))
let _ = payload.body

// Send HTTP status code and headers before streaming the response body
try await responseWriter.writeStatusAndHeaders(
StreamingLambdaStatusAndHeadersResponse(
statusCode: 418, // I'm a tea pot
headers: [
"Content-Type": "text/plain",
"x-my-custom-header": "streaming-example",
]
)
)

// Stream numbers with pauses to demonstrate streaming functionality
for i in 1...3 {
// Send partial data
try await responseWriter.write(ByteBuffer(string: "Number: \(i)\n"))

// Perform some long asynchronous work to simulate processing
try await Task.sleep(for: .milliseconds(1000))
}

// Send final message
try await responseWriter.write(ByteBuffer(string: "Streaming complete!\n"))

// All data has been sent. Close off the response stream.
try await responseWriter.finish()
}
}

let runtime = LambdaManagedRuntime(handler: SendNumbersWithPause())
try await runtime.run()
Loading