feat: Adds vault client
This commit is contained in:
@@ -59,6 +59,16 @@ extension ConfigurationClient: DependencyKey {
|
||||
public static var liveValue: Self {
|
||||
.live(environment: ProcessInfo.processInfo.environment)
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public static func mock(_ configuration: Configuration = .mock) -> Self {
|
||||
.init(
|
||||
find: { throw MockFindError() },
|
||||
generate: Self().generate,
|
||||
load: { _ in configuration },
|
||||
write: Self().write
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveConfigurationClient {
|
||||
@@ -229,3 +239,5 @@ enum ConfigurationError: Error {
|
||||
case decodingError
|
||||
case fileExists(path: String)
|
||||
}
|
||||
|
||||
struct MockFindError: Error {}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import FileClient
|
||||
import Foundation
|
||||
|
||||
/// Represents a file location and type on disk for a configuration file.
|
||||
@@ -49,11 +50,3 @@ public enum File: Equatable, Sendable {
|
||||
return .toml(fileUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public extension URL {
|
||||
|
||||
var cleanFilePath: String {
|
||||
absoluteString.replacing("file://", with: "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@_spi(Internal) @_exported import CommandClient
|
||||
@_exported import Dependencies
|
||||
import Foundation
|
||||
import Logging
|
||||
@@ -7,6 +8,35 @@ public protocol TestCase {}
|
||||
|
||||
public extension TestCase {
|
||||
|
||||
static var loggingOptions: LoggingOptions {
|
||||
let levelString = ProcessInfo.processInfo.environment["LOG_LEVEL"] ?? "debug"
|
||||
let logLevel = Logger.Level(rawValue: levelString) ?? .debug
|
||||
return .init(commandName: "\(Self.self)", logLevel: logLevel)
|
||||
}
|
||||
|
||||
func withCapturingCommandClient(
|
||||
_ key: String,
|
||||
dependencies setupDependencies: @escaping (inout DependencyValues) -> Void = { _ in },
|
||||
run: @Sendable @escaping () async throws -> Void,
|
||||
assert: @Sendable @escaping (CommandClient.RunCommandOptions) -> Void
|
||||
) async throws {
|
||||
let captured = CommandClient.CapturingClient()
|
||||
try await withDependencies {
|
||||
$0.commandClient = .capturing(captured)
|
||||
setupDependencies(&$0)
|
||||
} operation: {
|
||||
try await Self.loggingOptions.withLogger {
|
||||
try await run()
|
||||
|
||||
guard let options = await captured.options else {
|
||||
throw TestSupportError.optionsNotSet
|
||||
}
|
||||
|
||||
assert(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func withTestLogger(
|
||||
key: String,
|
||||
logLevel: Logger.Level = .debug,
|
||||
@@ -131,3 +161,7 @@ public func withTestLogger(
|
||||
try await operation()
|
||||
}
|
||||
}
|
||||
|
||||
enum TestSupportError: Error {
|
||||
case optionsNotSet
|
||||
}
|
||||
|
||||
164
Sources/VaultClient/VaultClient.swift
Normal file
164
Sources/VaultClient/VaultClient.swift
Normal file
@@ -0,0 +1,164 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user