import ConfigurationClient import Dependencies import DependenciesMacros import FileClient import Foundation import ShellClient public extension CliClient { func runCommand( quiet: Bool, shell: ShellCommand.Shell, _ args: [String] ) async throws { try await runCommand(.init(arguments: args, quiet: quiet, shell: shell)) } func runCommand( quiet: Bool, shell: ShellCommand.Shell, _ args: String... ) async throws { try await runCommand(quiet: quiet, shell: shell, args) } func runPlaybookCommand(_ options: PlaybookOptions) async throws { @Dependency(\.configurationClient) var configurationClient @Dependency(\.logger) var logger let configuration = try await configurationClient.ensuredConfiguration(options.configuration) logger.trace("Configuration: \(configuration)") let playbookDirectory = try configuration.ensuredPlaybookDirectory(options.playbookDirectory) let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)" logger.trace("Playbook path: \(playbookPath)") let inventoryPath = ensuredInventoryPath( options.inventoryFilePath, configuration: configuration, playbookDirectory: playbookDirectory ) logger.trace("Inventory path: \(inventoryPath)") var arguments = [ Constants.playbookCommand, playbookPath, "--inventory", inventoryPath ] + options.arguments if let defaultArgs = configuration.args { arguments.append(contentsOf: defaultArgs) } if configuration.useVaultArgs, let vaultArgs = configuration.vault.args { arguments.append(contentsOf: vaultArgs) } logger.trace("Running playbook command with arguments: \(arguments)") try await runCommand( quiet: options.quiet, shell: options.shell.orDefault, arguments ) } func runVaultCommand(_ options: VaultOptions) async throws { @Dependency(\.configurationClient) var configurationClient @Dependency(\.fileClient) var fileClient @Dependency(\.logger) var logger let configuration = try await configurationClient.ensuredConfiguration(options.configuration) logger.trace("Configuration: \(configuration)") let vaultFilePath = try await fileClient.ensuredVaultFilePath(options.vaultFilePath) logger.trace("Vault file: \(vaultFilePath)") var arguments = [ Constants.vaultCommand ] + options.arguments if let defaultArgs = configuration.vault.args { arguments.append(contentsOf: defaultArgs) } 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("Running vault command with arguments: \(arguments)") try await runCommand( quiet: options.quiet, shell: options.shell.orDefault, arguments ) } } @_spi(Internal) public extension ConfigurationClient { func ensuredConfiguration(_ optionalConfig: Configuration?) async throws -> Configuration { guard let config = optionalConfig else { return try await findAndLoad() } return config } } @_spi(Internal) public extension Configuration { func ensuredPlaybookDirectory(_ optionalDirectory: String?) throws -> String { guard let directory = optionalDirectory else { guard let directory = playbook?.directory else { throw CliClientError.playbookDirectoryNotFound } return directory } return directory } } @_spi(Internal) public extension Optional where Wrapped == String { var orDefault: ShellCommand.Shell { guard let shell = self else { return .zsh(useDashC: true) } return .custom(path: shell, useDashC: true) } } @_spi(Internal) public func ensuredInventoryPath( _ optionalInventoryPath: String?, configuration: Configuration, playbookDirectory: String ) -> String { guard let path = optionalInventoryPath else { guard let path = configuration.playbook?.inventory else { return "\(playbookDirectory)/\(Constants.inventoryFileName)" } return path } return path } @_spi(Internal) public extension FileClient { func ensuredVaultFilePath(_ optionalPath: String?) async throws -> String { guard let path = optionalPath else { guard let url = try await findVaultFileInCurrentDirectory() else { throw CliClientError.vaultFileNotFound } return url.cleanFilePath } return path } } enum CliClientError: Error { case playbookDirectoryNotFound case vaultFileNotFound } extension ShellCommand.Shell: @retroactive @unchecked Sendable {}