Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[plugin] add a deployer plugin + SAM example #278

Closed
wants to merge 44 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3af979a
initial commit for deployer plugin + SAM example
sebsto Dec 22, 2022
7c4babb
Remove unused example files
sebsto Dec 23, 2022
4f5cb9c
Simplify API for function developers
sebsto Dec 29, 2022
45733a6
fix comment
sebsto Dec 29, 2022
8fd7487
simplify data structures by removing unecessary enums
sebsto Dec 29, 2022
8ad22e6
simplify API
sebsto Dec 30, 2022
2f623be
add addResource API
sebsto Dec 31, 2022
c3d73a9
add support for passing SQS queue Arn
sebsto Dec 31, 2022
332fdb6
ad unit test
sebsto Dec 31, 2022
00029af
add test target to Package.swift
sebsto Dec 31, 2022
ae41897
simplify code and factorise functions
sebsto Jan 2, 2023
fef8bb2
simplify API : allow to pass Resource to SQSEventSource
sebsto Jan 3, 2023
b3ce4ca
add different type of env variable + refactor resource names
sebsto Jan 3, 2023
639d96d
add more example to create the deployment descriptor
sebsto Jan 3, 2023
fbd0b95
removed unused portion of code
sebsto Jan 3, 2023
11469a7
Merge branch 'swift-server:main' into sebsto/deployerplugin
sebsto Jan 11, 2023
a2a1275
Merge branch 'master' into sebsto/deployerplugin
sebsto Jan 25, 2023
61d9c51
fix an issue that prevent to identify the correct path for Deploy exec
sebsto Jan 25, 2023
e0a94ef
Merge branch 'swift-server:main' into sebsto/deployerplugin
sebsto Jan 30, 2023
e52d196
new version of the deployer plugin. Now it uses a declarative interfa…
sebsto Jan 30, 2023
09385fa
remove old Deployment swift code
sebsto Jan 30, 2023
607e877
add the PR README to the plugin source directory
sebsto Jan 30, 2023
fa6eddb
add linter to SAM validate
sebsto Jan 31, 2023
2d07b14
refactor unit test
sebsto Jan 31, 2023
8a816c5
Factor out common code between the two plugins
sebsto Jan 31, 2023
d09ecbe
remove dependency on URL
sebsto Jan 31, 2023
7180a4a
add help option to the deployer plugin
sebsto Jan 31, 2023
fac6719
refactor platform architecture for unit tests
sebsto Jan 31, 2023
c712083
refactor for better readability
sebsto Jan 31, 2023
f3f4021
function architecture is now a parameter that function developers can…
sebsto Jan 31, 2023
b8cb5b0
add sample architecture entry
sebsto Feb 1, 2023
464fd01
add example tests for the two Lambda functions
sebsto Feb 1, 2023
1d720a2
cleaner fix for --static-swift-stdlib issue on linux
sebsto Feb 1, 2023
ca4d1c2
update README to reflect latest changes
sebsto Feb 1, 2023
971af4f
minor refactor and reformat
sebsto Feb 1, 2023
9031b6a
minor refactor
sebsto Feb 1, 2023
6ed5f15
more code cleanup and documentation
sebsto Feb 1, 2023
a649d27
Merge branch 'swift-server:main' into sebsto/deployerplugin
sebsto Feb 15, 2023
c2d8072
use sam list command to print endpoints and improve error handling w…
sebsto Feb 16, 2023
46b53ed
simplify example code
sebsto Feb 16, 2023
1f054b0
add extra sam commands
sebsto Feb 17, 2023
e4401a4
Update the README file
sebsto Feb 17, 2023
c39b82d
update git ignore
sebsto Feb 17, 2023
366fea3
Merge branch 'swift-server:main' into sebsto/deployerplugin
sebsto Feb 23, 2023
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
xcuserdata
Package.resolved
.serverless
.vscode
71 changes: 71 additions & 0 deletions Examples/SAM/Deploy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import AWSLambdaDeploymentDescriptor

// example of a shared resource
let sharedQueue : Resource = Resource.queue(logicalName: "SharedQueue",
physicalName: "swift-lambda-shared-queue")

// example of common environment variables
let sharedEnvironementVariables = ["LOG_LEVEL":"debug"]

