feat: Adds output values to some of the commands to allow them to be piped into other commands

This commit is contained in:
2024-12-16 12:28:38 -05:00
parent 1302b15ee2
commit 1429c51821
11 changed files with 112 additions and 64 deletions

View File

@@ -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,15 +51,14 @@ 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,
arguments in: workingDirectory,
makeArguments: { try await (makeArguments(), ()) }
) )
} }
}
/// Runs a shell command. /// Runs a shell command.
public func run( public func run(

View File

@@ -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)
} }
} }
} }

View File

@@ -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)

View File

@@ -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
} }
}
@_spi(Internal)
public enum Route: String, Equatable, Sendable { public enum Route: String, Equatable, Sendable {
case encrypt case encrypt
case decrypt 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
} }

View File

@@ -68,6 +68,7 @@ struct CreateCommand: AsyncParsableCommand {
template: .init(directory: templateDir), template: .init(directory: templateDir),
useLocalTemplateDirectory: localTemplateDir useLocalTemplateDirectory: localTemplateDir
)) ))
print(projectDir)
} }
} }

View File

@@ -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)
} }
} }

View File

@@ -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)
} }
} }

View File

@@ -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)
} }
} }

View File

@@ -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,

View File

@@ -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 = [

View File

@@ -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)"
] ]