diff --git a/Package.swift b/Package.swift index 294a890..0deab32 100644 --- a/Package.swift +++ b/Package.swift @@ -30,6 +30,7 @@ let package = Package( "CliClient", "ConfigurationClient", "FileClient", + "PlaybookClient", .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "CliDoc", package: "swift-cli-doc"), .product(name: "Dependencies", package: "swift-dependencies"), diff --git a/Sources/CommandClient/CommandClient.swift b/Sources/CommandClient/CommandClient.swift index 44b2a76..4207536 100644 --- a/Sources/CommandClient/CommandClient.swift +++ b/Sources/CommandClient/CommandClient.swift @@ -2,7 +2,6 @@ import Constants import Dependencies import DependenciesMacros import Foundation -import LoggingExtensions import ShellClient public extension DependencyValues { diff --git a/Sources/PlaybookClient/PlaybookClient+RunPlaybook.swift b/Sources/PlaybookClient/PlaybookClient+RunPlaybook.swift index 6fadea9..a604f72 100644 --- a/Sources/PlaybookClient/PlaybookClient+RunPlaybook.swift +++ b/Sources/PlaybookClient/PlaybookClient+RunPlaybook.swift @@ -52,6 +52,25 @@ extension PlaybookClient.RunPlaybook.CreateOptions { } } +extension PlaybookClient.RunPlaybook.GenerateTemplateOptions { + func run() async throws { + try await shared.run { arguments, _ in + arguments.append(contentsOf: [ + "--tags", "repo-template", + "--extra-vars", "output_dir=\(templateDirectory)" + ]) + + if let templateVarsDirectory { + arguments.append(contentsOf: ["--extra-vars", "repo_vars_dir=\(templateVarsDirectory)"]) + } + + if !useVault { + arguments.append(contentsOf: ["--extra-vars", "use_vault=false"]) + } + } + } +} + extension PlaybookClient.RunPlaybook.SharedRunOptions { func run(_ apply: @Sendable @escaping (inout [String], Configuration) throws -> Void) async throws { diff --git a/Sources/PlaybookClient/PlaybookClient.swift b/Sources/PlaybookClient/PlaybookClient.swift index 37af941..9b5b713 100644 --- a/Sources/PlaybookClient/PlaybookClient.swift +++ b/Sources/PlaybookClient/PlaybookClient.swift @@ -44,6 +44,7 @@ public extension PlaybookClient { struct RunPlaybook: Sendable { public var buildProject: @Sendable (BuildOptions) async throws -> Void public var createProject: @Sendable (CreateOptions, JSONEncoder?) async throws -> Void + public var generateTemplate: @Sendable (GenerateTemplateOptions) async throws -> Void public func createProject(_ options: CreateOptions) async throws { try await createProject(options, nil) @@ -84,26 +85,6 @@ public extension PlaybookClient { self.shared = shared } - public init( - extraOptions: [String]? = nil, - inventoryFilePath: String? = nil, - loggingOptions: LoggingOptions, - quiet: Bool = false, - shell: String? = nil, - projectDirectory: String? = nil - ) { - self.init( - projectDirectory: projectDirectory, - shared: .init( - extraOptions: extraOptions, - inventoryFilePath: inventoryFilePath, - loggingOptions: loggingOptions, - quiet: quiet, - shell: shell - ) - ) - } - public subscript(dynamicMember keyPath: KeyPath) -> T { shared[keyPath: keyPath] } @@ -128,28 +109,28 @@ public extension PlaybookClient { self.useLocalTemplateDirectory = useLocalTemplateDirectory } + public subscript(dynamicMember keyPath: KeyPath) -> T { + shared[keyPath: keyPath] + } + } + + @dynamicMemberLookup + public struct GenerateTemplateOptions: Equatable, Sendable { + public let shared: SharedRunOptions + public let templateDirectory: String + public let templateVarsDirectory: String? + public let useVault: Bool + public init( - extraOptions: [String]? = nil, - inventoryFilePath: String? = nil, - loggingOptions: LoggingOptions, - projectDirectory: String, - quiet: Bool = false, - shell: String? = nil, - template: Configuration.Template? = nil, - useLocalTemplateDirectory: Bool = false + shared: SharedRunOptions, + templateDirectory: String, + templateVarsDirectory: String? = nil, + useVault: Bool = true ) { - self.init( - projectDirectory: projectDirectory, - shared: .init( - extraOptions: extraOptions, - inventoryFilePath: inventoryFilePath, - loggingOptions: loggingOptions, - quiet: quiet, - shell: shell - ), - template: template, - useLocalTemplateDirectory: useLocalTemplateDirectory - ) + self.shared = shared + self.templateDirectory = templateDirectory + self.templateVarsDirectory = templateVarsDirectory + self.useVault = useVault } public subscript(dynamicMember keyPath: KeyPath) -> T { @@ -173,7 +154,8 @@ extension PlaybookClient.RunPlaybook: DependencyKey { public static var liveValue: PlaybookClient.RunPlaybook { .init( buildProject: { try await $0.run() }, - createProject: { try await $0.run(encoder: $1) } + createProject: { try await $0.run(encoder: $1) }, + generateTemplate: { try await $0.run() } ) } } diff --git a/Sources/hpa/BuildCommand.swift b/Sources/hpa/BuildCommand.swift index 30d22b8..1280d45 100644 --- a/Sources/hpa/BuildCommand.swift +++ b/Sources/hpa/BuildCommand.swift @@ -2,6 +2,7 @@ import ArgumentParser import CliClient import Dependencies import Foundation +import PlaybookClient struct BuildCommand: AsyncParsableCommand { @@ -34,33 +35,14 @@ struct BuildCommand: AsyncParsableCommand { var extraOptions: [String] = [] mutating func run() async throws { - try await _run() - } + @Dependency(\.playbookClient) var playbookClient - private func _run() async throws { - @Dependency(\.cliClient) var cliClient - - let projectDir: String - if projectDirectory == nil { - guard let pwd = ProcessInfo.processInfo.environment["PWD"] else { - throw ProjectDirectoryNotSupplied() - } - projectDir = pwd - } else { - projectDir = projectDirectory! - } - - try await cliClient.runPlaybookCommand( - globals.playbookOptions( - arguments: [ - "--tags", "build-project", - "--extra-vars", "project_dir=\(projectDir)" - ], - configuration: nil - ), - logging: globals.loggingOptions(commandName: Self.commandName) - ) + try await playbookClient.run.buildProject(.init( + projectDirectory: projectDirectory, + shared: globals.sharedPlaybookRunOptions( + commandName: Self.commandName, + extraOptions: extraOptions + ) + )) } } - -struct ProjectDirectoryNotSupplied: Error {} diff --git a/Sources/hpa/CreateCommand.swift b/Sources/hpa/CreateCommand.swift index 3506e5d..8be86a4 100644 --- a/Sources/hpa/CreateCommand.swift +++ b/Sources/hpa/CreateCommand.swift @@ -4,6 +4,7 @@ import ConfigurationClient import Dependencies import Foundation import Logging +import PlaybookClient struct CreateCommand: AsyncParsableCommand { @@ -57,40 +58,16 @@ struct CreateCommand: AsyncParsableCommand { var extraOptions: [String] = [] mutating func run() async throws { - try await _run() - } - - private func _run() async throws { - @Dependency(\.coders) var coders - @Dependency(\.cliClient) var cliClient - @Dependency(\.configurationClient) var configurationClient - - let loggingOptions = globals.loggingOptions(commandName: Self.commandName) - - try await cliClient.withLogger(loggingOptions) { - @Dependency(\.logger) var logger - - let json = try await cliClient.generateJSON( - generateJsonOptions, - logging: loggingOptions - ) - - logger.debug("JSON string: \(json)") - - let arguments = [ - "--tags", "setup-project", - "--extra-vars", "project_dir=\(self.projectDir)", - "--extra-vars", "'\(json)'" - ] + extraOptions - - try await cliClient.runPlaybookCommand( - globals.playbookOptions( - arguments: arguments, - configuration: nil - ), - logging: loggingOptions - ) - } + @Dependency(\.playbookClient) var playbookClient + try await playbookClient.run.createProject(.init( + projectDirectory: projectDir, + shared: globals.sharedPlaybookRunOptions( + commandName: Self.commandName, + extraOptions: extraOptions + ), + template: .init(directory: templateDir), + useLocalTemplateDirectory: localTemplateDir + )) } } diff --git a/Sources/hpa/Internal/GlobalOptions.swift b/Sources/hpa/Internal/GlobalOptions.swift index 25684ca..f67a61e 100644 --- a/Sources/hpa/Internal/GlobalOptions.swift +++ b/Sources/hpa/Internal/GlobalOptions.swift @@ -1,5 +1,7 @@ import ArgumentParser import CliClient +import ConfigurationClient +import PlaybookClient struct BasicGlobalOptions: ParsableArguments { @Flag( @@ -57,6 +59,8 @@ struct GlobalOptions: ParsableArguments { } +// TODO: Update these to use CommandClient.LoggingOptions + extension GlobalOptions { func loggingOptions(commandName: String) -> CliClient.LoggingOptions { .init( @@ -74,3 +78,39 @@ extension BasicGlobalOptions { ) } } + +// TODO: Remove +extension GlobalOptions { + + func playbookOptions( + arguments: [String], + configuration: Configuration? + ) -> CliClient.PlaybookOptions { + .init( + arguments: arguments, + configuration: configuration, + inventoryFilePath: inventoryPath, + playbookDirectory: playbookDirectory, + quiet: quietOnlyPlaybook ? true : basic.quiet, + shell: basic.shell + ) + } +} + +extension GlobalOptions { + func sharedPlaybookRunOptions( + commandName: String, + extraOptions: [String]? + ) -> PlaybookClient.RunPlaybook.SharedRunOptions { + return .init( + extraOptions: extraOptions, + inventoryFilePath: inventoryPath, + loggingOptions: .init( + commandName: commandName, + logLevel: .init(globals: basic, quietOnlyPlaybook: quietOnlyPlaybook) + ), + quiet: basic.quiet, + shell: basic.shell + ) + } +} diff --git a/Sources/hpa/Internal/PlaybookOptions+Globals.swift b/Sources/hpa/Internal/PlaybookOptions+Globals.swift deleted file mode 100644 index f805f3c..0000000 --- a/Sources/hpa/Internal/PlaybookOptions+Globals.swift +++ /dev/null @@ -1,19 +0,0 @@ -import CliClient -import ConfigurationClient - -extension GlobalOptions { - - func playbookOptions( - arguments: [String], - configuration: Configuration? - ) -> CliClient.PlaybookOptions { - .init( - arguments: arguments, - configuration: configuration, - inventoryFilePath: inventoryPath, - playbookDirectory: playbookDirectory, - quiet: quietOnlyPlaybook ? true : basic.quiet, - shell: basic.shell - ) - } -} diff --git a/Sources/hpa/UtilsCommands/CreateTemplateCommand.swift b/Sources/hpa/UtilsCommands/CreateTemplateCommand.swift index 4e24d24..2b24cce 100644 --- a/Sources/hpa/UtilsCommands/CreateTemplateCommand.swift +++ b/Sources/hpa/UtilsCommands/CreateTemplateCommand.swift @@ -1,6 +1,7 @@ import ArgumentParser import CliClient import Dependencies +import PlaybookClient struct GenerateProjectTemplateCommand: AsyncParsableCommand { @@ -39,24 +40,16 @@ struct GenerateProjectTemplateCommand: AsyncParsableCommand { var extraOptions: [String] = [] mutating func run() async throws { - @Dependency(\.cliClient) var cliClient + @Dependency(\.playbookClient) var playbookClient - var arguments = [ - "--tags", "repo-template", - "--extra-vars", "output_dir=\(path)" - ] - - if let varsDir = templateVars { - arguments.append(contentsOf: ["--extra-vars", "repo_vars_dir=\(varsDir)"]) - } - - if noVault { - arguments.append(contentsOf: ["--extra-vars", "use_vault=false"]) - } - - try await cliClient.runPlaybookCommand( - globals.playbookOptions(arguments: arguments, configuration: nil), - logging: globals.loggingOptions(commandName: Self.commandName) - ) + try await playbookClient.run.generateTemplate(.init( + shared: globals.sharedPlaybookRunOptions( + commandName: Self.commandName, + extraOptions: extraOptions + ), + templateDirectory: path, + templateVarsDirectory: templateVars, + useVault: !noVault + )) } } diff --git a/Tests/PlaybookClientTests/PlaybookClientTests.swift b/Tests/PlaybookClientTests/PlaybookClientTests.swift index d8d4e16..a9bde0e 100644 --- a/Tests/PlaybookClientTests/PlaybookClientTests.swift +++ b/Tests/PlaybookClientTests/PlaybookClientTests.swift @@ -16,6 +16,10 @@ struct PlaybookClientTests: TestCase { .init(loggingOptions: loggingOptions) } + static let defaultPlaybookPath = "~/.local/share/hpa/playbook/main.yml" + static let defaultInventoryPath = "~/.local/share/hpa/playbook/inventory.ini" + static let mockVaultArg = Configuration.mock.vault.args![0] + @Test(.tags(.repository)) func repositoryInstallation() async throws { try await withDependencies { @@ -65,9 +69,9 @@ struct PlaybookClientTests: TestCase { print(arguments) #expect(arguments == [ - "ansible-playbook", "~/.local/share/hpa/playbook/main.yml", - "--inventory", "~/.local/share/hpa/playbook/inventory.ini", - configuration.vault.args!.first!, + "ansible-playbook", Self.defaultPlaybookPath, + "--inventory", Self.defaultInventoryPath, + Self.mockVaultArg, "--tags", "build-project", "--extra-vars", "project_dir=/foo" ]) @@ -102,9 +106,9 @@ struct PlaybookClientTests: TestCase { logger.debug("\(arguments)") #expect(arguments == [ - "ansible-playbook", "~/.local/share/hpa/playbook/main.yml", - "--inventory", "~/.local/share/hpa/playbook/inventory.ini", - configuration.vault.args!.first!, + "ansible-playbook", Self.defaultPlaybookPath, + "--inventory", Self.defaultInventoryPath, + Self.mockVaultArg, "--tags", "setup-project", "--extra-vars", "project_dir=/project", "--extra-vars", json @@ -139,6 +143,33 @@ struct PlaybookClientTests: TestCase { } } + @Test + func generateTemplate() async throws { + try await withCapturingCommandClient("generateTemplate") { + $0.configurationClient = .mock() + $0.playbookClient = .liveValue + } run: { + @Dependency(\.playbookClient) var playbookClient + + try await playbookClient.run.generateTemplate(.init( + shared: Self.sharedRunOptions, + templateDirectory: "/template" + )) + + } assert: { output in + + let expected = [ + "ansible-playbook", Self.defaultPlaybookPath, + "--inventory", Self.defaultInventoryPath, + Self.mockVaultArg, + "--tags", "repo-template", + "--extra-vars", "output_dir=/template" + ] + + #expect(output.arguments == expected) + } + } + func withMockConfiguration( _ capturing: CommandClient.CapturingClient, configuration: Configuration = .mock,