feat: Adds output values to some of the commands to allow them to be piped into other commands
This commit is contained in:
@@ -19,6 +19,30 @@ public struct CommandClient: Sendable {
|
|||||||
/// Runs a shell command.
|
/// Runs a shell command.
|
||||||
public var runCommand: @Sendable (RunCommandOptions) async throws -> Void
|
public var runCommand: @Sendable (RunCommandOptions) async throws -> Void
|
||||||
|
|
||||||
|
/// Runs a shell command, sets up logging, and returns an output value.
|
||||||
|
///
|
||||||
|
/// This is useful when you need to give some output value to the caller,
|
||||||
|
/// generally for the ability of that output to be piped into other commands.
|
||||||
|
///
|
||||||
|
@discardableResult
|
||||||
|
public func run<T>(
|
||||||
|
logging logginOptions: LoggingOptions,
|
||||||
|
quiet: Bool = false,
|
||||||
|
shell: String? = nil,
|
||||||
|
in workingDirectory: String? = nil,
|
||||||
|
makeArguments: @Sendable @escaping () async throws -> ([String], T)
|
||||||
|
) async throws -> T {
|
||||||
|
try await logginOptions.withLogger {
|
||||||
|
let (arguments, returnValue) = try await makeArguments()
|
||||||
|
try await self.run(
|
||||||
|
quiet: quiet,
|
||||||
|
shell: shell,
|
||||||
|
arguments
|
||||||
|
)
|
||||||
|
return returnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs a shell command and sets up logging.
|
/// Runs a shell command and sets up logging.
|
||||||
public func run(
|
public func run(
|
||||||
logging logginOptions: LoggingOptions,
|
logging logginOptions: LoggingOptions,
|
||||||
@@ -27,14 +51,13 @@ public struct CommandClient: Sendable {
|
|||||||
in workingDirectory: String? = nil,
|
in workingDirectory: String? = nil,
|
||||||
makeArguments: @Sendable @escaping () async throws -> [String]
|
makeArguments: @Sendable @escaping () async throws -> [String]
|
||||||
) async throws {
|
) async throws {
|
||||||
try await logginOptions.withLogger {
|
try await run(
|
||||||
let arguments = try await makeArguments()
|
logging: logginOptions,
|
||||||
try await self.run(
|
quiet: quiet,
|
||||||
quiet: quiet,
|
shell: shell,
|
||||||
shell: shell,
|
in: workingDirectory,
|
||||||
arguments
|
makeArguments: { try await (makeArguments(), ()) }
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs a shell command.
|
/// Runs a shell command.
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ extension PlaybookClient.RunPlaybook.CreateOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension PlaybookClient.RunPlaybook.GenerateTemplateOptions {
|
extension PlaybookClient.RunPlaybook.GenerateTemplateOptions {
|
||||||
func run() async throws {
|
func run() async throws -> String {
|
||||||
try await shared.run { arguments, _ in
|
try await shared.run { arguments, _ in
|
||||||
arguments.append(contentsOf: [
|
arguments.append(contentsOf: [
|
||||||
"--tags", "repo-template",
|
"--tags", "repo-template",
|
||||||
@@ -67,16 +67,21 @@ extension PlaybookClient.RunPlaybook.GenerateTemplateOptions {
|
|||||||
if !useVault {
|
if !useVault {
|
||||||
arguments.append(contentsOf: ["--extra-vars", "use_vault=false"])
|
arguments.append(contentsOf: ["--extra-vars", "use_vault=false"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return templateDirectory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PlaybookClient.RunPlaybook.SharedRunOptions {
|
extension PlaybookClient.RunPlaybook.SharedRunOptions {
|
||||||
|
|
||||||
func run(_ apply: @Sendable @escaping (inout [String], Configuration) throws -> Void) async throws {
|
@discardableResult
|
||||||
|
func run<T>(
|
||||||
|
_ apply: @Sendable @escaping (inout [String], Configuration) throws -> T
|
||||||
|
) async throws -> T {
|
||||||
@Dependency(\.commandClient) var commandClient
|
@Dependency(\.commandClient) var commandClient
|
||||||
|
|
||||||
try await commandClient.run(
|
return try await commandClient.run(
|
||||||
logging: loggingOptions,
|
logging: loggingOptions,
|
||||||
quiet: quiet,
|
quiet: quiet,
|
||||||
shell: shell
|
shell: shell
|
||||||
@@ -91,9 +96,9 @@ extension PlaybookClient.RunPlaybook.SharedRunOptions {
|
|||||||
inventoryFilePath: inventoryFilePath
|
inventoryFilePath: inventoryFilePath
|
||||||
)
|
)
|
||||||
|
|
||||||
try apply(&arguments, configuration)
|
let output = try apply(&arguments, configuration)
|
||||||
|
|
||||||
return arguments
|
return (arguments, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public extension PlaybookClient {
|
|||||||
struct RunPlaybook: Sendable {
|
struct RunPlaybook: Sendable {
|
||||||
public var buildProject: @Sendable (BuildOptions) async throws -> Void
|
public var buildProject: @Sendable (BuildOptions) async throws -> Void
|
||||||
public var createProject: @Sendable (CreateOptions, JSONEncoder?) async throws -> Void
|
public var createProject: @Sendable (CreateOptions, JSONEncoder?) async throws -> Void
|
||||||
public var generateTemplate: @Sendable (GenerateTemplateOptions) async throws -> Void
|
public var generateTemplate: @Sendable (GenerateTemplateOptions) async throws -> String
|
||||||
|
|
||||||
public func createProject(_ options: CreateOptions) async throws {
|
public func createProject(_ options: CreateOptions) async throws {
|
||||||
try await createProject(options, nil)
|
try await createProject(options, nil)
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ public extension DependencyValues {
|
|||||||
|
|
||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct VaultClient: Sendable {
|
public struct VaultClient: Sendable {
|
||||||
public var run: @Sendable (RunOptions) async throws -> Void
|
public var run: Run
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct Run: Sendable {
|
||||||
|
public var decrypt: @Sendable (RunOptions) async throws -> String
|
||||||
|
public var encrypt: @Sendable (RunOptions) async throws -> String
|
||||||
|
}
|
||||||
|
|
||||||
public struct RunOptions: Equatable, Sendable {
|
public struct RunOptions: Equatable, Sendable {
|
||||||
|
|
||||||
@@ -23,12 +29,10 @@ public struct VaultClient: Sendable {
|
|||||||
public let loggingOptions: LoggingOptions
|
public let loggingOptions: LoggingOptions
|
||||||
public let outputFilePath: String?
|
public let outputFilePath: String?
|
||||||
public let quiet: Bool
|
public let quiet: Bool
|
||||||
public let route: Route
|
|
||||||
public let shell: String?
|
public let shell: String?
|
||||||
public let vaultFilePath: String?
|
public let vaultFilePath: String?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
_ route: Route,
|
|
||||||
extraOptions: [String]? = nil,
|
extraOptions: [String]? = nil,
|
||||||
loggingOptions: LoggingOptions,
|
loggingOptions: LoggingOptions,
|
||||||
outputFilePath: String? = nil,
|
outputFilePath: String? = nil,
|
||||||
@@ -40,28 +44,34 @@ public struct VaultClient: Sendable {
|
|||||||
self.loggingOptions = loggingOptions
|
self.loggingOptions = loggingOptions
|
||||||
self.outputFilePath = outputFilePath
|
self.outputFilePath = outputFilePath
|
||||||
self.quiet = quiet
|
self.quiet = quiet
|
||||||
self.route = route
|
|
||||||
self.shell = shell
|
self.shell = shell
|
||||||
self.vaultFilePath = vaultFilePath
|
self.vaultFilePath = vaultFilePath
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum Route: String, Equatable, Sendable {
|
@_spi(Internal)
|
||||||
case encrypt
|
public enum Route: String, Equatable, Sendable {
|
||||||
case decrypt
|
case encrypt
|
||||||
|
case decrypt
|
||||||
|
|
||||||
@_spi(Internal)
|
public var verb: String { rawValue }
|
||||||
public var verb: String { rawValue }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VaultClient: DependencyKey {
|
extension VaultClient: DependencyKey {
|
||||||
|
|
||||||
public static let testValue: VaultClient = Self()
|
public static let testValue: VaultClient = Self(run: Run())
|
||||||
|
|
||||||
public static var liveValue: VaultClient {
|
public static var liveValue: VaultClient {
|
||||||
.init(
|
.init(
|
||||||
run: { try await $0.run() }
|
run: .init(
|
||||||
|
decrypt: {
|
||||||
|
try await $0.run(route: .decrypt)
|
||||||
|
},
|
||||||
|
encrypt: {
|
||||||
|
try await $0.run(route: .encrypt)
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,10 +85,18 @@ public extension VaultClient {
|
|||||||
|
|
||||||
extension VaultClient.RunOptions {
|
extension VaultClient.RunOptions {
|
||||||
|
|
||||||
func run() async throws {
|
// Sets up the default arguments and runs the `ansible-vault` command,
|
||||||
|
// returning the output file path, which is either supplied by the caller
|
||||||
|
// or found via the `fileClient.findVaultFileInCurrentDirectory`.
|
||||||
|
//
|
||||||
|
// This allows the output to be piped into other commands.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
@discardableResult
|
||||||
|
func run(route: VaultClient.Route) async throws -> String {
|
||||||
@Dependency(\.commandClient) var commandClient
|
@Dependency(\.commandClient) var commandClient
|
||||||
|
|
||||||
try await commandClient.run(
|
return try await commandClient.run(
|
||||||
logging: loggingOptions,
|
logging: loggingOptions,
|
||||||
quiet: quiet,
|
quiet: quiet,
|
||||||
shell: shell
|
shell: shell
|
||||||
@@ -87,6 +105,8 @@ extension VaultClient.RunOptions {
|
|||||||
@Dependency(\.fileClient) var fileClient
|
@Dependency(\.fileClient) var fileClient
|
||||||
@Dependency(\.logger) var logger
|
@Dependency(\.logger) var logger
|
||||||
|
|
||||||
|
var output: String?
|
||||||
|
|
||||||
let configuration = try await configurationClient.findAndLoad()
|
let configuration = try await configurationClient.findAndLoad()
|
||||||
logger.trace("Configuration: \(configuration)")
|
logger.trace("Configuration: \(configuration)")
|
||||||
|
|
||||||
@@ -101,6 +121,7 @@ extension VaultClient.RunOptions {
|
|||||||
guard let vaultFilePath else {
|
guard let vaultFilePath else {
|
||||||
throw VaultClientError.vaultFileNotFound
|
throw VaultClientError.vaultFileNotFound
|
||||||
}
|
}
|
||||||
|
output = vaultFilePath
|
||||||
|
|
||||||
logger.trace("Vault file: \(vaultFilePath)")
|
logger.trace("Vault file: \(vaultFilePath)")
|
||||||
|
|
||||||
@@ -111,6 +132,7 @@ extension VaultClient.RunOptions {
|
|||||||
|
|
||||||
if let outputFilePath {
|
if let outputFilePath {
|
||||||
arguments.append(contentsOf: ["--output", outputFilePath])
|
arguments.append(contentsOf: ["--output", outputFilePath])
|
||||||
|
output = outputFilePath
|
||||||
}
|
}
|
||||||
|
|
||||||
if let extraOptions {
|
if let extraOptions {
|
||||||
@@ -132,33 +154,11 @@ extension VaultClient.RunOptions {
|
|||||||
|
|
||||||
logger.trace("Arguments: \(arguments)")
|
logger.trace("Arguments: \(arguments)")
|
||||||
|
|
||||||
return arguments
|
return (arguments, output ?? "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// extension VaultClient.RunOptions.Route {
|
|
||||||
//
|
|
||||||
// var arguments: [String] {
|
|
||||||
// let outputFile: String?
|
|
||||||
// var arguments: [String]
|
|
||||||
//
|
|
||||||
// switch self {
|
|
||||||
// case let .decrypt(outputFile: output):
|
|
||||||
// outputFile = output
|
|
||||||
// arguments = ["decrypt"]
|
|
||||||
// case let .encrypt(outputFile: output):
|
|
||||||
// outputFile = output
|
|
||||||
// arguments = ["encrypt"]
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if let outputFile {
|
|
||||||
// arguments.append(contentsOf: ["--output", outputFile])
|
|
||||||
// }
|
|
||||||
// return arguments
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
enum VaultClientError: Error {
|
enum VaultClientError: Error {
|
||||||
case vaultFileNotFound
|
case vaultFileNotFound
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ struct CreateCommand: AsyncParsableCommand {
|
|||||||
template: .init(directory: templateDir),
|
template: .init(directory: templateDir),
|
||||||
useLocalTemplateDirectory: localTemplateDir
|
useLocalTemplateDirectory: localTemplateDir
|
||||||
))
|
))
|
||||||
|
print(projectDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ struct GenerateProjectTemplateCommand: AsyncParsableCommand {
|
|||||||
mutating func run() async throws {
|
mutating func run() async throws {
|
||||||
@Dependency(\.playbookClient) var playbookClient
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
|
|
||||||
try await playbookClient.run.generateTemplate(.init(
|
let output = try await playbookClient.run.generateTemplate(.init(
|
||||||
shared: globals.sharedPlaybookRunOptions(
|
shared: globals.sharedPlaybookRunOptions(
|
||||||
commandName: Self.commandName,
|
commandName: Self.commandName,
|
||||||
extraOptions: extraOptions
|
extraOptions: extraOptions
|
||||||
@@ -51,5 +51,7 @@ struct GenerateProjectTemplateCommand: AsyncParsableCommand {
|
|||||||
templateVarsDirectory: templateVars,
|
templateVarsDirectory: templateVars,
|
||||||
useVault: !noVault
|
useVault: !noVault
|
||||||
))
|
))
|
||||||
|
|
||||||
|
print(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,11 @@ struct DecryptCommand: AsyncParsableCommand {
|
|||||||
mutating func run() async throws {
|
mutating func run() async throws {
|
||||||
@Dependency(\.vaultClient) var vaultClient
|
@Dependency(\.vaultClient) var vaultClient
|
||||||
|
|
||||||
try await vaultClient.run(options.runOptions(
|
let output = try await vaultClient.run.decrypt(options.runOptions(
|
||||||
commandName: Self.commandName,
|
commandName: Self.commandName,
|
||||||
route: .decrypt,
|
|
||||||
outputFilePath: output
|
outputFilePath: output
|
||||||
))
|
))
|
||||||
|
|
||||||
|
print(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,11 @@ struct EncryptCommand: AsyncParsableCommand {
|
|||||||
mutating func run() async throws {
|
mutating func run() async throws {
|
||||||
@Dependency(\.vaultClient) var vaultClient
|
@Dependency(\.vaultClient) var vaultClient
|
||||||
|
|
||||||
try await vaultClient.run(options.runOptions(
|
let output = try await vaultClient.run.encrypt(options.runOptions(
|
||||||
commandName: Self.commandName,
|
commandName: Self.commandName,
|
||||||
route: .encrypt,
|
|
||||||
outputFilePath: output
|
outputFilePath: output
|
||||||
))
|
))
|
||||||
|
|
||||||
|
print(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,11 +42,9 @@ extension VaultOptions {
|
|||||||
|
|
||||||
func runOptions(
|
func runOptions(
|
||||||
commandName: String,
|
commandName: String,
|
||||||
route: VaultClient.RunOptions.Route,
|
|
||||||
outputFilePath: String? = nil
|
outputFilePath: String? = nil
|
||||||
) -> VaultClient.RunOptions {
|
) -> VaultClient.RunOptions {
|
||||||
.init(
|
.init(
|
||||||
route,
|
|
||||||
extraOptions: extraOptions,
|
extraOptions: extraOptions,
|
||||||
loggingOptions: .init(
|
loggingOptions: .init(
|
||||||
commandName: commandName,
|
commandName: commandName,
|
||||||
|
|||||||
@@ -151,11 +151,13 @@ struct PlaybookClientTests: TestCase {
|
|||||||
} run: {
|
} run: {
|
||||||
@Dependency(\.playbookClient) var playbookClient
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
|
|
||||||
try await playbookClient.run.generateTemplate(.init(
|
let output = try await playbookClient.run.generateTemplate(.init(
|
||||||
shared: Self.sharedRunOptions,
|
shared: Self.sharedRunOptions,
|
||||||
templateDirectory: "/template"
|
templateDirectory: "/template"
|
||||||
))
|
))
|
||||||
|
|
||||||
|
#expect(output == "/template")
|
||||||
|
|
||||||
} assert: { output in
|
} assert: { output in
|
||||||
|
|
||||||
let expected = [
|
let expected = [
|
||||||
|
|||||||
@@ -19,13 +19,20 @@ struct VaultClientTests: TestCase {
|
|||||||
} run: {
|
} run: {
|
||||||
@Dependency(\.vaultClient) var vaultClient
|
@Dependency(\.vaultClient) var vaultClient
|
||||||
|
|
||||||
try await vaultClient.run(.init(
|
let output = try await vaultClient.run.decrypt(.init(
|
||||||
.decrypt,
|
|
||||||
extraOptions: input.extraOptions,
|
extraOptions: input.extraOptions,
|
||||||
loggingOptions: Self.loggingOptions,
|
loggingOptions: Self.loggingOptions,
|
||||||
outputFilePath: input.outputFilePath,
|
outputFilePath: input.outputFilePath,
|
||||||
vaultFilePath: input.vaultFilePath
|
vaultFilePath: input.vaultFilePath
|
||||||
))
|
))
|
||||||
|
|
||||||
|
if let outputFilePath = input.outputFilePath {
|
||||||
|
#expect(output == outputFilePath)
|
||||||
|
} else if let vaultFilePath = input.vaultFilePath {
|
||||||
|
#expect(output == vaultFilePath)
|
||||||
|
} else {
|
||||||
|
#expect(output == "/vault.yml")
|
||||||
|
}
|
||||||
} assert: { options in
|
} assert: { options in
|
||||||
|
|
||||||
#expect(options.arguments == input.expected(.decrypt))
|
#expect(options.arguments == input.expected(.decrypt))
|
||||||
@@ -43,13 +50,21 @@ struct VaultClientTests: TestCase {
|
|||||||
} run: {
|
} run: {
|
||||||
@Dependency(\.vaultClient) var vaultClient
|
@Dependency(\.vaultClient) var vaultClient
|
||||||
|
|
||||||
try await vaultClient.run(.init(
|
let output = try await vaultClient.run.encrypt(.init(
|
||||||
.encrypt,
|
|
||||||
extraOptions: input.extraOptions,
|
extraOptions: input.extraOptions,
|
||||||
loggingOptions: Self.loggingOptions,
|
loggingOptions: Self.loggingOptions,
|
||||||
outputFilePath: input.outputFilePath,
|
outputFilePath: input.outputFilePath,
|
||||||
vaultFilePath: input.vaultFilePath
|
vaultFilePath: input.vaultFilePath
|
||||||
))
|
))
|
||||||
|
|
||||||
|
if let outputFilePath = input.outputFilePath {
|
||||||
|
#expect(output == outputFilePath)
|
||||||
|
} else if let vaultFilePath = input.vaultFilePath {
|
||||||
|
#expect(output == vaultFilePath)
|
||||||
|
} else {
|
||||||
|
#expect(output == "/vault.yml")
|
||||||
|
}
|
||||||
|
|
||||||
} assert: { options in
|
} assert: { options in
|
||||||
#expect(options.arguments == input.expected(.encrypt))
|
#expect(options.arguments == input.expected(.encrypt))
|
||||||
}
|
}
|
||||||
@@ -75,7 +90,7 @@ struct TestOptions: Sendable {
|
|||||||
self.vaultFilePath = vaultFilePath
|
self.vaultFilePath = vaultFilePath
|
||||||
}
|
}
|
||||||
|
|
||||||
func expected(_ route: VaultClient.RunOptions.Route) -> [String] {
|
func expected(_ route: VaultClient.Route) -> [String] {
|
||||||
var expected = [
|
var expected = [
|
||||||
"ansible-vault", "\(route.verb)"
|
"ansible-vault", "\(route.verb)"
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user