feat: Integrates playbook client into hpa-executable.
This commit is contained in:
@@ -30,6 +30,7 @@ let package = Package(
|
|||||||
"CliClient",
|
"CliClient",
|
||||||
"ConfigurationClient",
|
"ConfigurationClient",
|
||||||
"FileClient",
|
"FileClient",
|
||||||
|
"PlaybookClient",
|
||||||
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||||
.product(name: "CliDoc", package: "swift-cli-doc"),
|
.product(name: "CliDoc", package: "swift-cli-doc"),
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import Constants
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import DependenciesMacros
|
import DependenciesMacros
|
||||||
import Foundation
|
import Foundation
|
||||||
import LoggingExtensions
|
|
||||||
import ShellClient
|
import ShellClient
|
||||||
|
|
||||||
public extension DependencyValues {
|
public extension DependencyValues {
|
||||||
|
|||||||
@@ -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 {
|
extension PlaybookClient.RunPlaybook.SharedRunOptions {
|
||||||
|
|
||||||
func run(_ apply: @Sendable @escaping (inout [String], Configuration) throws -> Void) async throws {
|
func run(_ apply: @Sendable @escaping (inout [String], Configuration) throws -> Void) async throws {
|
||||||
|
|||||||
@@ -44,6 +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 func createProject(_ options: CreateOptions) async throws {
|
public func createProject(_ options: CreateOptions) async throws {
|
||||||
try await createProject(options, nil)
|
try await createProject(options, nil)
|
||||||
@@ -84,26 +85,6 @@ public extension PlaybookClient {
|
|||||||
self.shared = shared
|
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 {
|
public subscript<T>(dynamicMember keyPath: KeyPath<SharedRunOptions, T>) -> T {
|
||||||
shared[keyPath: keyPath]
|
shared[keyPath: keyPath]
|
||||||
}
|
}
|
||||||
@@ -128,28 +109,28 @@ public extension PlaybookClient {
|
|||||||
self.useLocalTemplateDirectory = useLocalTemplateDirectory
|
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(
|
public init(
|
||||||
extraOptions: [String]? = nil,
|
shared: SharedRunOptions,
|
||||||
inventoryFilePath: String? = nil,
|
templateDirectory: String,
|
||||||
loggingOptions: LoggingOptions,
|
templateVarsDirectory: String? = nil,
|
||||||
projectDirectory: String,
|
useVault: Bool = true
|
||||||
quiet: Bool = false,
|
|
||||||
shell: String? = nil,
|
|
||||||
template: Configuration.Template? = nil,
|
|
||||||
useLocalTemplateDirectory: Bool = false
|
|
||||||
) {
|
) {
|
||||||
self.init(
|
self.shared = shared
|
||||||
projectDirectory: projectDirectory,
|
self.templateDirectory = templateDirectory
|
||||||
shared: .init(
|
self.templateVarsDirectory = templateVarsDirectory
|
||||||
extraOptions: extraOptions,
|
self.useVault = useVault
|
||||||
inventoryFilePath: inventoryFilePath,
|
|
||||||
loggingOptions: loggingOptions,
|
|
||||||
quiet: quiet,
|
|
||||||
shell: shell
|
|
||||||
),
|
|
||||||
template: template,
|
|
||||||
useLocalTemplateDirectory: useLocalTemplateDirectory
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public subscript<T>(dynamicMember keyPath: KeyPath<SharedRunOptions, T>) -> T {
|
public subscript<T>(dynamicMember keyPath: KeyPath<SharedRunOptions, T>) -> T {
|
||||||
@@ -173,7 +154,8 @@ extension PlaybookClient.RunPlaybook: DependencyKey {
|
|||||||
public static var liveValue: PlaybookClient.RunPlaybook {
|
public static var liveValue: PlaybookClient.RunPlaybook {
|
||||||
.init(
|
.init(
|
||||||
buildProject: { try await $0.run() },
|
buildProject: { try await $0.run() },
|
||||||
createProject: { try await $0.run(encoder: $1) }
|
createProject: { try await $0.run(encoder: $1) },
|
||||||
|
generateTemplate: { try await $0.run() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import ArgumentParser
|
|||||||
import CliClient
|
import CliClient
|
||||||
import Dependencies
|
import Dependencies
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import PlaybookClient
|
||||||
|
|
||||||
struct BuildCommand: AsyncParsableCommand {
|
struct BuildCommand: AsyncParsableCommand {
|
||||||
|
|
||||||
@@ -34,33 +35,14 @@ struct BuildCommand: AsyncParsableCommand {
|
|||||||
var extraOptions: [String] = []
|
var extraOptions: [String] = []
|
||||||
|
|
||||||
mutating func run() async throws {
|
mutating func run() async throws {
|
||||||
try await _run()
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
}
|
|
||||||
|
|
||||||
private func _run() async throws {
|
try await playbookClient.run.buildProject(.init(
|
||||||
@Dependency(\.cliClient) var cliClient
|
projectDirectory: projectDirectory,
|
||||||
|
shared: globals.sharedPlaybookRunOptions(
|
||||||
let projectDir: String
|
commandName: Self.commandName,
|
||||||
if projectDirectory == nil {
|
extraOptions: extraOptions
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProjectDirectoryNotSupplied: Error {}
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import ConfigurationClient
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import Foundation
|
import Foundation
|
||||||
import Logging
|
import Logging
|
||||||
|
import PlaybookClient
|
||||||
|
|
||||||
struct CreateCommand: AsyncParsableCommand {
|
struct CreateCommand: AsyncParsableCommand {
|
||||||
|
|
||||||
@@ -57,40 +58,16 @@ struct CreateCommand: AsyncParsableCommand {
|
|||||||
var extraOptions: [String] = []
|
var extraOptions: [String] = []
|
||||||
|
|
||||||
mutating func run() async throws {
|
mutating func run() async throws {
|
||||||
try await _run()
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
}
|
try await playbookClient.run.createProject(.init(
|
||||||
|
projectDirectory: projectDir,
|
||||||
private func _run() async throws {
|
shared: globals.sharedPlaybookRunOptions(
|
||||||
@Dependency(\.coders) var coders
|
commandName: Self.commandName,
|
||||||
@Dependency(\.cliClient) var cliClient
|
extraOptions: extraOptions
|
||||||
@Dependency(\.configurationClient) var configurationClient
|
),
|
||||||
|
template: .init(directory: templateDir),
|
||||||
let loggingOptions = globals.loggingOptions(commandName: Self.commandName)
|
useLocalTemplateDirectory: localTemplateDir
|
||||||
|
))
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
import CliClient
|
import CliClient
|
||||||
|
import ConfigurationClient
|
||||||
|
import PlaybookClient
|
||||||
|
|
||||||
struct BasicGlobalOptions: ParsableArguments {
|
struct BasicGlobalOptions: ParsableArguments {
|
||||||
@Flag(
|
@Flag(
|
||||||
@@ -57,6 +59,8 @@ struct GlobalOptions: ParsableArguments {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Update these to use CommandClient.LoggingOptions
|
||||||
|
|
||||||
extension GlobalOptions {
|
extension GlobalOptions {
|
||||||
func loggingOptions(commandName: String) -> CliClient.LoggingOptions {
|
func loggingOptions(commandName: String) -> CliClient.LoggingOptions {
|
||||||
.init(
|
.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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
import CliClient
|
import CliClient
|
||||||
import Dependencies
|
import Dependencies
|
||||||
|
import PlaybookClient
|
||||||
|
|
||||||
struct GenerateProjectTemplateCommand: AsyncParsableCommand {
|
struct GenerateProjectTemplateCommand: AsyncParsableCommand {
|
||||||
|
|
||||||
@@ -39,24 +40,16 @@ struct GenerateProjectTemplateCommand: AsyncParsableCommand {
|
|||||||
var extraOptions: [String] = []
|
var extraOptions: [String] = []
|
||||||
|
|
||||||
mutating func run() async throws {
|
mutating func run() async throws {
|
||||||
@Dependency(\.cliClient) var cliClient
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
|
|
||||||
var arguments = [
|
try await playbookClient.run.generateTemplate(.init(
|
||||||
"--tags", "repo-template",
|
shared: globals.sharedPlaybookRunOptions(
|
||||||
"--extra-vars", "output_dir=\(path)"
|
commandName: Self.commandName,
|
||||||
]
|
extraOptions: extraOptions
|
||||||
|
),
|
||||||
if let varsDir = templateVars {
|
templateDirectory: path,
|
||||||
arguments.append(contentsOf: ["--extra-vars", "repo_vars_dir=\(varsDir)"])
|
templateVarsDirectory: templateVars,
|
||||||
}
|
useVault: !noVault
|
||||||
|
))
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ struct PlaybookClientTests: TestCase {
|
|||||||
.init(loggingOptions: loggingOptions)
|
.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))
|
@Test(.tags(.repository))
|
||||||
func repositoryInstallation() async throws {
|
func repositoryInstallation() async throws {
|
||||||
try await withDependencies {
|
try await withDependencies {
|
||||||
@@ -65,9 +69,9 @@ struct PlaybookClientTests: TestCase {
|
|||||||
print(arguments)
|
print(arguments)
|
||||||
|
|
||||||
#expect(arguments == [
|
#expect(arguments == [
|
||||||
"ansible-playbook", "~/.local/share/hpa/playbook/main.yml",
|
"ansible-playbook", Self.defaultPlaybookPath,
|
||||||
"--inventory", "~/.local/share/hpa/playbook/inventory.ini",
|
"--inventory", Self.defaultInventoryPath,
|
||||||
configuration.vault.args!.first!,
|
Self.mockVaultArg,
|
||||||
"--tags", "build-project",
|
"--tags", "build-project",
|
||||||
"--extra-vars", "project_dir=/foo"
|
"--extra-vars", "project_dir=/foo"
|
||||||
])
|
])
|
||||||
@@ -102,9 +106,9 @@ struct PlaybookClientTests: TestCase {
|
|||||||
logger.debug("\(arguments)")
|
logger.debug("\(arguments)")
|
||||||
|
|
||||||
#expect(arguments == [
|
#expect(arguments == [
|
||||||
"ansible-playbook", "~/.local/share/hpa/playbook/main.yml",
|
"ansible-playbook", Self.defaultPlaybookPath,
|
||||||
"--inventory", "~/.local/share/hpa/playbook/inventory.ini",
|
"--inventory", Self.defaultInventoryPath,
|
||||||
configuration.vault.args!.first!,
|
Self.mockVaultArg,
|
||||||
"--tags", "setup-project",
|
"--tags", "setup-project",
|
||||||
"--extra-vars", "project_dir=/project",
|
"--extra-vars", "project_dir=/project",
|
||||||
"--extra-vars", json
|
"--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(
|
func withMockConfiguration(
|
||||||
_ capturing: CommandClient.CapturingClient,
|
_ capturing: CommandClient.CapturingClient,
|
||||||
configuration: Configuration = .mock,
|
configuration: Configuration = .mock,
|
||||||
|
|||||||
Reference in New Issue
Block a user