import CommandClient import ConfigurationClient import Dependencies import DependenciesMacros import FileClient // TODO: Add edit / view routes, possibly create? public extension DependencyValues { var vaultClient: VaultClient { get { self[VaultClient.self] } set { self[VaultClient.self] = newValue } } } @DependencyClient public struct VaultClient: Sendable { public var run: @Sendable (RunOptions) async throws -> Void public struct RunOptions: Equatable, Sendable { public let extraOptions: [String]? public let loggingOptions: LoggingOptions public let outputFilePath: String? public let quiet: Bool public let route: Route public let shell: String? public let vaultFilePath: String? public init( _ route: Route, 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.route = route self.shell = shell self.vaultFilePath = vaultFilePath } public enum Route: String, Equatable, Sendable { case encrypt case decrypt @_spi(Internal) public var verb: String { rawValue } } } } extension VaultClient: DependencyKey { public static let testValue: VaultClient = Self() public static var liveValue: VaultClient { .init( run: { try await $0.run() } ) } } @_spi(Internal) public extension VaultClient { enum Constants { public static let vaultCommand = "ansible-vault" } } extension VaultClient.RunOptions { func run() async throws { @Dependency(\.commandClient) var commandClient try await commandClient.run( logging: loggingOptions, quiet: quiet, shell: shell ) { @Dependency(\.configurationClient) var configurationClient @Dependency(\.fileClient) var fileClient @Dependency(\.logger) var logger 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 } logger.trace("Vault file: \(vaultFilePath)") var arguments = [ VaultClient.Constants.vaultCommand, route.verb ] if let outputFilePath { arguments.append(contentsOf: ["--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 } } } // extension VaultClient.RunOptions.Route { // // var arguments: [String] { // let outputFile: String? // var arguments: [String] // // switch self { // case let .decrypt(outputFile: output): // outputFile = output // arguments = ["decrypt"] // case let .encrypt(outputFile: output): // outputFile = output // arguments = ["encrypt"] // } // // if let outputFile { // arguments.append(contentsOf: ["--output", outputFile]) // } // return arguments // } // } enum VaultClientError: Error { case vaultFileNotFound }