Skip to content

Commit b790508

Browse files
committed
Address review
1 parent a565ae4 commit b790508

File tree

5 files changed

+56
-72
lines changed

5 files changed

+56
-72
lines changed

.github/workflows/pull_request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
# We pass the list of examples here, but we can't pass an array as argument
3737
# Instead, we pass a String with a valid JSON array.
3838
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
39-
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'ResourcesPackaging', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'Testing', 'Tutorial' ]"
39+
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'Testing', 'Tutorial' ]"
4040
archive_plugin_examples: "[ 'HelloWorld', 'ResourcesPackaging' ]"
4141
archive_plugin_enabled: true
4242

Examples/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ This directory contains example code for Lambda functions.
2828

2929
- **[HelloWorld](HelloWorld/README.md)**: a simple Lambda function (requires [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)).
3030

31+
- **[S3EventNotifier](S3EventNotifier/README.md)**: a Lambda function that receives object-upload notifications from an [Amazon S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) bucket.
32+
3133
- **[S3_AWSSDK](S3_AWSSDK/README.md)**: a Lambda function that uses the [AWS SDK for Swift](https://docs.aws.amazon.com/sdk-for-swift/latest/developer-guide/getting-started.html) to invoke an [Amazon S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) API (requires [AWS SAM](https://aws.amazon.com/serverless/sam/)).
3234

3335
- **[S3_Soto](S3_Soto/README.md)**: a Lambda function that uses [Soto](https://github.com/soto-project/soto) to invoke an [Amazon S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) API (requires [AWS SAM](https://aws.amazon.com/serverless/sam/)).
@@ -64,4 +66,4 @@ To obtain these keys, you need an AWS account:
6466

6567
4. **(Optional) Generate Temporary Security Credentials**: If you’re using temporary credentials (which are more secure for short-term access), use AWS Security Token Service (STS). You can call the `GetSessionToken` or `AssumeRole` API to generate temporary credentials, including a session token.
6668

67-
With these in hand, you can use AWS SigV4 to securely sign your requests and interact with AWS services from your Swift app.
69+
With these in hand, you can use AWS SigV4 to securely sign your requests and interact with AWS services from your Swift app.

Examples/S3EventNotifier/Package.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,18 @@ import PackageDescription
55
import struct Foundation.URL
66

77
let package = Package(
8-
name: "CSVUploadAPINotificationLambda",
8+
name: "S3EventNotifier",
99
platforms: [.macOS(.v15)],
1010
dependencies: [
1111
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main"),
1212
.package(url: "https://github.com/swift-server/swift-aws-lambda-events", branch: "main"),
13-
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.24.0"),
1413
],
1514
targets: [
1615
.executableTarget(
17-
name: "CSVUploadAPINotificationLambda",
16+
name: "S3EventNotifier",
1817
dependencies: [
1918
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
2019
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
21-
.product(name: "AsyncHTTPClient", package: "async-http-client"),
2220
]
2321
)
2422
]

Examples/S3EventNotifier/README.md

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# S3 Event Notifier
22

3-
This example demonstrates how to create a Lambda that notifies an API of an S3 event in a bucket.
3+
This example demonstrates how to write a Lambda that is invoked by an event originating from Amazon S3, such as a new object being uploaded to a bucket.
44

55
## Code
66

7-
In this example the lambda function receives an `S3Event` object from the `AWSLambdaEvents` library as input object instead of a `APIGatewayV2Request`. The `S3Event` object contains all the information about the S3 event that triggered the lambda, but what we are interested in is the bucket name and the object key, which are inside of a notification `Record`. The object contains an array of records, however since the lambda is triggered by a single event, we can safely assume that there is only one record in the array: the first one. Inside of this record, we can find the bucket name and the object key:
7+
In this example the lambda function receives an `S3Event` object defined in the `AWSLambdaEvents` library as input object. The `S3Event` object contains all the information about the S3 event that triggered the function, but what we are interested in is the bucket name and the object key, which are inside of a notification `Record`. The object contains an array of records, however since the lambda is triggered by a single event, we can safely assume that there is only one record in the array: the first one. Inside of this record, we can find the bucket name and the object key:
88

99
```swift
1010
guard let s3NotificationRecord = event.records.first else {
@@ -17,8 +17,6 @@ let key = s3NotificationRecord.s3.object.key.replacingOccurrences(of: "+", with:
1717

1818
The key is URL encoded, so we replace the `+` with a space.
1919

20-
Once the event is decoded, the lambda sends a POST request to an API endpoint with the bucket name and the object key as parameters. The API URL is set as an environment variable.
21-
2220
## Build & Package
2321

2422
To build & archive the package you can use the following commands:
@@ -36,17 +34,46 @@ To deploy the Lambda function, you can use the `aws` command line:
3634

3735
```bash
3836
aws lambda create-function \
39-
--function-name S3EventNotifier \
40-
--zip-file fileb://.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/S3EventNotifier/S3EventNotifier.zip \
41-
--runtime provided.al2 \
42-
--handler provided \
43-
--architectures arm64 \
44-
--role arn:aws:iam::<YOUR_ACCOUNT_ID>:role/lambda_basic_execution
37+
--function-name S3EventNotifier \
38+
--zip-file fileb://.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/S3EventNotifier/S3EventNotifier.zip \
39+
--runtime provided.al2 \
40+
--handler provided \
41+
--architectures arm64 \
42+
--role arn:aws:iam::<YOUR_ACCOUNT_ID>:role/lambda_basic_execution
4543
```
4644

4745
The `--architectures` flag is only required when you build the binary on an Apple Silicon machine (Apple M1 or more recent). It defaults to `x64`.
4846

49-
Be sure to replace <YOUR_ACCOUNT_ID> with your actual AWS account ID (for example: 012345678901).
47+
Be sure to replace `<YOUR_ACCOUNT_ID>` with your actual AWS account ID (for example: 012345678901).
48+
49+
Besides deploying the lambda function you also need to create the S3 bucket and configure it to send events to the lambda function. You can do this using the following commands:
50+
51+
```bash
52+
aws s3api create-bucket --bucket my-test-bucket --region eu-west-1 --create-bucket-configuration LocationConstraint=eu-west-1
53+
54+
aws lambda add-permission \
55+
--function-name ProcessS3Upload \
56+
--statement-id S3InvokeFunction \
57+
--action lambda:InvokeFunction \
58+
--principal s3.amazonaws.com \
59+
--source-arn arn:aws:s3:::my-test-bucket
60+
61+
aws s3api put-bucket-notification-configuration \
62+
--bucket my-test-bucket \
63+
--notification-configuration '{
64+
"LambdaFunctionConfigurations": [{
65+
"LambdaFunctionArn": "arn:aws:lambda:<REGION>:<YOUR_ACCOUNT_ID>:function:ProcessS3Upload",
66+
"Events": ["s3:ObjectCreated:*"]
67+
}]
68+
}'
69+
70+
aws s3 cp testfile.txt s3://my-test-bucket/
71+
```
72+
73+
This will:
74+
- create a bucket named `my-test-bucket` in the `eu-west-1` region;
75+
- add a permission to the lambda function to be invoked by the bucket;
76+
- configure the bucket to send `s3:ObjectCreated:*` events to the lambda function named `ProcessS3Upload`;
77+
- upload a file named `testfile.txt` to the bucket.
5078

51-
> [!WARNING]
52-
> You will have to set up an S3 bucket and configure it to send events to the lambda function. This is not covered in this example.
79+
Replace `<REGION>` with the region where you deployed the lambda function and `<YOUR_ACCOUNT_ID>` with your actual AWS account ID.

Examples/S3EventNotifier/Sources/main.swift

Lines changed: 10 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the SwiftAWSLambdaRuntime open source project
44
//
5-
// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors
5+
// Copyright (c) 2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors
66
// Licensed under Apache License v2.0
77
//
88
// See LICENSE.txt for license information
@@ -24,63 +24,20 @@ import Foundation
2424

2525
let httpClient = HTTPClient.shared
2626

27-
enum LambdaError: Error {
28-
case noNotificationRecord
29-
case missingEnvVar(name: String)
27+
let runtime = LambdaRuntime { (event: S3Event, context: LambdaContext) async throws in
28+
context.logger.debug("Received S3 event: \(event)")
3029

31-
var description: String {
32-
switch self {
33-
case .noNotificationRecord:
34-
"No notification record in S3 event"
35-
case .missingEnvVar(let name):
36-
"Missing env var named \(name)"
37-
}
30+
guard let s3NotificationRecord = event.records.first else {
31+
context.logger.error("No S3 notification record found in the event")
32+
return
3833
}
39-
}
40-
41-
let runtime = LambdaRuntime { (event: S3Event, context: LambdaContext) async throws -> APIGatewayV2Response in
42-
do {
43-
context.logger.debug("Received S3 event: \(event)")
44-
45-
guard let s3NotificationRecord = event.records.first else {
46-
throw LambdaError.noNotificationRecord
47-
}
48-
49-
let bucket = s3NotificationRecord.s3.bucket.name
50-
let key = s3NotificationRecord.s3.object.key.replacingOccurrences(of: "+", with: " ")
5134

52-
guard let apiURL = ProcessInfo.processInfo.environment["API_URL"] else {
53-
throw LambdaError.missingEnvVar(name: "API_URL")
54-
}
35+
let bucket = s3NotificationRecord.s3.bucket.name
36+
let key = s3NotificationRecord.s3.object.key.replacingOccurrences(of: "+", with: " ")
5537

56-
let body = """
57-
{
58-
"bucket": "\(bucket)",
59-
"key": "\(key)"
60-
}
61-
"""
38+
context.logger.info("Received notification from S3 bucket '\(bucket)' for object with key '\(key)'")
6239

63-
context.logger.debug("Sending request to \(apiURL) with body \(body)")
64-
65-
var request = HTTPClientRequest(url: "\(apiURL)/upload-complete/")
66-
request.method = .POST
67-
request.headers = [
68-
"Content-Type": "application/json"
69-
]
70-
request.body = .bytes(.init(string: body))
71-
72-
let response = try await httpClient.execute(request, timeout: .seconds(30))
73-
return APIGatewayV2Response(
74-
statusCode: .ok,
75-
body: "Lambda terminated successfully. API responded with: Status: \(response.status), Body: \(response.body)"
76-
)
77-
} catch let error as LambdaError {
78-
context.logger.error("\(error.description)")
79-
return APIGatewayV2Response(statusCode: .internalServerError, body: "[ERROR] \(error.description)")
80-
} catch {
81-
context.logger.error("\(error)")
82-
return APIGatewayV2Response(statusCode: .internalServerError, body: "[ERROR] \(error)")
83-
}
40+
// Here you could, for example, notify an API or a messaging service
8441
}
8542

8643
try await runtime.run()

0 commit comments

Comments
 (0)