let _ = DeploymentDefinition(

description: "Working SAM template for Swift Lambda function",

functions: [

.function(
// the name of the function
name: "HttpApiLambda",

// the AWS Lambda architecture (defaults to current build platform)
//architecture: .x64,

// the event sources
eventSources: [
// example of a catch all API
// .httpApi(),

// example of an API for a specific path and specific http verb
.httpApi(method: .GET, path: "/test"),
],

// optional environment variables - one variable
//environment: .variable("NAME","VALUE")

// optional environment variables - multiple environment variables at once
environment: .variable([sharedEnvironementVariables, ["NAME2":"VALUE2"]])
),

.function(
name: "SQSLambda",
eventSources: [
// this will reference an existing queue
// .sqs(queue: "arn:aws:sqs:eu-central-1:012345678901:swift-lambda-shared-queue"),

// this will create a new queue resource
.sqs(queue: "swift-lambda-queue-name"),

// this will create a new queue resource
// .sqs(queue: .queue(logicalName: "LambdaQueueResource", physicalName: "swift-lambda-queue-resource"))

// this will reference a queue resource created in this deployment descriptor
// .sqs(queue: sharedQueue)
],
environment: .variable(sharedEnvironementVariables)
)
],

// additional resources
resources: [

// create a SQS queue
.queue(logicalName: "TopLevelQueueResource",
physicalName: "swift-lambda-top-level-queue"),

// create a DynamoDB table
.table(logicalName: "SwiftLambdaTable",
physicalName: "swift-lambda-table",
primaryKeyName: "id",
primaryKeyType: "String")
]
)
52 changes: 52 additions & 0 deletions Examples/SAM/HttpApiLambda/Lambda.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// ===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2021 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 Foundation

@main
struct HttpApiLambda: SimpleLambdaHandler {
init() {}
init(context: LambdaInitializationContext) async throws {
context.logger.info(
"Log Level env var : \(ProcessInfo.processInfo.environment["LOG_LEVEL"] ?? "info" )")
}

// the return value must be either APIGatewayV2Response or any Encodable struct
func handle(_ event: APIGatewayV2Request, context: AWSLambdaRuntimeCore.LambdaContext) async throws -> APIGatewayV2Response {

var header = HTTPHeaders()
do {
context.logger.debug("HTTP API Message received")

header["content-type"] = "application/json"

// echo the request in the response
let data = try JSONEncoder().encode(event)
let response = String(data: data, encoding: .utf8)

// if you want contronl on the status code and headers, return an APIGatewayV2Response
// otherwise, just return any Encodable struct, the runtime will wrap it for you
return APIGatewayV2Response(statusCode: .ok, headers: header, body: response)

} catch {
// should never happen as the decoding was made by the runtime
// when the input event is malformed, this function is not even called
header["content-type"] = "text/plain"
return APIGatewayV2Response(statusCode: .badRequest, headers: header, body: "\(error.localizedDescription)")

}
}
}
40 changes: 40 additions & 0 deletions Examples/SAM/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
build:
swift build

update:
swift package update

test:
swift test

release:
swift build -c release

archive: build
swift package --disable-sandbox archive

deploy: build
swift package --disable-sandbox deploy --configuration debug

nodeploy: build
swift package --disable-sandbox deploy --verbose --nodeploy --configuration debug

localtest:
sam local invoke -t sam.json -e Tests/LambdaTests/data/apiv2.json HttpApiLambda

list:
sam list endpoints -t sam.json --stack-name swift-aws-lambda-runtime-example --output json

clean:
# find . -name .build -exec rm -rf {} \;
# find . -name .swiftpm -exec rm -rf {} \;
# find . -name dist -exec rm -rf {} \;
rm -rf ../../.build
rm -rf .build

test-generate:
/usr/bin/swift \
-L .build/debug \
-I .build/debug \
-lAWSLambdaDeploymentDescriptor \
./Deploy.swift
73 changes: 73 additions & 0 deletions Examples/SAM/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// swift-tools-version:5.7

// ===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2021 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 PackageDescription

