/swift-lambda-runtime

⚠️ Deprecated AWS Lambda Runtime - please use https://github.com/swift-server/swift-aws-lambda-runtime instead

Primary LanguageSwiftApache License 2.0Apache-2.0

swift-lambda-runtime

⚠️ This project is unmaintained legacy code, that never reached a version 1.0. It has been obsoleted by the swift-server/swift-aws-lambda-runtime which contains a faster, more optimized AWS Lambda Runtime with a better API that should satisfy more needs.

Swift 5.2 github-actions codecov

An AWS Lambda Swift runtime on top of SwiftNIO with some ready-to-use AWS Events. It is intended to be used with the Swift on Amazon Linux project which ensures that Swift executables can be run on Amazon Linux.

An APIGateway Lambda looks like this:

import LambdaRuntime
import NIO

let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer { try! group.syncShutdownGracefully() }

struct Input: Codable {
  let name: String
}

struct Greeting: Codable {
  let greeting: String
}

let handler = APIGateway.handler() { (request, ctx) in
  do {
    let payload = try request.decodeBody(Input.self)

    let response = try APIGateway.Response(
      statusCode: .ok,
      payload: Greeting(greeting: "Hello \(payload.name)"))
  
    return ctx.eventLoop.makeSucceededFuture(response)
  }
  catch {
    return ctx.eventLoop.makeFailedFuture(error)
  }
}

let runtime = try Runtime.createRuntime(eventLoopGroup: group, handler: handler)
defer { try! runtime.syncShutdown() }
try runtime.start().wait()

If you want to run your Vapor app on Lambda behind an APIGateway please checkout vapor-lambda-runtime, which builds on top of this package.

Status

  • Runs natively on Amazon Linux and links against system libraries. Uses the Lambda Layer created by the amazonlinux-swift project.
  • Built on top of Swift-NIO
  • Integration with Swift Logging
  • Ready-to-use AWS Events structs to get started as fast as possible. Currently implemented: Application Load Balancer, APIGateway, Cloudwatch Scheduled Events, DynamoDB Streams, S3, SNS and SQS Messages. More coming soon.
  • Tested integration with aws-swift-sdk
  • Two examples to get you up and running as fast as possible (including an API-Gateway Todo-List)
  • Unit and end-to-end tests
  • CI workflow with GitHub Actions

Alternatives: There is another project to run Swift within AWS-Lambda: Swift-Sprinter.

Create and run your first Swift Lambda

This should help you to get started with Swift on AWS Lambda. The focus is primarily on the AWS console, since it is the easiest way to begin with. Of course you can use the aws-cli, sam-cli, the serverless-framework, cloudformation or whatever tooling you prefer at every step of your way. I even encourage you to do so in a production environment. Noone likes clicky architectures. 🤯 If you are looking for an example, check out the sam-template in the TodoBackend example.

Note: The following instructions were recorded on 19.12.2019 and the GUI may have changed since then. Feel free to start an issue if you see a different one.

The Swift version used here is 5.2.1. You can look up available versions of Swift on Amazonlinux here. You may want to use a later version if that works for you!

Step 1: Develop your lambda

Create a new Swift Package Manager project. For simplicity reasons we will focus solely on squaring numbers with our Lambda function.

$ mkdir SquareNumbers
$ cd SquareNumbers
$ swift package init --type executable

The easiest way to go forward from here is to drag the newly created Package.swift onto Xcode to open the Swift package in Xcode.

Next, we will need to include the LambdaRuntime and SwiftNIO as dependencies. For that open the Package.swift and modify it so that it looks like this:

// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
  name: "SquareNumber",
  dependencies: [
    .package(url: "https://github.com/fabianfett/swift-lambda-runtime.git", .upToNextMajor(from: "0.6.0")),
    .package(url: "https://github.com/apple/swift-nio", .upToNextMajor(from: "2.13.0")),
  ],
  targets: [
    .target(
      name: "SquareNumber",
      dependencies: [
        .product(name: "LambdaRuntime", package: "swift-lambda-runtime"),
        .product(name: "NIO", package: "swift-nio"),
      ]
    ),
  ]
)

Then open your main.swift and create your function. As mentioned earlier, in this example we just want to square numbers although your function can do whatever you want.

import LambdaRuntime
import NIO

struct Input: Codable {
  let number: Double
}

struct Output: Codable {
  let result: Double
}

func squareNumber(input: Input, context: Context) -> EventLoopFuture<Output> {
  let squaredNumber = input.number * input.number
  return context.eventLoop.makeSucceededFuture(Output(result: squaredNumber))
}

let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer { try! group.syncShutdownGracefully() }

do {
  let runtime = try Runtime.createRuntime(
    eventLoopGroup: group, 
    handler: Runtime.codable(squareNumber))

  defer { try! runtime.syncShutdown() }
  
  try runtime.start().wait()
}
catch {
  print("\(error)")
}

