Files
swift-hpa/Sources/PlaybookClient/PlaybookClient+RunPlaybook.swift

241 lines
7.1 KiB
Swift

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(encoder jsonEncoder: JSONEncoder?) async throws {
try await shared.run { arguments, configuration in
let jsonData = try createJSONData(
configuration: configuration,
encoder: jsonEncoder
)
guard let json = String(data: jsonData, encoding: .utf8) else {
throw PlaybookClientError.encodingError
}
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.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 {
@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.
//
@_spi(Internal)
public extension PlaybookClient.RunPlaybook.CreateOptions {
func createJSONData(
configuration: Configuration,
encoder: JSONEncoder?
) throws -> Data {
@Dependency(\.logger) var logger
let encoder = encoder ?? jsonEncoder
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
}
}
private let jsonEncoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.outputFormatting = [.withoutEscapingSlashes, .sortedKeys]
return encoder
}()