diff --git a/.github/workflows/examples_matrix.yml b/.github/workflows/examples_matrix.yml index 39ec1d26..15e4e859 100644 --- a/.github/workflows/examples_matrix.yml +++ b/.github/workflows/examples_matrix.yml @@ -18,7 +18,7 @@ on: matrix_linux_swift_container_image: type: string description: "Container image for the matrix job. Defaults to matching latest Swift Ubuntu image." - default: "swift:latest" + default: "swift:amazonlinux2" ## We are cancelling previously triggered workflow runs concurrency: @@ -33,28 +33,49 @@ jobs: fail-fast: false matrix: # This should be passed as an argument in input. Can we pass arrays as argument ? - examples : [ "HelloWorld", "APIGateway" ] + examples : [ "HelloWorld", "APIGateway", "S3_AWSSDK", "S3_Soto" ] # examples: ${{ inputs.examples }} # We are using only one Swift version swift: - image: ${{ inputs.matrix_linux_swift_container_image }} - swift_version: "6.0.1-noble" + swift_version: "6.0.1-amazonlinux2" container: image: ${{ matrix.swift.image }} steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - persist-credentials: false + + # GitHub checkout action has a dep on NodeJS 20 which is not running on Amazonlinux2 + # workaround is to manually checkout the repository + # https://github.com/actions/checkout/issues/1487 + - name: Manually Clone repository and checkout PR + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + # Clone the repository + git clone https://github.com/${{ github.repository }} + cd ${{ github.event.repository.name }} + + # Fetch the pull request + git fetch origin +refs/pull/$PR_NUMBER/merge: + + # Checkout the pull request + git checkout -qf FETCH_HEAD + + # - name: Checkout repository + # uses: actions/checkout@v4 + # with: + # persist-credentials: false + - name: Mark the workspace as safe - # https://github.com/actions/checkout/issues/766 + working-directory: ${{ github.event.repository.name }} # until we can use action/checkout@v4 + # https://github.com/actions/checkout/issues/766 run: git config --global --add safe.directory ${GITHUB_WORKSPACE} + - name: Run matrix job + working-directory: ${{ github.event.repository.name }} # until we can use action/checkout@v4 env: SWIFT_VERSION: ${{ matrix.swift.swift_version }} COMMAND: ${{ inputs.matrix_linux_command }} EXAMPLE: ${{ matrix.examples }} run: | - apt-get -qq update && apt-get -qq -y install curl ./scripts/integration_tests.sh \ No newline at end of file diff --git a/Examples/APIGateway/Package.swift b/Examples/APIGateway/Package.swift index a2df5b73..ebaac99a 100644 --- a/Examples/APIGateway/Package.swift +++ b/Examples/APIGateway/Package.swift @@ -3,7 +3,6 @@ import PackageDescription // needed for CI to test the local version of the library -import class Foundation.ProcessInfo import struct Foundation.URL #if os(macOS) @@ -16,17 +15,16 @@ let package = Package( name: "swift-aws-lambda-runtime-example", platforms: platforms, products: [ - .executable(name: "APIGAtewayLambda", targets: ["APIGAtewayLambda"]) + .executable(name: "APIGatewayLambda", targets: ["APIGatewayLambda"]) ], dependencies: [ - // dependency on swift-aws-lambda-runtime is added dynamically below - // .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main") - - .package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", branch: "main") + // during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below + .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main"), + .package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", branch: "main"), ], targets: [ .executableTarget( - name: "APIGAtewayLambda", + name: "APIGatewayLambda", dependencies: [ .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"), .product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"), @@ -36,20 +34,29 @@ let package = Package( ] ) -if let localDepsPath = ProcessInfo.processInfo.environment["LAMBDA_USE_LOCAL_DEPS"], +if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"], localDepsPath != "", let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]), - let _ = v.isDirectory + v.isDirectory == true { + // when we use the local runtime as deps, let's remove the dependency added above + let indexToRemove = package.dependencies.firstIndex { dependency in + if case .sourceControl( + name: _, + location: "https://github.com/swift-server/swift-aws-lambda-runtime.git", + requirement: _ + ) = dependency.kind { + return true + } + return false + } + if let indexToRemove { + package.dependencies.remove(at: indexToRemove) + } + + // then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..) print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)") package.dependencies += [ .package(name: "swift-aws-lambda-runtime", path: localDepsPath) ] - -} else { - print("[INFO] LAMBDA_USE_LOCAL_DEPS is not pointing to your local swift-aws-lambda-runtime code") - print("[INFO] This project will compile against the main branch of the Lambda Runtime on GitHub") - package.dependencies += [ - .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main") - ] } diff --git a/Examples/APIGateway/README.md b/Examples/APIGateway/README.md index f534f9ba..be4c8b8c 100644 --- a/Examples/APIGateway/README.md +++ b/Examples/APIGateway/README.md @@ -26,7 +26,7 @@ swift package archive --allow-network-access docker ``` If there is no error, there is a ZIP file ready to deploy. -The ZIP file is located at `.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/MyLambda/MyLambda.zip` +The ZIP file is located at `.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/APIGatewayLambda/APIGatewayLambda.zip` ## Deploy @@ -40,9 +40,9 @@ To actually deploy your Lambda function and create the infrastructure, type the ```bash sam deploy \ -----resolve-s3 \ +--resolve-s3 \ --template-file template.yaml \ ---stack-name MyLambda \ +--stack-name APIGatewayLambda \ --capabilities CAPABILITY_IAM ``` @@ -53,8 +53,8 @@ The output is similar to this one. ----------------------------------------------------------------------------------------------------------------------------- Outputs ----------------------------------------------------------------------------------------------------------------------------- -Key APIGAtewayEndpoint -Description API Gateway endpoint UR" +Key APIGatewayEndpoint +Description API Gateway endpoint URL" Value https://a5q74es3k2.execute-api.us-east-1.amazonaws.com ----------------------------------------------------------------------------------------------------------------------------- ``` diff --git a/Examples/APIGateway/template.yaml b/Examples/APIGateway/template.yaml index 700c09b0..99a00da9 100644 --- a/Examples/APIGateway/template.yaml +++ b/Examples/APIGateway/template.yaml @@ -4,12 +4,12 @@ Description: SAM Template for QuoteService Resources: # Lambda function - APIGAtewayLambda: + APIGatewayLambda: Type: AWS::Serverless::Function Properties: - CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/APIGAtewayLambda/APIGAtewayLambda.zip + CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/APIGatewayLambda/APIGatewayLambda.zip Timeout: 60 - Handler: swift.bootstrap + Handler: swift.bootstrap # ignored by the Swift runtime Runtime: provided.al2 MemorySize: 512 Architectures: @@ -19,13 +19,13 @@ Resources: # by default, AWS Lambda runtime produces no log # use `LOG_LEVEL: debug` for for lifecycle and event handling information # use `LOG_LEVEL: trace` for detailed input event information - LOG_LEVEL: trace + LOG_LEVEL: debug Events: HttpApiEvent: Type: HttpApi Outputs: # print API Gateway endpoint - APIGAtewayEndpoint: + APIGatewayEndpoint: Description: API Gateway endpoint UR" Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" diff --git a/Examples/HelloWorld/Package.swift b/Examples/HelloWorld/Package.swift index 6468fb88..adb4b1f9 100644 --- a/Examples/HelloWorld/Package.swift +++ b/Examples/HelloWorld/Package.swift @@ -3,7 +3,6 @@ import PackageDescription // needed for CI to test the local version of the library -import class Foundation.ProcessInfo import struct Foundation.URL #if os(macOS) @@ -19,8 +18,8 @@ let package = Package( .executable(name: "MyLambda", targets: ["MyLambda"]) ], dependencies: [ - // dependency on swift-aws-lambda-runtime is added dynamically below - // .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main") + // during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below + .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main") ], targets: [ .executableTarget( @@ -33,20 +32,29 @@ let package = Package( ] ) -if let localDepsPath = ProcessInfo.processInfo.environment["LAMBDA_USE_LOCAL_DEPS"], +if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"], localDepsPath != "", let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]), - let _ = v.isDirectory + v.isDirectory == true { + // when we use the local runtime as deps, let's remove the dependency added above + let indexToRemove = package.dependencies.firstIndex { dependency in + if case .sourceControl( + name: _, + location: "https://github.com/swift-server/swift-aws-lambda-runtime.git", + requirement: _ + ) = dependency.kind { + return true + } + return false + } + if let indexToRemove { + package.dependencies.remove(at: indexToRemove) + } + + // then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..) print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)") package.dependencies += [ .package(name: "swift-aws-lambda-runtime", path: localDepsPath) ] - -} else { - print("[INFO] LAMBDA_USE_LOCAL_DEPS is not pointing to your local swift-aws-lambda-runtime code") - print("[INFO] This project will compile against the main branch of the Lambda Runtime on GitHub") - package.dependencies += [ - .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main") - ] } diff --git a/Examples/S3_AWSSDK/.gitignore b/Examples/S3_AWSSDK/.gitignore new file mode 100644 index 00000000..70799e05 --- /dev/null +++ b/Examples/S3_AWSSDK/.gitignore @@ -0,0 +1,12 @@ +.DS_Store +.aws-sam/ +.build +samtemplate.toml +*/build/* +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc \ No newline at end of file diff --git a/Examples/S3_AWSSDK/Package.swift b/Examples/S3_AWSSDK/Package.swift new file mode 100644 index 00000000..5417fbdd --- /dev/null +++ b/Examples/S3_AWSSDK/Package.swift @@ -0,0 +1,63 @@ +// swift-tools-version: 6.0 + +import PackageDescription + +// needed for CI to test the local version of the library +import struct Foundation.URL + +#if os(macOS) +let platforms: [PackageDescription.SupportedPlatform]? = [.macOS(.v15)] +#else +let platforms: [PackageDescription.SupportedPlatform]? = nil +#endif + +let package = Package( + name: "AWSSDKExample", + platforms: platforms, + products: [ + .executable(name: "AWSSDKExample", targets: ["AWSSDKExample"]) + ], + dependencies: [ + // during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below + .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main"), + .package(url: "https://github.com/swift-server/swift-aws-lambda-events", branch: "main"), + .package(url: "https://github.com/awslabs/aws-sdk-swift", from: "1.0.0"), + ], + targets: [ + .executableTarget( + name: "AWSSDKExample", + dependencies: [ + .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"), + .product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"), + .product(name: "AWSS3", package: "aws-sdk-swift"), + ] + ) + ] +) + +if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"], + localDepsPath != "", + let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]), + v.isDirectory == true +{ + // when we use the local runtime as deps, let's remove the dependency added above + let indexToRemove = package.dependencies.firstIndex { dependency in + if case .sourceControl( + name: _, + location: "https://github.com/swift-server/swift-aws-lambda-runtime.git", + requirement: _ + ) = dependency.kind { + return true + } + return false + } + if let indexToRemove { + package.dependencies.remove(at: indexToRemove) + } + + // then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..) + print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)") + package.dependencies += [ + .package(name: "swift-aws-lambda-runtime", path: localDepsPath) + ] +} diff --git a/Examples/S3_AWSSDK/README.md b/Examples/S3_AWSSDK/README.md new file mode 100644 index 00000000..58a7b83d --- /dev/null +++ b/Examples/S3_AWSSDK/README.md @@ -0,0 +1,89 @@ +# List Amazon S3 Buckets with the AWS SDK for Swift + +This is a simple example of an AWS Lambda function that uses the [AWS SDK for Swift](https://github.com/awslabs/aws-sdk-swift) to read data from Amazon S3. + +## Code + +The Lambda function reads all bucket names from your AWS account and returns them as a String. + +The code creates a `LambdaRuntime` struct. In it's simplest form, the initializer takes a function as argument. The function is the handler that will be invoked when the API Gateway receives an HTTP request. + +The handler is `(event: APIGatewayV2Request, context: LambdaContext) -> APIGatewayV2Response`. The function takes two arguments: +- the event argument is a `APIGatewayV2Request`. It is the parameter passed by the API Gateway. It contains all data passed in the HTTP request and some meta data. +- the context argument is a `Lambda Context`. It is a description of the runtime context. + +The function must return a `APIGatewayV2Response`. + +`APIGatewayV2Request` and `APIGatewayV2Response` are defined in the [Swift AWS Lambda Events](https://github.com/swift-server/swift-aws-lambda-events) library. + +The handler creates an S3 client and `ListBucketsInput` object. It passes the input object to the client and receives an output response. +It then extracts the list of bucket names from the output and creates a `\n`-separated list of names, as a `String` + +## Build & Package + +To build the package, type the following commands. + +```bash +swift build +swift package archive --allow-network-access docker +``` + +If there is no error, there is a ZIP file ready to deploy. +The ZIP file is located at `.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/AWSSDKExample/AWSSDKExample.zip` + +## Deploy + +The deployment must include the Lambda function and an API Gateway. We use the [Serverless Application Model (SAM)](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) to deploy the infrastructure. + +**Prerequisites** : Install the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) + +The example directory contains a file named `template.yaml` that describes the deployment. + +To actually deploy your Lambda function and create the infrastructure, type the following `sam` command. + +```bash +sam deploy \ +--resolve-s3 \ +--template-file template.yaml \ +--stack-name AWSSDKExample \ +--capabilities CAPABILITY_IAM +``` + +At the end of the deployment, the script lists the API Gateway endpoint. +The output is similar to this one. + +``` +----------------------------------------------------------------------------------------------------------------------------- +Outputs +----------------------------------------------------------------------------------------------------------------------------- +Key APIGatewayEndpoint +Description API Gateway endpoint URL" +Value https://a5q74es3k2.execute-api.us-east-1.amazonaws.com +----------------------------------------------------------------------------------------------------------------------------- +``` + +## Invoke your Lambda function + +To invoke the Lambda function, use this `curl` command line. + +```bash +curl https://a5q74es3k2.execute-api.us-east-1.amazonaws.com +``` + +Be sure to replace the URL with the API Gateway endpoint returned in the previous step. + +This should print text similar to + +```bash +my_bucket_1 +my_bucket_2 +... +``` + +## Delete the infrastructure + +When done testing, you can delete the infrastructure with this command. + +```bash +sam delete +``` \ No newline at end of file diff --git a/Examples/S3_AWSSDK/Sources/main.swift b/Examples/S3_AWSSDK/Sources/main.swift new file mode 100644 index 00000000..6665893c --- /dev/null +++ b/Examples/S3_AWSSDK/Sources/main.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// 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 +@preconcurrency import AWSS3 + +let client = try await S3Client() + +let runtime = LambdaRuntime { + (event: APIGatewayV2Request, context: LambdaContext) async throws -> APIGatewayV2Response in + + var response: APIGatewayV2Response + do { + // read the list of buckets + context.logger.debug("Reading list of buckets") + let output = try await client.listBuckets(input: ListBucketsInput()) + let bucketList = output.buckets?.compactMap { $0.name } + response = APIGatewayV2Response(statusCode: .ok, body: bucketList?.joined(separator: "\n")) + } catch { + context.logger.error("\(error)") + response = APIGatewayV2Response(statusCode: .internalServerError, body: "[ERROR] \(error)") + } + return response +} + +try await runtime.run() diff --git a/Examples/S3_AWSSDK/template.yaml b/Examples/S3_AWSSDK/template.yaml new file mode 100644 index 00000000..b1e02416 --- /dev/null +++ b/Examples/S3_AWSSDK/template.yaml @@ -0,0 +1,43 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: SAM Template for AWS SDK Example + +Resources: + # Lambda function + AWSSDKExample: + Type: AWS::Serverless::Function + Properties: + CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/AWSSDKExample/AWSSDKExample.zip + Timeout: 60 + Handler: swift.bootstrap # ignored by the Swift runtime + Runtime: provided.al2 + MemorySize: 512 + Architectures: + - arm64 + Environment: + Variables: + # by default, AWS Lambda runtime produces no log + # use `LOG_LEVEL: debug` for for lifecycle and event handling information + # use `LOG_LEVEL: trace` for detailed input event information + LOG_LEVEL: debug + + # Handles all methods of the REST API + Events: + Api: + Type: HttpApi + + # Add an IAM policy to this function. + # It grants the function permissions to read the list of buckets in your account. + Policies: + - Statement: + - Sid: ListAllS3BucketsInYourAccount + Effect: Allow + Action: + - s3:ListAllMyBuckets + Resource: '*' + +# print API endpoint +Outputs: + SwiftAPIEndpoint: + Description: "API Gateway endpoint URL for your application" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" diff --git a/Examples/S3_Soto/.gitignore b/Examples/S3_Soto/.gitignore new file mode 100644 index 00000000..70799e05 --- /dev/null +++ b/Examples/S3_Soto/.gitignore @@ -0,0 +1,12 @@ +.DS_Store +.aws-sam/ +.build +samtemplate.toml +*/build/* +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc \ No newline at end of file diff --git a/Examples/S3_Soto/Package.swift b/Examples/S3_Soto/Package.swift new file mode 100644 index 00000000..4b4eead1 --- /dev/null +++ b/Examples/S3_Soto/Package.swift @@ -0,0 +1,64 @@ +// swift-tools-version: 6.0 + +import PackageDescription + +// needed for CI to test the local version of the library +import struct Foundation.URL + +#if os(macOS) +let platforms: [PackageDescription.SupportedPlatform]? = [.macOS(.v15)] +#else +let platforms: [PackageDescription.SupportedPlatform]? = nil +#endif + +let package = Package( + name: "SotoExample", + platforms: platforms, + products: [ + .executable(name: "SotoExample", targets: ["SotoExample"]) + ], + dependencies: [ + .package(url: "https://github.com/soto-project/soto.git", from: "7.0.0"), + + // during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below + .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main"), + .package(url: "https://github.com/swift-server/swift-aws-lambda-events", branch: "main"), + ], + targets: [ + .executableTarget( + name: "SotoExample", + dependencies: [ + .product(name: "SotoS3", package: "soto"), + .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"), + .product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"), + ] + ) + ] +) + +if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"], + localDepsPath != "", + let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]), + v.isDirectory == true +{ + // when we use the local runtime as deps, let's remove the dependency added above + let indexToRemove = package.dependencies.firstIndex { dependency in + if case .sourceControl( + name: _, + location: "https://github.com/swift-server/swift-aws-lambda-runtime.git", + requirement: _ + ) = dependency.kind { + return true + } + return false + } + if let indexToRemove { + package.dependencies.remove(at: indexToRemove) + } + + // then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..) + print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)") + package.dependencies += [ + .package(name: "swift-aws-lambda-runtime", path: localDepsPath) + ] +} diff --git a/Examples/S3_Soto/README.md b/Examples/S3_Soto/README.md new file mode 100644 index 00000000..c9c0e4a1 --- /dev/null +++ b/Examples/S3_Soto/README.md @@ -0,0 +1,89 @@ +# List Amazon S3 Buckets with Soto + +This is a simple example of an AWS Lambda function that uses the [Soto SDK for AWS](https://github.com/soto-project/soto) to read data from Amazon S3. + +## Code + +The Lambda function reads all bucket names from your AWS account and returns them as a String. + +The code creates a `LambdaRuntime` struct. In it's simplest form, the initializer takes a function as argument. The function is the handler that will be invoked when the API Gateway receives an HTTP request. + +The handler is `(event: APIGatewayV2Request, context: LambdaContext) -> APIGatewayV2Response`. The function takes two arguments: +- the event argument is a `APIGatewayV2Request`. It is the parameter passed by the API Gateway. It contains all data passed in the HTTP request and some meta data. +- the context argument is a `Lambda Context`. It is a description of the runtime context. + +The function must return a `APIGatewayV2Response`. + +`APIGatewayV2Request` and `APIGatewayV2Response` are defined in the [Swift AWS Lambda Events](https://github.com/swift-server/swift-aws-lambda-events) library. + +The handler creates two clients : an AWS client that manages the communication with AWS API and and the S3 client that expose the S3 API. Then, the handler calls `listBuckets()` on the S3 client and receives an output response. +Finally, the handler extracts the list of bucket names from the output to create a `\n`-separated list of names, as a `String`. + +## Build & Package + +To build the package, type the following command. + +```bash +swift build +swift package archive --allow-network-access docker +``` + +If there is no error, there is a ZIP file ready to deploy. +The ZIP file is located at `.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/SotoExample/SotoExample.zip` + +## Deploy + +The deployment must include the Lambda function and an API Gateway. We use the [Serverless Application Model (SAM)](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) to deploy the infrastructure. + +**Prerequisites** : Install the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) + +The example directory contains a file named `template.yaml` that describes the deployment. + +To actually deploy your Lambda function and create the infrastructure, type the following `sam` command. + +```bash +sam deploy \ +--resolve-s3 \ +--template-file template.yaml \ +--stack-name SotoExample \ +--capabilities CAPABILITY_IAM +``` + +At the end of the deployment, the script lists the API Gateway endpoint. +The output is similar to this one. + +``` +----------------------------------------------------------------------------------------------------------------------------- +Outputs +----------------------------------------------------------------------------------------------------------------------------- +Key APIGatewayEndpoint +Description API Gateway endpoint URL" +Value https://a5q74es3k2.execute-api.us-east-1.amazonaws.com +----------------------------------------------------------------------------------------------------------------------------- +``` + +## Invoke your Lambda function + +To invoke the Lambda function, use this `curl` command line. + +```bash +curl https://a5q74es3k2.execute-api.us-east-1.amazonaws.com +``` + +Be sure to replace the URL with the API Gateway endpoint returned in the previous step. + +This should print text similar to + +```bash +my_bucket_1 +my_bucket_2 +... +``` + +## Delete the infrastructure + +When done testing, you can delete the infrastructure with this command. + +```bash +sam delete +``` \ No newline at end of file diff --git a/Examples/S3_Soto/Sources/main.swift b/Examples/S3_Soto/Sources/main.swift new file mode 100644 index 00000000..caa70116 --- /dev/null +++ b/Examples/S3_Soto/Sources/main.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// 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 SotoS3 + +let client = AWSClient() +let s3 = S3(client: client, region: .useast1) + +func handler(event: APIGatewayV2Request, context: LambdaContext) async throws -> APIGatewayV2Response { + + var response: APIGatewayV2Response + do { + context.logger.debug("Reading list of buckets") + + // read the list of buckets + let bucketResponse = try await s3.listBuckets() + let bucketList = bucketResponse.buckets?.compactMap { $0.name } + response = APIGatewayV2Response(statusCode: .ok, body: bucketList?.joined(separator: "\n")) + } catch { + context.logger.error("\(error)") + response = APIGatewayV2Response(statusCode: .internalServerError, body: "[ERROR] \(error)") + } + return response +} + +let runtime = LambdaRuntime.init(body: handler) + +try await runtime.run() +try await client.shutdown() diff --git a/Examples/S3_Soto/template.yaml b/Examples/S3_Soto/template.yaml new file mode 100644 index 00000000..50093169 --- /dev/null +++ b/Examples/S3_Soto/template.yaml @@ -0,0 +1,44 @@ + +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: SAM Template for AWS SDK Example + +Resources: + # Lambda function + SotoExample: + Type: AWS::Serverless::Function + Properties: + CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/SotoExample/SotoExample.zip + Timeout: 60 + Handler: swift.bootstrap # ignored by the Swift runtime + Runtime: provided.al2 + MemorySize: 512 + Architectures: + - arm64 + Environment: + Variables: + # by default, AWS Lambda runtime produces no log + # use `LOG_LEVEL: debug` for for lifecycle and event handling information + # use `LOG_LEVEL: trace` for detailed input event information + LOG_LEVEL: debug + + # Handles all methods of the REST API + Events: + Api: + Type: HttpApi + + # Add an IAM policy to this function. + # It grants the function permissions to read the list of buckets in your account. + Policies: + - Statement: + - Sid: ListAllS3BucketsInYourAccount + Effect: Allow + Action: + - s3:ListAllMyBuckets + Resource: '*' + +# print API endpoint +Outputs: + SwiftAPIEndpoint: + Description: "API Gateway endpoint URL for your application" + Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com"