Step 3: Built your lambda

Your lambda needs to be built for the Amazon Linux environment. For that we use Docker to compile the Lambda. Please be aware that you need to use the same Swift version for compiling your lambda as you will use for running it. ABI Stability is not a thing on Linux.

For this we will first need to build a development Docker image in order to compile your code on Linux. Create a Docker file and include the following code:

ARG SWIFT_VERSION=5.2.1
FROM fabianfett/amazonlinux-swift:$SWIFT_VERSION-amazonlinux2

# needed to do again after FROM due to docker limitation
ARG SWIFT_VERSION

RUN yum -y update && \
  yum -y install zlib-devel kernel-devel gcc-c++ openssl-devel

To create your Docker image run:

docker build --build-arg SWIFT_VERSION=5.2.1 -t lambda-swift-dev:5.2.1 .

Now we can compile our lambda with the new image.

# build your lambda in the linux environment 
$ docker run --rm --volume "$(pwd)/:/src" --workdir "/src/" lambda-swift-dev:5.2.1 swift build -c release

This will create a SquareNumber executable in your ./build/release folder. Let's grab the executable and rename it to bootstrap.

# copy your executable to your local folder and rename it to bootstrap
$ cp .build/release/$(EXAMPLE_EXECUTABLE) ./bootstrap

Last: We need to zip the bootstrap before uploading to AWS.

# zip your bootstrap
$ zip -j lambda.zip ./bootstrap

Step 4: Create your lambda on AWS

Open your AWS Console and navigate to Lambda. Select "Functions" in the side navigation and click on "Create function" in the upper right corner. Give your function a name. I'll choose "SquareNumbers" and select the runtime "Provide your own bootstrap".

You'll see a screen that looks like this.

Create your function

First we need to select our Swift runtime. We do so by clicking "Layers" below the function name in the center of the screen. The lower part of the screen changes and we can see an "Add Layer" button in the center. Let's click that button. On the next screen we need to select "Provide a layer version ARN" and there we enter the ARN that fits the Swift version that we've used to compile. For Swift 5.2.1 this is arn:aws:lambda:<region>:426836788079:layer:Swift:12. Do not forget to replace <region> with the AWS region identifier you operate in. Next we click "Add".

Add the Swift layer to your Function

Now you should see a layer below our function. Next we click on the function name. You should see the section "Function Code" in the lower part of the screen. Select "Upload a zip file" in the "Code entry type". Click on "Upload" and select your lambda.zip. In the "Handler" field you can fill in whatever you want (at least one character), since this field is not used by our runtime‌. Next click "Save".

Upload your lambda code

Step 5: Invoke your lambda

The only thing left is to invoke your lambda. Select "Test" (in the upper right corner) and change your test payload to whatever json you want to supply to your function. Since I want numbers squared mine is as follows:

{
  "number": 3
}

Since AWS wants to reuse your event for tests over and over again, you need to give your test event a name. Mine is "Number3". Click "Save" and you can click "Test" again, and this time your lambda will be execute. If everything went well, you should see a screen like this:

The lambda invocation is a success!

What's next?

Great! You've made it so far. In my point of view, you should now familiarize yourself with some tooling around AWS Lambda.

Lambda deployment/testing tooling

It may be serverless or aws-sam, as noone wants or should build Lambda services by just clicking around in the AWS Console. The TodoList example is setup with aws-sam. If you need more help about how to get started with aws-sam, please reach out by opening a GitHub issue.

aws-sdk

There are two projects providing you an API to interact with AWS resources.

  • aws-sdk-swift A community driven effort. The TodoList example uses this sdk to query DynamoDB.
  • smoke-aws An Amazon (not AWS 😉) driven effort. Please be aware that this sdk does not return EventLoopFutures. Therefore integrating may be a little tricky. Not tested.

Logging

If you want to log something inside your lambda you can use the logger property on the Context class. The logger is based on swift-log and should for this reason be compatible with lot's of other server-side Swift projects. By default the RequestId is exposed as metadata. An example can be found here.

EventLoop

The EventLoop, on which your function is executed, can be accessed via the eventLoop property on the Context class. An example can be found here.

Contributing

Please feel welcome and encouraged to contribute to swift-lambda-runtime. The current version of swift-lambda-runtime has a long way to go before being ready for production use and help is always welcome.

If you've found a bug, have a suggestion or need help getting started, please open an Issue or a PR. If you use this package, I'd be grateful for sharing your experience.

Focus areas for the time being:

  • Implementing all aws lambda resource events. Those should be quite easy for a first PR. Just grab one and go!
  • Fixing all the bugs and performance bottlenecks of the first release.

Credits

  • Toni Suter started the project to bring Swift to AWS Lambda.
  • grpc-swift influenced how the Logger and EventLoop are exposed to the user using the Context.