Files
swift-hpa/Sources/VaultClient/VaultClient.swift
Michael Housh faa28749bc
All checks were successful
CI / Run Tests (push) Successful in 2m43s
feat: Merges dev
2024-12-17 15:55:36 -05:00

178 lines
4.9 KiB
Swift

import CommandClient
import ConfigurationClient
import Dependencies
import DependenciesMacros
import FileClient
public extension DependencyValues {
/// Manages interactions with `ansible-vault` command line application.
var vaultClient: VaultClient {
get { self[VaultClient.self] }
set { self[VaultClient.self] = newValue }
}
}
@DependencyClient
public struct VaultClient: Sendable {
/// Run an `ansible-vault` command.
public var run: Run
@DependencyClient
public struct Run: Sendable {
/// Decrypt an `ansible-vault` file.
public var decrypt: @Sendable (RunOptions) async throws -> String
/// Encrypt an `ansible-vault` file.
public var encrypt: @Sendable (RunOptions) async throws -> String
}
public struct RunOptions: Equatable, Sendable {
/// Extra arguments / options passed to the `ansible-vault` command.
let extraOptions: [String]?
/// Logging options to use while running the command.
let loggingOptions: LoggingOptions
/// The optional output file path.
let outputFilePath: String?
/// Disable logging output.
let quiet: Bool
/// Optional shell used to call the command.
let shell: String?
/// The path to the vault file, if not supplied we will search the current directory for it.
let vaultFilePath: String?
/// Create new run options.
///
/// - Parameters:
/// - extraOptions: Extra arguments / options passed to the command.
/// - loggingOptions: The logging options used while running the command.
/// - outputFilePath: The optional output file path.
/// - quiet: Disable logging output of the command.
/// - shell: The shell used to call the command.
/// - vaultFilePath: The vault file, if not supplied we will search in the current working directory.
public init(
extraOptions: [String]? = nil,
loggingOptions: LoggingOptions,
outputFilePath: String? = nil,
quiet: Bool = false,
shell: String? = nil,
vaultFilePath: String? = nil
) {
self.extraOptions = extraOptions
self.loggingOptions = loggingOptions
self.outputFilePath = outputFilePath
self.quiet = quiet
self.shell = shell
self.vaultFilePath = vaultFilePath
}
}
@_spi(Internal)
public enum Route: String, Equatable, Sendable {
case decrypt
case encrypt
public var verb: String { rawValue }
}
}
extension VaultClient: DependencyKey {
public static let testValue: VaultClient = Self(run: Run())
public static var liveValue: VaultClient {
.init(
run: .init(
decrypt: { try await $0.run(route: .decrypt) },
encrypt: { try await $0.run(route: .encrypt) }
)
)
}
}
@_spi(Internal)
public extension VaultClient {
enum Constants {
public static let vaultCommand = "ansible-vault"
}
}
extension VaultClient.RunOptions {
// Sets up the default arguments and runs the `ansible-vault` command,
// returning the output file path, which is either supplied by the caller
// or found via the `fileClient.findVaultFileInCurrentDirectory`.
//
// This allows the output to be piped into other commands.
//
//
@discardableResult
func run(route: VaultClient.Route) async throws -> String {
@Dependency(\.commandClient) var commandClient
return try await commandClient.run(
logging: loggingOptions,
quiet: quiet,
shell: shell
) {
@Dependency(\.configurationClient) var configurationClient
@Dependency(\.fileClient) var fileClient
@Dependency(\.logger) var logger
var output: String?
let configuration = try await configurationClient.findAndLoad()
logger.trace("Configuration: \(configuration)")
var vaultFilePath: String? = vaultFilePath
if vaultFilePath == nil {
vaultFilePath = try await fileClient
.findVaultFileInCurrentDirectory()?
.cleanFilePath
}
guard let vaultFilePath else {
throw VaultClientError.vaultFileNotFound
}
output = vaultFilePath
logger.trace("Vault file: \(vaultFilePath)")
var arguments = [
VaultClient.Constants.vaultCommand,
route.verb
]
if let outputFilePath {
arguments.append(contentsOf: ["--output", outputFilePath])
output = outputFilePath
}
if let extraOptions {
arguments.append(contentsOf: extraOptions)
}
if let vaultArgs = configuration.vault.args {
arguments.append(contentsOf: vaultArgs)
}
if arguments.contains("encrypt"),
!arguments.contains("--encrypt-vault-id"),
let id = configuration.vault.encryptId
{
arguments.append(contentsOf: ["--encrypt-vault-id", id])
}
arguments.append(vaultFilePath)
logger.trace("Arguments: \(arguments)")
return (arguments, output ?? "")
}
}
}
enum VaultClientError: Error {
case vaultFileNotFound
}