Skip to content
Merged
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
1 change: 1 addition & 0 deletions .licenseignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ Package.resolved
*.yml
*.json
*.gif
Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# image used to compile your Swift code
FROM public.ecr.aws/docker/library/swift:6.1-amazonlinux2
FROM public.ecr.aws/docker/library/swift:6.2-amazonlinux2
RUN yum -y install git jq tar zip openssl-devel
63 changes: 63 additions & 0 deletions Examples/quoteapi-alb/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
### Add functions here and link them to builder-bot format MUST BE "build-FunctionResourceName in template.yaml"

build-QuoteServiceALB: builder-bot

# Helper commands
build:
sam build

deploy:
sam deploy

logs:
sam logs --stack-name QuoteServiceALB

tail:
sam logs --stack-name QuoteServiceALB --tail

local:
swift run QuoteServiceALB

local-invoke:
sam local invoke QuoteServiceALB --event events/GetQuote.json

###################### No Change required below this line ##########################

builder-bot:
$(eval $@PRODUCT = $(subst build-,,$(MAKECMDGOALS)))
$(eval $@BUILD_DIR = $(PWD)/.aws-sam/build-swift)
$(eval $@STAGE = $($@BUILD_DIR)/lambda)
$(eval $@ARTIFACTS_DIR = $(PWD)/.aws-sam/build/$($@PRODUCT))

## Building from swift-openapi-lambda in a local directory (not from Github)
## 2. Change `Package.swift` dependency to path: "../.."

## 3. add /../.. to BUILD_SRC
$(eval $@BUILD_SRC = $(PWD)/../..)
## $(eval $@BUILD_SRC = $(PWD))

## 4. add `cd Examples/quoteapi-alb &&` to the docker BUILD_CMD
$(eval $@BUILD_CMD = "cd Examples/quoteapi-alb && swift build --static-swift-stdlib --product $($@PRODUCT) -c release --build-path /build-target")
## $(eval $@BUILD_CMD = "swift build --static-swift-stdlib --product $($@PRODUCT) -c release --build-path /build-target")

# build docker image to compile Swift for Linux
docker build -f Dockerfile . -t swift-builder

# prep directories
mkdir -p $($@BUILD_DIR)/lambda $($@ARTIFACTS_DIR)

# compile application inside Docker image using source code from local project folder

docker run --rm -v $($@BUILD_DIR):/build-target -v $($@BUILD_SRC):/build-src -w /build-src swift-builder bash -cl $($@BUILD_CMD)

# create lambda bootstrap file
docker run --rm -v $($@BUILD_DIR):/build-target -v `pwd`:/build-src -w /build-src swift-builder bash -cl "cd /build-target/lambda && ln -s $($@PRODUCT) /bootstrap"

# copy binary to stage
cp $($@BUILD_DIR)/release/$($@PRODUCT) $($@STAGE)/bootstrap