let package = Package(
name: "swift-aws-lambda-runtime-example",
platforms: [
.macOS(.v12)
],
products: [
.executable(name: "HttpApiLambda", targets: ["HttpApiLambda"]),
.executable(name: "SQSLambda", targets: ["SQSLambda"]),
],
dependencies: [
// this is the dependency on the swift-aws-lambda-runtime library
// in real-world projects this would say
// .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main"),
.package(name: "swift-aws-lambda-runtime", path: "../.."),
.package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", branch: "main")
],
targets: [
sebsto marked this conversation as resolved.
Show resolved Hide resolved
.executableTarget(
name: "HttpApiLambda",
dependencies: [
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
],
path: "./HttpApiLambda"
),
.executableTarget(
name: "SQSLambda",
dependencies: [
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
] ,
path: "./SQSLambda"
),
.testTarget(
name: "LambdaTests",
dependencies: [
"HttpApiLambda", "SQSLambda",
.product(name: "AWSLambdaTesting", package: "swift-aws-lambda-runtime"),

// at least one target must have this dependency. It ensures the dylib is built
// The dylib is loaded dynamically by the deploy plugin when compiling Deploy.swift.
// The Lambda functions or the tests do not actually depend on it.
// I choose to add the dependency on a test target, because the archive plugin
// links the executable targets with --static-swift-stdlib which cannot include this dynamic library,
// and causes the build to fail
.product(name: "AWSLambdaDeploymentDescriptor", package: "swift-aws-lambda-runtime")
],
// testing data
resources: [
.process("data/apiv2.json"),
.process("data/sqs.json")
]
)
]
)
40 changes: 40 additions & 0 deletions Examples/SAM/SQSLambda/Lambda.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// ===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2021 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 Foundation

@main
struct SQSLambda: SimpleLambdaHandler {
typealias Event = SQSEvent
typealias Output = Void

init() {}
init(context: LambdaInitializationContext) async throws {
context.logger.info(
"Log Level env var : \(ProcessInfo.processInfo.environment["LOG_LEVEL"] ?? "info" )")
}

func handle(_ event: Event, context: AWSLambdaRuntimeCore.LambdaContext) async throws -> Output {

context.logger.info("Log Level env var : \(ProcessInfo.processInfo.environment["LOG_LEVEL"] ?? "not defined" )" )
context.logger.debug("SQS Message received, with \(event.records.count) record")

for msg in event.records {
context.logger.debug("Message ID : \(msg.messageId)")
context.logger.debug("Message body : \(msg.body)")
}
}
}
46 changes: 46 additions & 0 deletions Examples/SAM/Tests/LambdaTests/HttpApiLambdaTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// ===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2021 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 AWSLambdaTesting
import XCTest
@testable import HttpApiLambda

class HttpApiLambdaTests: LambdaTest {

func testHttpAPiLambda() async throws {

// given
let eventData = try self.loadTestData(file: .apiGatewayV2)
let event = try JSONDecoder().decode(APIGatewayV2Request.self, from: eventData)

do {
// when
let result = try await Lambda.test(HttpApiLambda.self, with: event)

// then
XCTAssertEqual(result.statusCode.code, 200)
XCTAssertNotNil(result.headers)
if let headers = result.headers {
XCTAssertNotNil(headers["content-type"])
if let contentType = headers["content-type"] {
XCTAssertTrue(contentType == "application/json")
}
}
} catch {
XCTFail("Lambda invocation should not throw error : \(error)")
}
}
}
22 changes: 22 additions & 0 deletions Examples/SAM/Tests/LambdaTests/LambdaTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Foundation
import XCTest

enum TestData : String {
case apiGatewayV2 = "apiv2"
case sqs = "sqs"
}

class LambdaTest: XCTestCase {
// return the URL of a test file
// files are copied to the bundle during build by the `resources` directive in `Package.swift`
private func urlForTestData(file: TestData) throws -> URL {
let filePath = Bundle.module.path(forResource: file.rawValue, ofType: "json")!
return URL(fileURLWithPath: filePath)
}

// load a test file added as a resource to the executable bundle
func loadTestData(file: TestData) throws -> Data {
// load list from file
return try Data(contentsOf: urlForTestData(file: file))
}
}
40 changes: 40 additions & 0 deletions Examples/SAM/Tests/LambdaTests/SQSLambdaTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// ===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2021 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 AWSLambdaTesting
import XCTest
@testable import SQSLambda

class SQSLambdaTests: LambdaTest {

func testSQSLambda() async throws {

// given
let eventData = try self.loadTestData(file: .sqs)
let event = try JSONDecoder().decode(SQSEvent.self, from: eventData)

// when
do {
try await Lambda.test(SQSLambda.self, with: event)
} catch {
XCTFail("Lambda invocation should not throw error : \(error)")
}

// then
// SQS Lambda returns Void

}
}
Loading