feat: Integrates playbook client into hpa-executable.

This commit is contained in:
2024-12-16 10:29:58 -05:00
parent 35d9422f07
commit da810d0a45
10 changed files with 151 additions and 146 deletions

View File

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

View File

@@ -2,7 +2,6 @@ import Constants
import Dependencies
import DependenciesMacros
import Foundation
import LoggingExtensions
import ShellClient
public extension DependencyValues {

View File

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

View File

@@ -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<T>(dynamicMember keyPath: KeyPath<SharedRunOptions, T>) -> T {
shared[keyPath: keyPath]
}
@@ -128,28 +109,28 @@ public extension PlaybookClient {
self.useLocalTemplateDirectory = useLocalTemplateDirectory
}
public subscript<T>(dynamicMember keyPath: KeyPath<SharedRunOptions, T>) -> 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<T>(dynamicMember keyPath: KeyPath<SharedRunOptions, T>) -> 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() }
)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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