feat: Begins adding docker containers
This commit is contained in:
@@ -4,6 +4,8 @@ import Foundation
|
|||||||
import TOMLKit
|
import TOMLKit
|
||||||
|
|
||||||
public extension DependencyValues {
|
public extension DependencyValues {
|
||||||
|
|
||||||
|
/// Holds onto decoders and encoders for json and toml files.
|
||||||
var coders: Coders {
|
var coders: Coders {
|
||||||
get { self[Coders.self] }
|
get { self[Coders.self] }
|
||||||
set { self[Coders.self] = newValue }
|
set { self[Coders.self] = newValue }
|
||||||
|
|||||||
@@ -204,7 +204,12 @@ extension ShellCommand.Shell {
|
|||||||
if let path {
|
if let path {
|
||||||
self = .custom(path: path, useDashC: true)
|
self = .custom(path: path, useDashC: true)
|
||||||
} else {
|
} else {
|
||||||
self = .zsh(useDashC: true)
|
#if os(macOS)
|
||||||
|
self = .zsh(useDashC: true)
|
||||||
|
#else
|
||||||
|
// Generally we're in a docker container when this occurs, which does not have `zsh` installed.
|
||||||
|
self = .sh(useDashC: true)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,28 @@ import Foundation
|
|||||||
|
|
||||||
// NOTE: When adding items, then the 'hpa.toml' resource file needs to be updated.
|
// NOTE: When adding items, then the 'hpa.toml' resource file needs to be updated.
|
||||||
|
|
||||||
/// Represents configurable settings for the command line tool.
|
/// Represents configurable settings for the application.
|
||||||
public struct Configuration: Codable, Equatable, Sendable {
|
public struct Configuration: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
/// Default arguments / options that can get passed into
|
||||||
|
/// ansible-playbook commands.
|
||||||
public let args: [String]?
|
public let args: [String]?
|
||||||
|
|
||||||
|
/// Whether to use the vault arguments as defaults that get passed into
|
||||||
|
/// the ansible-playbook commands.
|
||||||
public let useVaultArgs: Bool
|
public let useVaultArgs: Bool
|
||||||
|
|
||||||
|
/// Configuration for when generating files from templated directories.
|
||||||
public let generate: Generate?
|
public let generate: Generate?
|
||||||
|
|
||||||
|
/// Configuration of the ansible-playbook, these are more for developing the
|
||||||
|
/// playbook locally and generally not needed by the user.
|
||||||
public let playbook: Playbook?
|
public let playbook: Playbook?
|
||||||
|
|
||||||
|
/// Template configuration options.
|
||||||
public let template: Template
|
public let template: Template
|
||||||
|
|
||||||
|
/// Ansible-vault configuration options.
|
||||||
public let vault: Vault
|
public let vault: Vault
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
|||||||
@@ -3,23 +3,47 @@ import DependenciesMacros
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public extension DependencyValues {
|
public extension DependencyValues {
|
||||||
|
/// Represents interactions with the file system.
|
||||||
|
///
|
||||||
var fileClient: FileClient {
|
var fileClient: FileClient {
|
||||||
get { self[FileClient.self] }
|
get { self[FileClient.self] }
|
||||||
set { self[FileClient.self] = newValue }
|
set { self[FileClient.self] = newValue }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents interactions with the file system.
|
||||||
|
///
|
||||||
|
///
|
||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct FileClient: Sendable {
|
public struct FileClient: Sendable {
|
||||||
|
|
||||||
|
/// Copy an item from one location to another.
|
||||||
public var copy: @Sendable (URL, URL) async throws -> Void
|
public var copy: @Sendable (URL, URL) async throws -> Void
|
||||||
|
|
||||||
|
/// Create a directory at the given location.
|
||||||
public var createDirectory: @Sendable (URL) async throws -> Void
|
public var createDirectory: @Sendable (URL) async throws -> Void
|
||||||
|
|
||||||
|
/// Check if a file exists at the given location.
|
||||||
public var fileExists: @Sendable (URL) -> Bool = { _ in true }
|
public var fileExists: @Sendable (URL) -> Bool = { _ in true }
|
||||||
|
|
||||||
|
/// Find an ansible-vault file in the given location, checking up to 1 level deep
|
||||||
|
/// in subfolders.
|
||||||
public var findVaultFile: @Sendable (URL) async throws -> URL?
|
public var findVaultFile: @Sendable (URL) async throws -> URL?
|
||||||
|
|
||||||
|
/// Return the user's home directory.
|
||||||
public var homeDirectory: @Sendable () -> URL = { URL(filePath: "~/") }
|
public var homeDirectory: @Sendable () -> URL = { URL(filePath: "~/") }
|
||||||
|
|
||||||
|
/// Check if an item is a directory or not.
|
||||||
public var isDirectory: @Sendable (URL) async throws -> Bool
|
public var isDirectory: @Sendable (URL) async throws -> Bool
|
||||||
|
|
||||||
|
/// Load a file from the given location.
|
||||||
public var load: @Sendable (URL) async throws -> Data
|
public var load: @Sendable (URL) async throws -> Data
|
||||||
|
|
||||||
|
/// Write data to a file at the given location.
|
||||||
public var write: @Sendable (Data, URL) async throws -> Void
|
public var write: @Sendable (Data, URL) async throws -> Void
|
||||||
|
|
||||||
|
/// Find an ansible-vault file in the current directory, checking up to 1 level
|
||||||
|
/// deep in subfolders.
|
||||||
public func findVaultFileInCurrentDirectory() async throws -> URL? {
|
public func findVaultFileInCurrentDirectory() async throws -> URL? {
|
||||||
try await findVaultFile(URL(filePath: "./"))
|
try await findVaultFile(URL(filePath: "./"))
|
||||||
}
|
}
|
||||||
@@ -30,23 +54,16 @@ extension FileClient: DependencyKey {
|
|||||||
|
|
||||||
public static var liveValue: Self {
|
public static var liveValue: Self {
|
||||||
let manager = LiveFileClient()
|
let manager = LiveFileClient()
|
||||||
return .init {
|
return .init(
|
||||||
try await manager.copy($0, to: $1)
|
copy: { try await manager.copy($0, to: $1) },
|
||||||
} createDirectory: {
|
createDirectory: { try await manager.creatDirectory($0) },
|
||||||
try await manager.creatDirectory($0)
|
fileExists: { manager.fileExists(at: $0) },
|
||||||
} fileExists: { url in
|
findVaultFile: { try await manager.findVaultFile(in: $0) },
|
||||||
manager.fileExists(at: url)
|
homeDirectory: { manager.homeDirectory() },
|
||||||
} findVaultFile: {
|
isDirectory: { manager.isDirectory($0) },
|
||||||
try await manager.findVaultFile(in: $0)
|
load: { try await manager.load(from: $0) },
|
||||||
} homeDirectory: {
|
write: { try await manager.write($0, to: $1) }
|
||||||
manager.homeDirectory()
|
)
|
||||||
} isDirectory: {
|
|
||||||
manager.isDirectory($0)
|
|
||||||
} load: { url in
|
|
||||||
try await manager.load(from: url)
|
|
||||||
} write: { data, url in
|
|
||||||
try await manager.write(data, to: url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ struct ConfigurationClientTests: TestCase {
|
|||||||
@Test(arguments: ["config.toml", "config.json"])
|
@Test(arguments: ["config.toml", "config.json"])
|
||||||
func generateConfigFile(fileName: String) async throws {
|
func generateConfigFile(fileName: String) async throws {
|
||||||
try await withTestLogger(key: "generateConfigFile") {
|
try await withTestLogger(key: "generateConfigFile") {
|
||||||
|
$0.coders = .liveValue
|
||||||
$0.fileClient = .liveValue
|
$0.fileClient = .liveValue
|
||||||
} operation: {
|
} operation: {
|
||||||
@Dependency(\.logger) var logger
|
@Dependency(\.logger) var logger
|
||||||
@@ -53,6 +54,7 @@ struct ConfigurationClientTests: TestCase {
|
|||||||
@Test(arguments: ["config.toml", "config.json", nil])
|
@Test(arguments: ["config.toml", "config.json", nil])
|
||||||
func loadConfigFile(fileName: String?) async throws {
|
func loadConfigFile(fileName: String?) async throws {
|
||||||
try await withTestLogger(key: "generateConfigFile") {
|
try await withTestLogger(key: "generateConfigFile") {
|
||||||
|
$0.coders = .liveValue
|
||||||
$0.fileClient = .liveValue
|
$0.fileClient = .liveValue
|
||||||
} operation: {
|
} operation: {
|
||||||
@Dependency(\.logger) var logger
|
@Dependency(\.logger) var logger
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ struct FileClientTests {
|
|||||||
let fileClient = FileClient.liveValue
|
let fileClient = FileClient.liveValue
|
||||||
|
|
||||||
let vaultFilePath = url.appending(path: fileName)
|
let vaultFilePath = url.appending(path: fileName)
|
||||||
try FileManager.default.createFile(atPath: vaultFilePath.cleanFilePath, contents: nil)
|
FileManager.default.createFile(atPath: vaultFilePath.cleanFilePath, contents: nil)
|
||||||
let output = try await fileClient.findVaultFile(url)!
|
let output = try await fileClient.findVaultFile(url)!
|
||||||
|
|
||||||
#expect(output.cleanFilePath == vaultFilePath.cleanFilePath)
|
#expect(output.cleanFilePath == vaultFilePath.cleanFilePath)
|
||||||
@@ -43,7 +43,7 @@ struct FileClientTests {
|
|||||||
try await fileClient.createDirectory(subDir)
|
try await fileClient.createDirectory(subDir)
|
||||||
|
|
||||||
let vaultFilePath = subDir.appending(path: fileName)
|
let vaultFilePath = subDir.appending(path: fileName)
|
||||||
try FileManager.default.createFile(atPath: vaultFilePath.cleanFilePath, contents: nil)
|
FileManager.default.createFile(atPath: vaultFilePath.cleanFilePath, contents: nil)
|
||||||
let output = try await fileClient.findVaultFile(url)!
|
let output = try await fileClient.findVaultFile(url)!
|
||||||
|
|
||||||
#expect(output.cleanFilePath == vaultFilePath.cleanFilePath)
|
#expect(output.cleanFilePath == vaultFilePath.cleanFilePath)
|
||||||
|
|||||||
@@ -22,12 +22,14 @@ struct PlaybookClientTests: TestCase {
|
|||||||
|
|
||||||
@Test(.tags(.repository))
|
@Test(.tags(.repository))
|
||||||
func repositoryInstallation() async throws {
|
func repositoryInstallation() async throws {
|
||||||
try await withDependencies {
|
try await withTestLogger(key: "repositoryInstallation") {
|
||||||
$0.fileClient = .liveValue
|
$0.fileClient = .liveValue
|
||||||
$0.asyncShellClient = .liveValue
|
$0.asyncShellClient = .liveValue
|
||||||
$0.commandClient = .liveValue
|
$0.commandClient = .liveValue
|
||||||
} operation: {
|
} operation: {
|
||||||
try await withTemporaryDirectory { tempDirectory in
|
try await withTemporaryDirectory { tempDirectory in
|
||||||
|
@Dependency(\.fileClient) var fileClient
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
let pathUrl = tempDirectory.appending(path: "playbook")
|
let pathUrl = tempDirectory.appending(path: "playbook")
|
||||||
let playbookClient = PlaybookClient.liveValue
|
let playbookClient = PlaybookClient.liveValue
|
||||||
|
|
||||||
@@ -35,7 +37,8 @@ struct PlaybookClientTests: TestCase {
|
|||||||
|
|
||||||
try? FileManager.default.removeItem(at: pathUrl)
|
try? FileManager.default.removeItem(at: pathUrl)
|
||||||
try await playbookClient.repository.install(configuration)
|
try await playbookClient.repository.install(configuration)
|
||||||
let exists = FileManager.default.fileExists(atPath: pathUrl.cleanFilePath)
|
logger.debug("Done cloning playbook")
|
||||||
|
let exists = try await fileClient.isDirectory(pathUrl)
|
||||||
#expect(exists)
|
#expect(exists)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,8 +64,6 @@ struct PlaybookClientTests: TestCase {
|
|||||||
try await withMockConfiguration(captured, key: "runBuildProject") {
|
try await withMockConfiguration(captured, key: "runBuildProject") {
|
||||||
@Dependency(\.playbookClient) var playbookClient
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
|
|
||||||
let configuration = Configuration.mock
|
|
||||||
|
|
||||||
try await playbookClient.run.buildProject(.init(projectDirectory: "/foo", shared: Self.sharedRunOptions))
|
try await playbookClient.run.buildProject(.init(projectDirectory: "/foo", shared: Self.sharedRunOptions))
|
||||||
|
|
||||||
let arguments = await captured.options!.arguments
|
let arguments = await captured.options!.arguments
|
||||||
@@ -92,8 +93,6 @@ struct PlaybookClientTests: TestCase {
|
|||||||
@Dependency(\.logger) var logger
|
@Dependency(\.logger) var logger
|
||||||
@Dependency(\.playbookClient) var playbookClient
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
|
|
||||||
let configuration = Configuration.mock
|
|
||||||
|
|
||||||
try await playbookClient.run.createProject(
|
try await playbookClient.run.createProject(
|
||||||
.init(
|
.init(
|
||||||
projectDirectory: "/project",
|
projectDirectory: "/project",
|
||||||
|
|||||||
23
docker/Dockerfile
Executable file
23
docker/Dockerfile
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
# Used this to build the release version of the image.
|
||||||
|
# Build the executable
|
||||||
|
ARG SWIFT_IMAGE_VERSION="6.0.3"
|
||||||
|
|
||||||
|
FROM swift:${SWIFT_IMAGE_VERSION} AS build
|
||||||
|
WORKDIR /build
|
||||||
|
COPY ./Package.* ./
|
||||||
|
RUN swift package resolve
|
||||||
|
COPY . .
|
||||||
|
RUN swift build -c release -Xswiftc -g
|
||||||
|
|
||||||
|
# Run image
|
||||||
|
FROM swift:${SWIFT_IMAGE_VERSION}-slim
|
||||||
|
|
||||||
|
RUN export DEBIAN_FRONTEND=nointeractive DEBCONF_NOINTERACTIVE_SEEN=true && apt-get -q update && \
|
||||||
|
apt-get -q install -y \
|
||||||
|
ansible \
|
||||||
|
pandoc \
|
||||||
|
texlive \
|
||||||
|
&& rm -r /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=build /build/.build/release/hpa /usr/local/bin
|
||||||
|
CMD ["/bin/bash", "-xc", "/usr/local/bin/hpa"]
|
||||||
10
docker/Dockerfile.test
Normal file
10
docker/Dockerfile.test
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Used to build a test image.
|
||||||
|
ARG SWIFT_IMAGE_VERSION="6.0.3"
|
||||||
|
FROM swift:${SWIFT_IMAGE_VERSION}
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ./Package.* ./
|
||||||
|
RUN swift package resolve
|
||||||
|
COPY . .
|
||||||
|
RUN swift build
|
||||||
|
CMD ["/bin/bash", "-xc", "swift", "test"]
|
||||||
14
justfile
14
justfile
@@ -1,14 +1,28 @@
|
|||||||
|
docker_image_name := "swift-hpa"
|
||||||
|
|
||||||
build mode="debug":
|
build mode="debug":
|
||||||
swift build -c {{mode}}
|
swift build -c {{mode}}
|
||||||
|
|
||||||
alias b := build
|
alias b := build
|
||||||
|
|
||||||
|
build-docker file="Dockerfile" tag="latest":
|
||||||
|
@docker build \
|
||||||
|
--file docker/{{file}} \
|
||||||
|
--tag {{docker_image_name}}:{{tag}} .
|
||||||
|
|
||||||
|
build-docker-test: (build-docker "Dockerfile.test" "test")
|
||||||
|
|
||||||
test *ARGS:
|
test *ARGS:
|
||||||
swift test {{ARGS}}
|
swift test {{ARGS}}
|
||||||
|
|
||||||
alias t := test
|
alias t := test
|
||||||
|
|
||||||
|
test-docker *ARGS: (build-docker-test)
|
||||||
|
@docker run --rm -it \
|
||||||
|
--network host \
|
||||||
|
{{docker_image_name}}:test \
|
||||||
|
swift test {{ARGS}}
|
||||||
|
|
||||||
run *ARGS:
|
run *ARGS:
|
||||||
swift run hpa {{ARGS}}
|
swift run hpa {{ARGS}}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user