# copy resources to stage (if they exist)
[ -d "$($@BUILD_DIR)/release/$($@PRODUCT)_$($@PRODUCT).resources" ] && cp $($@BUILD_DIR)/release/$($@PRODUCT)_$($@PRODUCT).resources/* $($@STAGE) || true

# copy app from stage to artifacts dir
cp $($@STAGE)/* $($@ARTIFACTS_DIR)
43 changes: 43 additions & 0 deletions Examples/quoteapi-alb/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "QuoteServiceALB",
platforms: [
.macOS(.v15)
],
products: [
.executable(name: "QuoteServiceALB", targets: ["QuoteServiceALB"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-openapi-generator.git", from: "1.4.0"),
.package(url: "https://github.com/apple/swift-openapi-runtime.git", from: "1.8.2"),
.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.2.0"),
.package(name: "swift-openapi-lambda", path: "../.."),
],
targets: [
.executableTarget(
name: "QuoteServiceALB",
dependencies: [
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
.product(name: "OpenAPILambda", package: "swift-openapi-lambda"),
],
path: "Sources/QuoteAPI",
resources: [
.copy("openapi.yaml"),
.copy("openapi-generator-config.yaml"),
],
plugins: [
.plugin(
name: "OpenAPIGenerator",
package: "swift-openapi-generator"
)
]
)
]
)
104 changes: 104 additions & 0 deletions Examples/quoteapi-alb/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# QuoteAPI ALB Example

This application illustrates how to deploy a Server-Side Swift workload on AWS using an Application Load Balancer (ALB) with Lambda targets. The workload is a simple REST API that returns stock quotes. Requests to the ALB are forwarded to an AWS Lambda Function written in Swift using the OpenAPI Lambda library.

## Prerequisites

To build this sample application, you need:

- [AWS Account](https://console.aws.amazon.com/)
- [AWS Command Line Interface (AWS CLI)](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) - install the CLI and [configure](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html) it with credentials to your AWS account
- [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) - a command-line tool used to create serverless workloads on AWS
- [Docker Desktop](https://www.docker.com/products/docker-desktop/) - to compile your Swift code for Linux deployment to AWS Lambda

## Build the application

The **sam build** command uses Docker to compile your Swift Lambda function and package it for deployment to AWS.

```bash
sam build
```

On macOS, you might need to run this command if `sam` doesn't see `docker`:
```bash
export DOCKER_HOST=unix://$HOME/.docker/run/docker.sock
```

## Deploy the application

The **sam deploy** command creates the Lambda function, Application Load Balancer, and associated VPC resources in your AWS account.

```bash
sam deploy --guided
```

## Use the API

At the end of the deployment, SAM displays the endpoint of your Application Load Balancer:

```bash
Outputs
----------------------------------------------------------------------------------------
Key QuoteAPILoadBalancerURL
Description Application Load Balancer URL for QuoteAPI
Value http://QuoteAPILoadBalancer-123456789.us-east-1.elb.amazonaws.com/stocks/AAPL
----------------------------------------------------------------------------------------
```

Use cURL or a tool such as [Postman](https://www.postman.com/) to interact with your API. Replace **[your-alb-endpoint]** with the QuoteAPILoadBalancerURL value from the deployment output.

**Invoke the API Endpoint**

```bash
curl http://[your-alb-endpoint]/stocks/AMZN
```

## Test the API Locally

SAM also allows you to execute your Lambda functions locally on your development computer.

**Invoke the Lambda Function Locally**

```bash
sam local invoke QuoteServiceALB --event events/GetQuote.json
```

On macOS, you might need to run this command if `sam` doesn't see `docker`:
```bash
export DOCKER_HOST=unix://$HOME/.docker/run/docker.sock
```

## Architecture

This example demonstrates:

- **Application Load Balancer**: Routes HTTP requests to Lambda functions
- **Lambda Target Group**: Configures the ALB to forward requests to Lambda
- **VPC Setup**: Creates a VPC with public subnets for the ALB
- **Security Groups**: Controls inbound traffic to the ALB
- **OpenAPI Integration**: Uses Swift OpenAPI Lambda library with ALB events

## Cleanup

When finished with your application, use SAM to delete it from your AWS account. Answer **Yes (y)** to all prompts. This will delete all of the application resources created in your AWS account.

```bash
sam delete
```

> **⚠️ Security and Reliability Notice**
>
> This is an example application for demonstration purposes. When deploying such infrastructure in production environments, we strongly encourage you to follow these best practices for improved security and resiliency:
>
> - Enable access logging on Application Load Balancer ([documentation](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html))
> - Ensure that AWS Lambda function is configured for function-level concurrent execution limit ([concurrency documentation](https://docs.aws.amazon.com/lambda/latest/dg/lambda-concurrency.html), [configuration guide](https://docs.aws.amazon.com/lambda/latest/dg/configuration-concurrency.html))
> - Check encryption settings for Lambda environment variables ([documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars-encryption.html))
> - Ensure that AWS Lambda function is configured for a Dead Letter Queue (DLQ) ([documentation](https://docs.aws.amazon.com/lambda/latest/dg/invocation-async-retain-records.html#invocation-dlq))
> - Configure HTTPS/TLS termination on the Application Load Balancer ([documentation](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html))
>
> **Note:** The `openapi.yaml` file in this example is not suited for production. In real-world scenarios, you must:
> 1. Ensure that the global security field has rules defined
> 2. Ensure that security operations is not empty ([OpenAPI Security Specification](https://learn.openapis.org/specification/security.html))
> 3. Follow proper authentication, authorization, input validation, and error handling practices
>
> As per Checkov CKV_OPENAPI_4 and CKV_OPENAPI_5 security checks.
79 changes: 79 additions & 0 deletions Examples/quoteapi-alb/Sources/QuoteAPI/QuoteService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift OpenAPI Lambda open source project
//
// Copyright Swift OpenAPI Lambda project authors
// Copyright (c) 2025 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 Swift OpenAPI Lambda project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation
import Logging
import OpenAPIRuntime
import OpenAPILambda

@main
struct QuoteServiceALBImpl: APIProtocol, OpenAPILambdaALB {

let logger: Logger

func register(transport: OpenAPILambdaTransport) throws {

// OPTIONAL
// you have a chance here to customize the routes, for example
try transport.router.get("/health") { _, _ in
"OK"
}
logger.trace("Available Routes\n\(transport.router)") // print the router tree (for debugging purposes)

// OPTIONAL
// to log all requests and their responses, add a logging middleware
let loggingMiddleware = LoggingMiddleware(logger: logger)

// MANDATORY (middlewares are optional)
try self.registerHandlers(on: transport, middlewares: [loggingMiddleware])
}

static func main() async throws {
let openAPIService = QuoteServiceALBImpl()
try await openAPIService.run()
}

init() {
var logger = Logger(label: "QuoteServiceALB")
logger.logLevel = .trace
self.logger = logger
}

func getQuote(_ input: Operations.getQuote.Input) async throws -> Operations.getQuote.Output {
logger.trace("GetQuote - Started")

let symbol = input.path.symbol

var date: Date = Date()
if let dateString = input.query.date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd"
date = dateFormatter.date(from: dateString) ?? Date()
}

let price = Components.Schemas.quote(
symbol: symbol,
price: Double.random(in: 100..<150).rounded(),
change: Double.random(in: -5..<5).rounded(),
changePercent: Double.random(in: -0.05..<0.05),
volume: Double.random(in: 10000..<100000).rounded(),
timestamp: date
)

logger.trace("GetQuote - Returning")

return .ok(.init(body: .json(price)))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
generate:
- types
- server
54 changes: 54 additions & 0 deletions Examples/quoteapi-alb/Sources/QuoteAPI/openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
openapi: 3.1.0
info:
title: StockQuoteService
version: 1.0.0

components:
schemas:
quote:
type: object
properties:
symbol:
type: string
price:
type: number
change:
type: number
changePercent:
type: number
volume:
type: number
timestamp:
type: string
format: date-time

paths:
/stocks/{symbol}:
get:
summary: Get the latest quote for a stock
operationId: getQuote
parameters:
- name: symbol
in: path
required: true
schema:
type: string
- name: date
in: query
required: false
schema:
type: string
format: date
tags:
- stocks
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/quote'
400:
description: Bad Request
404:
description: Not Found
25 changes: 25 additions & 0 deletions Examples/quoteapi-alb/events/GetQuote.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"requestContext": {
"elb": {
"targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-target/50dc6c495c0c9188"
}
},
"httpMethod": "GET",
"path": "/stocks/AAPL",
"queryStringParameters": {},
"headers": {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"accept-encoding": "gzip, deflate",
"accept-language": "en-US,en;q=0.9",
"connection": "keep-alive",
"host": "lambda-alb-123578498.us-east-1.elb.amazonaws.com",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
"x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476",
"x-forwarded-for": "72.12.164.125",
"x-forwarded-port": "80",
"x-forwarded-proto": "http"
},
"body": "",
"isBase64Encoded": false
}
Loading