feat: Adds generate-config command.
This commit is contained in:
@@ -16,6 +16,7 @@ public struct CliClient: Sendable {
|
|||||||
public var encoder: @Sendable () -> JSONEncoder = { .init() }
|
public var encoder: @Sendable () -> JSONEncoder = { .init() }
|
||||||
public var loadConfiguration: @Sendable () throws -> Configuration
|
public var loadConfiguration: @Sendable () throws -> Configuration
|
||||||
public var runCommand: @Sendable ([String], Bool, ShellCommand.Shell) async throws -> Void
|
public var runCommand: @Sendable ([String], Bool, ShellCommand.Shell) async throws -> Void
|
||||||
|
public var createConfiguration: @Sendable (String) throws -> Void
|
||||||
|
|
||||||
public func runCommand(
|
public func runCommand(
|
||||||
quiet: Bool,
|
quiet: Bool,
|
||||||
@@ -41,14 +42,14 @@ extension CliClient: DependencyKey {
|
|||||||
encoder: JSONEncoder = .init(),
|
encoder: JSONEncoder = .init(),
|
||||||
env: [String: String]
|
env: [String: String]
|
||||||
) -> Self {
|
) -> Self {
|
||||||
.init {
|
@Dependency(\.fileClient) var fileClient
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
|
||||||
|
return .init {
|
||||||
decoder
|
decoder
|
||||||
} encoder: {
|
} encoder: {
|
||||||
encoder
|
encoder
|
||||||
} loadConfiguration: {
|
} loadConfiguration: {
|
||||||
@Dependency(\.logger) var logger
|
|
||||||
@Dependency(\.fileClient) var fileClient
|
|
||||||
|
|
||||||
let urls = try findConfigurationFiles(env: env)
|
let urls = try findConfigurationFiles(env: env)
|
||||||
var env = env
|
var env = env
|
||||||
|
|
||||||
@@ -75,6 +76,8 @@ extension CliClient: DependencyKey {
|
|||||||
in: nil,
|
in: nil,
|
||||||
args
|
args
|
||||||
))
|
))
|
||||||
|
} createConfiguration: { path in
|
||||||
|
try fileClient.write(path, Data(Configuration.fileTemplate.utf8))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,4 +40,27 @@ public struct Configuration: Decodable {
|
|||||||
let data = try encoder.encode(env)
|
let data = try encoder.encode(env)
|
||||||
return try decoder.decode(Configuration.self, from: data)
|
return try decoder.decode(Configuration.self, from: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var fileTemplate: String {
|
||||||
|
"""
|
||||||
|
# Example configuration, uncomment the lines and set the values appropriate for your
|
||||||
|
# usage.
|
||||||
|
|
||||||
|
# Set this to the location of the ansible-hpa-playbook on your local machine.
|
||||||
|
#HPA_PLAYBOOK_DIR="/path/to/ansible-hpa-playbook"
|
||||||
|
|
||||||
|
# Set this to the location of a template repository, which is used to create new assessment projects.
|
||||||
|
#HPA_TEMPLATE_REPO="https://git.example.com/your/template.git"
|
||||||
|
|
||||||
|
# Specify a branch, version, or sha of the template repository.
|
||||||
|
#HPA_TEMPLATE_VERSION="main" # branch, version, or sha
|
||||||
|
|
||||||
|
# Set this to a location of a template directory to use to create new projects.
|
||||||
|
#HPA_TEMPLATE_DIR="/path/to/local/template"
|
||||||
|
|
||||||
|
# Extra arguments that get passed directly to the ansible-playbook command.
|
||||||
|
#HPA_DEFAULT_PLAYBOOK_ARGS="--vault-id=consults@$SCRIPTS/vault-gopass-client"
|
||||||
|
|
||||||
|
"""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public struct FileClient: Sendable {
|
|||||||
public var homeDir: @Sendable () -> URL = { URL(string: "~/")! }
|
public var homeDir: @Sendable () -> URL = { URL(string: "~/")! }
|
||||||
public var isDirectory: @Sendable (URL) -> Bool = { _ in false }
|
public var isDirectory: @Sendable (URL) -> Bool = { _ in false }
|
||||||
public var isReadable: @Sendable (URL) -> Bool = { _ in false }
|
public var isReadable: @Sendable (URL) -> Bool = { _ in false }
|
||||||
|
public var write: @Sendable (String, Data) throws -> Void
|
||||||
}
|
}
|
||||||
|
|
||||||
@_spi(Internal)
|
@_spi(Internal)
|
||||||
@@ -31,7 +32,10 @@ extension FileClient: DependencyKey {
|
|||||||
loadFile: { try client.loadFile(at: $0, into: &$1, decoder: $2) },
|
loadFile: { try client.loadFile(at: $0, into: &$1, decoder: $2) },
|
||||||
homeDir: { client.homeDir },
|
homeDir: { client.homeDir },
|
||||||
isDirectory: { client.isDirectory(url: $0) },
|
isDirectory: { client.isDirectory(url: $0) },
|
||||||
isReadable: { client.isReadable(url: $0) }
|
isReadable: { client.isReadable(url: $0) },
|
||||||
|
write: { path, data in
|
||||||
|
try data.write(to: URL(filePath: path))
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ struct Application: AsyncParsableCommand {
|
|||||||
static let configuration = CommandConfiguration(
|
static let configuration = CommandConfiguration(
|
||||||
commandName: "hpa",
|
commandName: "hpa",
|
||||||
abstract: "A utility for working with ansible hpa playbook.",
|
abstract: "A utility for working with ansible hpa playbook.",
|
||||||
subcommands: [BuildCommand.self, CreateProjectCommand.self, CreateProjectTemplateCommand.self]
|
subcommands: [
|
||||||
|
BuildCommand.self, CreateCommand.self, CreateProjectTemplateCommand.self,
|
||||||
|
GenerateConfigurationCommand.self
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Dependencies
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Logging
|
import Logging
|
||||||
|
|
||||||
struct CreateProjectCommand: AsyncParsableCommand {
|
struct CreateCommand: AsyncParsableCommand {
|
||||||
|
|
||||||
static let commandName = "create"
|
static let commandName = "create"
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ struct CreateProjectCommand: AsyncParsableCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func parseOptions(
|
private func parseOptions(
|
||||||
command: CreateProjectCommand,
|
command: CreateCommand,
|
||||||
configuration: Configuration,
|
configuration: Configuration,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
encoder: JSONEncoder
|
encoder: JSONEncoder
|
||||||
54
Sources/hpa/GenerateConfigCommand.swift
Normal file
54
Sources/hpa/GenerateConfigCommand.swift
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import ArgumentParser
|
||||||
|
import CliClient
|
||||||
|
import Dependencies
|
||||||
|
|
||||||
|
struct GenerateConfigurationCommand: AsyncParsableCommand {
|
||||||
|
|
||||||
|
static let commandName = "generate-config"
|
||||||
|
|
||||||
|
static let configuration = CommandConfiguration(
|
||||||
|
commandName: commandName,
|
||||||
|
abstract: "\("Generate a local configuration file.".blue)",
|
||||||
|
discussion: """
|
||||||
|
|
||||||
|
If a directory is not supplied then a configuration file will be created
|
||||||
|
at \("'~/.config/hpa-playbook/config'".yellow).
|
||||||
|
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
@Option(
|
||||||
|
name: .shortAndLong,
|
||||||
|
help: "Directory to generate the configuration in.",
|
||||||
|
completion: .directory
|
||||||
|
)
|
||||||
|
var path: String?
|
||||||
|
|
||||||
|
@OptionGroup var globals: BasicGlobalOptions
|
||||||
|
|
||||||
|
mutating func run() async throws {
|
||||||
|
try await _run()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func _run() async throws {
|
||||||
|
try await withSetupLogger(commandName: Self.commandName, globals: globals) {
|
||||||
|
@Dependency(\.cliClient) var cliClient
|
||||||
|
|
||||||
|
let actualPath: String
|
||||||
|
|
||||||
|
if let path {
|
||||||
|
actualPath = "\(path)/config"
|
||||||
|
} else {
|
||||||
|
let path = "~/.config/hpa-playbook/"
|
||||||
|
try await cliClient.runCommand(
|
||||||
|
quiet: false,
|
||||||
|
shell: globals.shellOrDefault,
|
||||||
|
"mkdir", "-p", path
|
||||||
|
)
|
||||||
|
actualPath = "\(path)/config"
|
||||||
|
}
|
||||||
|
|
||||||
|
try cliClient.createConfiguration(actualPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,27 @@
|
|||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
|
|
||||||
|
struct BasicGlobalOptions: ParsableArguments {
|
||||||
|
@Flag(
|
||||||
|
name: .shortAndLong,
|
||||||
|
help: "Increase logging level (can be passed multiple times)."
|
||||||
|
)
|
||||||
|
var verbose: Int
|
||||||
|
|
||||||
|
@Flag(
|
||||||
|
name: .shortAndLong,
|
||||||
|
help: "Supress logging."
|
||||||
|
)
|
||||||
|
var quiet = false
|
||||||
|
|
||||||
|
@Option(
|
||||||
|
name: .shortAndLong,
|
||||||
|
help: "Optional shell to use when calling shell commands.",
|
||||||
|
completion: .file()
|
||||||
|
)
|
||||||
|
var shell: String?
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
struct GlobalOptions: ParsableArguments {
|
struct GlobalOptions: ParsableArguments {
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
@@ -14,29 +36,12 @@ struct GlobalOptions: ParsableArguments {
|
|||||||
)
|
)
|
||||||
var inventoryPath: String?
|
var inventoryPath: String?
|
||||||
|
|
||||||
@Flag(
|
|
||||||
name: .shortAndLong,
|
|
||||||
help: "Increase logging level (can be passed multiple times)."
|
|
||||||
)
|
|
||||||
var verbose: Int
|
|
||||||
|
|
||||||
@Flag(
|
|
||||||
name: .shortAndLong,
|
|
||||||
help: "Supress logging."
|
|
||||||
)
|
|
||||||
var quiet = false
|
|
||||||
|
|
||||||
@Flag(
|
@Flag(
|
||||||
name: .long,
|
name: .long,
|
||||||
help: "Supress only playbook logging."
|
help: "Supress only playbook logging."
|
||||||
)
|
)
|
||||||
var quietOnlyPlaybook = false
|
var quietOnlyPlaybook = false
|
||||||
|
|
||||||
@Option(
|
@OptionGroup var basic: BasicGlobalOptions
|
||||||
name: .shortAndLong,
|
|
||||||
help: "Optional shell to use when calling shell commands.",
|
|
||||||
completion: .file()
|
|
||||||
)
|
|
||||||
var shell: String?
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ extension CommandConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension GlobalOptions {
|
extension BasicGlobalOptions {
|
||||||
|
|
||||||
var shellOrDefault: ShellCommand.Shell {
|
var shellOrDefault: ShellCommand.Shell {
|
||||||
guard let shell else { return .zsh(useDashC: true) }
|
guard let shell else { return .zsh(useDashC: true) }
|
||||||
@@ -48,6 +48,11 @@ extension GlobalOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension GlobalOptions {
|
||||||
|
|
||||||
|
var shellOrDefault: ShellCommand.Shell { basic.shellOrDefault }
|
||||||
|
}
|
||||||
|
|
||||||
func ensureString(
|
func ensureString(
|
||||||
globals: GlobalOptions,
|
globals: GlobalOptions,
|
||||||
configuration: Configuration,
|
configuration: Configuration,
|
||||||
@@ -65,13 +70,14 @@ func ensureString(
|
|||||||
|
|
||||||
func withSetupLogger(
|
func withSetupLogger(
|
||||||
commandName: String,
|
commandName: String,
|
||||||
globals: GlobalOptions,
|
globals: BasicGlobalOptions,
|
||||||
|
quietOnlyPlaybook: Bool = false,
|
||||||
dependencies setupDependencies: (inout DependencyValues) -> Void = { _ in },
|
dependencies setupDependencies: (inout DependencyValues) -> Void = { _ in },
|
||||||
operation: @escaping () async throws -> Void
|
operation: @escaping () async throws -> Void
|
||||||
) async rethrows {
|
) async rethrows {
|
||||||
try await withDependencies {
|
try await withDependencies {
|
||||||
$0.logger = .init(label: "\("hpa".yellow)")
|
$0.logger = .init(label: "\("hpa".yellow)")
|
||||||
if globals.quietOnlyPlaybook || !globals.quiet {
|
if quietOnlyPlaybook || !globals.quiet {
|
||||||
switch globals.verbose {
|
switch globals.verbose {
|
||||||
case 0:
|
case 0:
|
||||||
$0.logger.logLevel = .info
|
$0.logger.logLevel = .info
|
||||||
@@ -89,6 +95,21 @@ func withSetupLogger(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withSetupLogger(
|
||||||
|
commandName: String,
|
||||||
|
globals: GlobalOptions,
|
||||||
|
dependencies setupDependencies: (inout DependencyValues) -> Void = { _ in },
|
||||||
|
operation: @escaping () async throws -> Void
|
||||||
|
) async rethrows {
|
||||||
|
try await withSetupLogger(
|
||||||
|
commandName: commandName,
|
||||||
|
globals: globals.basic,
|
||||||
|
quietOnlyPlaybook: globals.quietOnlyPlaybook,
|
||||||
|
dependencies: setupDependencies,
|
||||||
|
operation: operation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func runPlaybook(
|
func runPlaybook(
|
||||||
commandName: String,
|
commandName: String,
|
||||||
globals: GlobalOptions,
|
globals: GlobalOptions,
|
||||||
@@ -156,7 +177,7 @@ func runPlaybook(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try await cliClient.runCommand(
|
try await cliClient.runCommand(
|
||||||
quiet: globals.quietOnlyPlaybook ? true : globals.quiet,
|
quiet: globals.quietOnlyPlaybook ? true : globals.basic.quiet,
|
||||||
shell: globals.shellOrDefault,
|
shell: globals.shellOrDefault,
|
||||||
playbookArgs + args + extraArgs
|
playbookArgs + args + extraArgs
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user