feat: Moves playbook run into playbook client, need to move tests.
This commit is contained in:
@@ -3,6 +3,9 @@ public extension PlaybookClient {
|
|||||||
@_spi(Internal)
|
@_spi(Internal)
|
||||||
enum Constants {
|
enum Constants {
|
||||||
public static let defaultInstallationPath = "~/.local/share/hpa/playbook"
|
public static let defaultInstallationPath = "~/.local/share/hpa/playbook"
|
||||||
|
public static let inventoryFileName = "inventory.ini"
|
||||||
|
public static let playbookCommand = "ansible-playbook"
|
||||||
|
public static let playbookFileName = "main.yml"
|
||||||
public static let playbookRepoUrl = "https://git.housh.dev/michael/ansible-hpa-playbook.git"
|
public static let playbookRepoUrl = "https://git.housh.dev/michael/ansible-hpa-playbook.git"
|
||||||
public static let playbookRepoVersion = "main"
|
public static let playbookRepoVersion = "main"
|
||||||
}
|
}
|
||||||
|
|||||||
205
Sources/PlaybookClient/PlaybookClient+RunPlaybook.swift
Normal file
205
Sources/PlaybookClient/PlaybookClient+RunPlaybook.swift
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import CommandClient
|
||||||
|
import ConfigurationClient
|
||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension PlaybookClient.RunPlaybook.BuildOptions {
|
||||||
|
|
||||||
|
func run() async throws {
|
||||||
|
try await shared.run { arguments, _ in
|
||||||
|
let projectDirectory = projectDirectory
|
||||||
|
?? ProcessInfo.processInfo.environment["PWD"]
|
||||||
|
|
||||||
|
guard let projectDirectory else {
|
||||||
|
throw PlaybookClientError.projectDirectoryNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments.append(contentsOf: [
|
||||||
|
"--tags", "build-project",
|
||||||
|
"--extra-vars", "project_dir=\(projectDirectory)"
|
||||||
|
])
|
||||||
|
|
||||||
|
if let extraOptions = shared.extraOptions {
|
||||||
|
arguments.append(contentsOf: extraOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PlaybookClient.RunPlaybook.CreateOptions {
|
||||||
|
|
||||||
|
func run() async throws {
|
||||||
|
try await shared.run { arguments, configuration in
|
||||||
|
let json = try createJSONData(configuration: configuration)
|
||||||
|
|
||||||
|
arguments.append(contentsOf: [
|
||||||
|
"--tags", "setup-project",
|
||||||
|
"--extra-vars", "project_dir=\(projectDirectory)",
|
||||||
|
"--extra-vars", "'\(json)'"
|
||||||
|
])
|
||||||
|
|
||||||
|
if let extraOptions = shared.extraOptions {
|
||||||
|
arguments.append(contentsOf: extraOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PlaybookClient.RunPlaybook.SharedRunOptions {
|
||||||
|
|
||||||
|
func run(_ apply: @Sendable @escaping (inout [String], Configuration) throws -> Void) async throws {
|
||||||
|
@Dependency(\.commandClient) var commandClient
|
||||||
|
|
||||||
|
try await commandClient.run(
|
||||||
|
logging: loggingOptions,
|
||||||
|
quiet: quiet,
|
||||||
|
shell: shell
|
||||||
|
) {
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
@Dependency(\.configurationClient) var configurationClient
|
||||||
|
|
||||||
|
let configuration = try await configurationClient.findAndLoad()
|
||||||
|
|
||||||
|
var arguments = try await PlaybookClient.RunPlaybook.makeCommonArguments(
|
||||||
|
configuration: configuration,
|
||||||
|
inventoryFilePath: inventoryFilePath
|
||||||
|
)
|
||||||
|
|
||||||
|
try apply(&arguments, configuration)
|
||||||
|
|
||||||
|
return arguments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@_spi(Internal)
|
||||||
|
public extension PlaybookClient.RunPlaybook {
|
||||||
|
|
||||||
|
static func ensuredInventoryPath(
|
||||||
|
_ optionalInventoryPath: String?,
|
||||||
|
configuration: Configuration,
|
||||||
|
playbookDirectory: String
|
||||||
|
) -> String {
|
||||||
|
guard let path = optionalInventoryPath else {
|
||||||
|
guard let path = configuration.playbook?.inventory else {
|
||||||
|
return "\(playbookDirectory)/\(PlaybookClient.Constants.inventoryFileName)"
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
static func makeCommonArguments(
|
||||||
|
configuration: Configuration,
|
||||||
|
inventoryFilePath: String?
|
||||||
|
) async throws -> [String] {
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
|
||||||
|
let playbookDirectory = try await PlaybookClient.Repository.findDirectory(configuration: configuration)
|
||||||
|
let playbookPath = "\(playbookDirectory)/\(PlaybookClient.Constants.playbookFileName)"
|
||||||
|
logger.trace("Playbook path: \(playbookPath)")
|
||||||
|
|
||||||
|
let inventoryPath = ensuredInventoryPath(
|
||||||
|
inventoryFilePath,
|
||||||
|
configuration: configuration,
|
||||||
|
playbookDirectory: playbookDirectory
|
||||||
|
)
|
||||||
|
logger.trace("Inventory path: \(inventoryPath)")
|
||||||
|
|
||||||
|
var arguments = [
|
||||||
|
PlaybookClient.Constants.playbookCommand, playbookPath,
|
||||||
|
"--inventory", inventoryPath
|
||||||
|
]
|
||||||
|
|
||||||
|
if let defaultArgs = configuration.args {
|
||||||
|
arguments.append(contentsOf: defaultArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configuration.useVaultArgs, let vaultArgs = configuration.vault.args {
|
||||||
|
arguments.append(contentsOf: vaultArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.trace("Common arguments: \(inventoryPath)")
|
||||||
|
return arguments
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// NOTE: We're not using the `Coders` client because we generally do not
|
||||||
|
// want the output to be `prettyPrinted` or anything, unless we're running
|
||||||
|
// tests, so we use a supplied json encoder.
|
||||||
|
//
|
||||||
|
extension PlaybookClient.RunPlaybook.CreateOptions {
|
||||||
|
|
||||||
|
func createJSONData(
|
||||||
|
configuration: Configuration,
|
||||||
|
encoder: JSONEncoder = .init()
|
||||||
|
) throws -> Data {
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
|
||||||
|
let templateDir = template.directory ?? configuration.template.directory
|
||||||
|
let templateRepo = template.url ?? configuration.template.url
|
||||||
|
let version = template.version ?? configuration.template.version
|
||||||
|
|
||||||
|
logger.debug("""
|
||||||
|
(\(useLocalTemplateDirectory), \(String(describing: templateDir)), \(String(describing: templateRepo)))
|
||||||
|
""")
|
||||||
|
|
||||||
|
switch (useLocalTemplateDirectory, templateDir, templateRepo) {
|
||||||
|
case (true, .none, _):
|
||||||
|
// User supplied they wanted to use a local template directory, but we could not find
|
||||||
|
// the path set from command line or in configuration.
|
||||||
|
throw PlaybookClientError.templateDirectoryNotFound
|
||||||
|
case let (false, _, .some(repo)):
|
||||||
|
// User did not supply they wanted to use a local template directory, and we found a repo url that was
|
||||||
|
// either set by the command line or found in the configuration.
|
||||||
|
logger.debug("Using repo.")
|
||||||
|
return try encoder.encode(TemplateRepo(repo: repo, version: version))
|
||||||
|
case let (true, .some(templateDir), _):
|
||||||
|
// User supplied they wanted to use a local template directory, and we found the template directory
|
||||||
|
// either set by the command line or in the configuration.
|
||||||
|
logger.debug("Using template directory.")
|
||||||
|
return try encoder.encode(TemplateDirJson(path: templateDir))
|
||||||
|
case let (false, .some(templateDir), _):
|
||||||
|
// User supplied they did not wanted to use a local template directory, and we found the template directory
|
||||||
|
// either set by the command line or in the configuration, and no repo was found / handled previously.
|
||||||
|
logger.debug("Using template directory.")
|
||||||
|
return try encoder.encode(TemplateDirJson(path: templateDir))
|
||||||
|
case (_, .none, .none):
|
||||||
|
// We could not find a repo or template directory.
|
||||||
|
throw PlaybookClientError.templateDirectoryOrRepoNotSpecified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct TemplateDirJson: Encodable {
|
||||||
|
|
||||||
|
let template: Template
|
||||||
|
|
||||||
|
init(path: String) {
|
||||||
|
self.template = .init(path: path)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Template: Encodable {
|
||||||
|
let path: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct TemplateRepo: Encodable {
|
||||||
|
|
||||||
|
let template: Template
|
||||||
|
|
||||||
|
init(repo: String, version: String?) {
|
||||||
|
self.template = .init(repo: .init(url: repo, version: version ?? "main"))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Template: Encodable {
|
||||||
|
let repo: Repo
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Repo: Encodable {
|
||||||
|
let url: String
|
||||||
|
let version: String
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import CommandClient
|
||||||
import ConfigurationClient
|
import ConfigurationClient
|
||||||
import Dependencies
|
import Dependencies
|
||||||
import DependenciesMacros
|
import DependenciesMacros
|
||||||
@@ -17,6 +18,7 @@ public extension DependencyValues {
|
|||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct PlaybookClient: Sendable {
|
public struct PlaybookClient: Sendable {
|
||||||
public var repository: Repository
|
public var repository: Repository
|
||||||
|
public var run: RunPlaybook
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension PlaybookClient {
|
public extension PlaybookClient {
|
||||||
@@ -33,23 +35,131 @@ public extension PlaybookClient {
|
|||||||
public func directory() async throws -> String {
|
public func directory() async throws -> String {
|
||||||
try await directory(nil)
|
try await directory(nil)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static var liveValue: Self {
|
public extension PlaybookClient {
|
||||||
.init {
|
|
||||||
try await installPlaybook(configuration: $0)
|
@DependencyClient
|
||||||
} directory: {
|
struct RunPlaybook: Sendable {
|
||||||
try await findDirectory(configuration: $0)
|
public var buildProject: @Sendable (BuildOptions) async throws -> Void
|
||||||
|
public var createProject: @Sendable (CreateOptions) async throws -> Void
|
||||||
|
|
||||||
|
public struct SharedRunOptions: Equatable, Sendable {
|
||||||
|
public let extraOptions: [String]?
|
||||||
|
public let inventoryFilePath: String?
|
||||||
|
public let loggingOptions: LoggingOptions
|
||||||
|
public let quiet: Bool
|
||||||
|
public let shell: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
extraOptions: [String]?,
|
||||||
|
inventoryFilePath: String?,
|
||||||
|
loggingOptions: LoggingOptions,
|
||||||
|
quiet: Bool,
|
||||||
|
shell: String?
|
||||||
|
) {
|
||||||
|
self.extraOptions = extraOptions
|
||||||
|
self.inventoryFilePath = inventoryFilePath
|
||||||
|
self.loggingOptions = loggingOptions
|
||||||
|
self.quiet = quiet
|
||||||
|
self.shell = shell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@dynamicMemberLookup
|
||||||
|
public struct BuildOptions: Equatable, Sendable {
|
||||||
|
public let projectDirectory: String?
|
||||||
|
public let shared: SharedRunOptions
|
||||||
|
|
||||||
|
public init(
|
||||||
|
extraOptions: [String]?,
|
||||||
|
inventoryFilePath: String?,
|
||||||
|
loggingOptions: LoggingOptions,
|
||||||
|
quiet: Bool,
|
||||||
|
shell: String?,
|
||||||
|
projectDirectory: String
|
||||||
|
) {
|
||||||
|
self.projectDirectory = projectDirectory
|
||||||
|
self.shared = .init(
|
||||||
|
extraOptions: extraOptions,
|
||||||
|
inventoryFilePath: inventoryFilePath,
|
||||||
|
loggingOptions: loggingOptions,
|
||||||
|
quiet: quiet,
|
||||||
|
shell: shell
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscript<T>(dynamicMember keyPath: KeyPath<SharedRunOptions, T>) -> T {
|
||||||
|
shared[keyPath: keyPath]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@dynamicMemberLookup
|
||||||
|
public struct CreateOptions: Equatable, Sendable {
|
||||||
|
public let projectDirectory: String
|
||||||
|
public let shared: SharedRunOptions
|
||||||
|
public let template: Configuration.Template
|
||||||
|
public let useLocalTemplateDirectory: Bool
|
||||||
|
|
||||||
|
public init(
|
||||||
|
extraOptions: [String]?,
|
||||||
|
inventoryFilePath: String?,
|
||||||
|
loggingOptions: LoggingOptions,
|
||||||
|
projectDirectory: String,
|
||||||
|
quiet: Bool,
|
||||||
|
shell: String?,
|
||||||
|
template: Configuration.Template,
|
||||||
|
useLocalTemplateDirectory: Bool
|
||||||
|
) {
|
||||||
|
self.projectDirectory = projectDirectory
|
||||||
|
self.template = template
|
||||||
|
self.useLocalTemplateDirectory = useLocalTemplateDirectory
|
||||||
|
self.shared = .init(
|
||||||
|
extraOptions: extraOptions,
|
||||||
|
inventoryFilePath: inventoryFilePath,
|
||||||
|
loggingOptions: loggingOptions,
|
||||||
|
quiet: quiet,
|
||||||
|
shell: shell
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscript<T>(dynamicMember keyPath: KeyPath<SharedRunOptions, T>) -> T {
|
||||||
|
shared[keyPath: keyPath]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PlaybookClient: DependencyKey {
|
extension PlaybookClient.Repository: DependencyKey {
|
||||||
public static let testValue: PlaybookClient = Self(repository: Repository())
|
public static var liveValue: Self {
|
||||||
|
.init {
|
||||||
|
try await installPlaybook(configuration: $0)
|
||||||
|
} directory: {
|
||||||
|
try await findDirectory(configuration: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static var liveValue: PlaybookClient {
|
extension PlaybookClient.RunPlaybook: DependencyKey {
|
||||||
|
public static var liveValue: PlaybookClient.RunPlaybook {
|
||||||
.init(
|
.init(
|
||||||
repository: .liveValue
|
buildProject: { try await $0.run() },
|
||||||
|
createProject: { try await $0.run() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PlaybookClient: DependencyKey {
|
||||||
|
public static let testValue: PlaybookClient = Self(
|
||||||
|
repository: Repository(),
|
||||||
|
run: RunPlaybook()
|
||||||
|
)
|
||||||
|
|
||||||
|
public static var liveValue: PlaybookClient {
|
||||||
|
.init(
|
||||||
|
repository: .liveValue,
|
||||||
|
run: .liveValue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
Sources/PlaybookClient/PlaybookClientError.swift
Normal file
7
Sources/PlaybookClient/PlaybookClientError.swift
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum PlaybookClientError: Error {
|
||||||
|
case projectDirectoryNotFound
|
||||||
|
case templateDirectoryNotFound
|
||||||
|
case templateDirectoryOrRepoNotSpecified
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user