Skip to content

Commit cc08a9a

Browse files
committed
WIP deploy plugin
1 parent 2b5405a commit cc08a9a

File tree

5 files changed

+565
-0
lines changed

5 files changed

+565
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Foundation
16+
import PackagePlugin
17+
18+
@main
19+
@available(macOS 15.0, *)
20+
struct AWSLambdaDeployer: CommandPlugin {
21+
22+
23+
func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws {
24+
let configuration = try Configuration(context: context, arguments: arguments)
25+
26+
if configuration.help {
27+
self.displayHelpMessage()
28+
return
29+
}
30+
31+
let tool = try context.tool(named: "AWSLambdaDeployerHelper")
32+
try Utils.execute(executable: tool.url, arguments: [], logLevel: .debug)
33+
}
34+
35+
private func displayHelpMessage() {
36+
print(
37+
"""
38+
OVERVIEW: A SwiftPM plugin to deploy a Lambda function.
39+
40+
USAGE: swift package lambda-deploy
41+
[--with-url]
42+
[--help] [--verbose]
43+
44+
OPTIONS:
45+
--with-url Add an URL to access the Lambda function
46+
--verbose Produce verbose output for debugging.
47+
--help Show help information.
48+
"""
49+
)
50+
}
51+
}
52+
53+
private struct Configuration: CustomStringConvertible {
54+
public let help: Bool
55+
public let verboseLogging: Bool
56+
57+
public init(
58+
context: PluginContext,
59+
arguments: [String]
60+
) throws {
61+
var argumentExtractor = ArgumentExtractor(arguments)
62+
let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0
63+
let helpArgument = argumentExtractor.extractFlag(named: "help") > 0
64+
65+
// help required ?
66+
self.help = helpArgument
67+
68+
// verbose logging required ?
69+
self.verboseLogging = verboseArgument
70+
}
71+
72+
var description: String {
73+
"""
74+
{
75+
verboseLogging: \(self.verboseLogging)
76+
}
77+
"""
78+
}
79+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Dispatch
16+
import Foundation
17+
import PackagePlugin
18+
import Synchronization
19+
20+
@available(macOS 15.0, *)
21+
struct Utils {
22+
@discardableResult
23+
static func execute(
24+
executable: URL,
25+
arguments: [String],
26+
customWorkingDirectory: URL? = .none,
27+
logLevel: ProcessLogLevel
28+
) throws -> String {
29+
if logLevel >= .debug {
30+
print("\(executable.path()) \(arguments.joined(separator: " "))")
31+
}
32+
33+
let fd = dup(1)
34+
let stdout = fdopen(fd, "rw")
35+
defer { if let so = stdout { fclose(so) } }
36+
37+
// We need to use an unsafe transfer here to get the fd into our Sendable closure.
38+
// This transfer is fine, because we write to the variable from a single SerialDispatchQueue here.
39+
// We wait until the process is run below process.waitUntilExit().
40+
// This means no further writes to output will happen.
41+
// This makes it save for us to read the output
42+
struct UnsafeTransfer<Value>: @unchecked Sendable {
43+
let value: Value
44+
}
45+
46+
let outputMutex = Mutex("")
47+
let outputSync = DispatchGroup()
48+
let outputQueue = DispatchQueue(label: "AWSLambdaPackager.output")
49+
let unsafeTransfer = UnsafeTransfer(value: stdout)
50+
let outputHandler = { @Sendable (data: Data?) in
51+
dispatchPrecondition(condition: .onQueue(outputQueue))
52+
53+
outputSync.enter()
54+
defer { outputSync.leave() }
55+
56+
guard
57+
let _output = data.flatMap({
58+
String(data: $0, encoding: .utf8)?.trimmingCharacters(in: CharacterSet(["\n"]))
59+
}), !_output.isEmpty
60+
else {
61+
return
62+
}
63+
64+
outputMutex.withLock { output in
65+
output += _output + "\n"
66+
}
67+
68+
switch logLevel {
69+
case .silent:
70+
break
71+
case .debug(let outputIndent), .output(let outputIndent):
72+
print(String(repeating: " ", count: outputIndent), terminator: "")
73+
print(_output)
74+
fflush(unsafeTransfer.value)
75+
}
76+
}
77+
78+
let pipe = Pipe()
79+
pipe.fileHandleForReading.readabilityHandler = { fileHandle in
80+
outputQueue.async { outputHandler(fileHandle.availableData) }
81+
}
82+
83+
let process = Process()
84+
process.standardOutput = pipe
85+
process.standardError = pipe
86+
process.executableURL = executable
87+
process.arguments = arguments
88+
if let workingDirectory = customWorkingDirectory {
89+
process.currentDirectoryURL = URL(fileURLWithPath: workingDirectory.path())
90+
}
91+
process.terminationHandler = { _ in
92+
outputQueue.async {
93+
outputHandler(try? pipe.fileHandleForReading.readToEnd())
94+
}
95+
}
96+
97+
try process.run()
98+
process.waitUntilExit()
99+
100+
// wait for output to be full processed
101+
outputSync.wait()
102+
103+
let output = outputMutex.withLock { $0 }
104+
105+
if process.terminationStatus != 0 {
106+
// print output on failure and if not already printed
107+
if logLevel < .output {
108+
print(output)
109+
fflush(stdout)
110+
}
111+
throw ProcessError.processFailed([executable.path()] + arguments, process.terminationStatus)
112+
}
113+
114+
return output
115+
}
116+
117+
enum ProcessError: Error, CustomStringConvertible {
118+
case processFailed([String], Int32)
119+
120+
var description: String {
121+
switch self {
122+
case .processFailed(let arguments, let code):
123+
return "\(arguments.joined(separator: " ")) failed with code \(code)"
124+
}
125+
}
126+
}
127+
128+
enum ProcessLogLevel: Comparable {
129+
case silent
130+
case output(outputIndent: Int)
131+
case debug(outputIndent: Int)
132+
133+
var naturalOrder: Int {
134+
switch self {
135+
case .silent:
136+
return 0
137+
case .output:
138+
return 1
139+
case .debug:
140+
return 2
141+
}
142+
}
143+
144+
static var output: Self {
145+
.output(outputIndent: 2)
146+
}
147+
148+
static var debug: Self {
149+
.debug(outputIndent: 2)
150+
}
151+
152+
static func < (lhs: ProcessLogLevel, rhs: ProcessLogLevel) -> Bool {
153+
lhs.naturalOrder < rhs.naturalOrder
154+
}
155+
}
156+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
//
16+
// credentials.swift
17+
// aws-sign
18+
//
19+
// Created by Adam Fowler on 29/08/2019.
20+
//
21+
import class Foundation.ProcessInfo
22+
23+
/// Protocol for providing credential details for accessing AWS services
24+
public protocol Credential {
25+
var accessKeyId: String {get}
26+
var secretAccessKey: String {get}
27+
var sessionToken: String? {get}
28+
}
29+
30+
/// basic version of Credential where you supply the credentials
31+
public struct StaticCredential: Credential {
32+
public let accessKeyId: String
33+
public let secretAccessKey: String
34+
public let sessionToken: String?
35+
36+
public init(accessKeyId: String, secretAccessKey: String, sessionToken: String? = nil) {
37+
self.accessKeyId = accessKeyId
38+
self.secretAccessKey = secretAccessKey
39+
self.sessionToken = sessionToken
40+
}
41+
}
42+
43+
/// environment variable version of credential that uses system environment variables to get credential details
44+
public struct EnvironmentCredential: Credential {
45+
public let accessKeyId: String
46+
public let secretAccessKey: String
47+
public let sessionToken: String?
48+
49+
public init?() {
50+
guard let accessKeyId = ProcessInfo.processInfo.environment["AWS_ACCESS_KEY_ID"] else {
51+
return nil
52+
}
53+
guard let secretAccessKey = ProcessInfo.processInfo.environment["AWS_SECRET_ACCESS_KEY"] else {
54+
return nil
55+
}
56+
self.accessKeyId = accessKeyId
57+
self.secretAccessKey = secretAccessKey
58+
self.sessionToken = ProcessInfo.processInfo.environment["AWS_SESSION_TOKEN"]
59+
}
60+
}

0 commit comments

Comments
 (0)