From f7f168b7fdaf9da73c384c742d21549fa282c421 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Tue, 17 Dec 2024 10:15:07 -0500 Subject: [PATCH] feat: Begins adding docker containers --- Sources/CodersClient/Coders.swift | 2 + Sources/CommandClient/CommandClient.swift | 7 ++- .../ConfigurationClient/Configuration.swift | 17 ++++++- Sources/FileClient/FileClient.swift | 51 ++++++++++++------- .../ConfigurationClientTests.swift | 2 + Tests/FileClientTests/FileClientTests.swift | 4 +- .../PlaybookClientTests.swift | 11 ++-- docker/Dockerfile | 23 +++++++++ docker/Dockerfile.test | 10 ++++ justfile | 14 +++++ 10 files changed, 114 insertions(+), 27 deletions(-) create mode 100755 docker/Dockerfile create mode 100644 docker/Dockerfile.test diff --git a/Sources/CodersClient/Coders.swift b/Sources/CodersClient/Coders.swift index c97ee39..f861c69 100644 --- a/Sources/CodersClient/Coders.swift +++ b/Sources/CodersClient/Coders.swift @@ -4,6 +4,8 @@ import Foundation import TOMLKit public extension DependencyValues { + + /// Holds onto decoders and encoders for json and toml files. var coders: Coders { get { self[Coders.self] } set { self[Coders.self] = newValue } diff --git a/Sources/CommandClient/CommandClient.swift b/Sources/CommandClient/CommandClient.swift index 7a8c075..d730b9a 100644 --- a/Sources/CommandClient/CommandClient.swift +++ b/Sources/CommandClient/CommandClient.swift @@ -204,7 +204,12 @@ extension ShellCommand.Shell { if let path { self = .custom(path: path, useDashC: true) } 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 } } } diff --git a/Sources/ConfigurationClient/Configuration.swift b/Sources/ConfigurationClient/Configuration.swift index 687a9c9..8fe731d 100644 --- a/Sources/ConfigurationClient/Configuration.swift +++ b/Sources/ConfigurationClient/Configuration.swift @@ -2,13 +2,28 @@ import Foundation // 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 { + + /// Default arguments / options that can get passed into + /// ansible-playbook commands. public let args: [String]? + + /// Whether to use the vault arguments as defaults that get passed into + /// the ansible-playbook commands. public let useVaultArgs: Bool + + /// Configuration for when generating files from templated directories. 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? + + /// Template configuration options. public let template: Template + + /// Ansible-vault configuration options. public let vault: Vault public init( diff --git a/Sources/FileClient/FileClient.swift b/Sources/FileClient/FileClient.swift index 021f285..1dbea4a 100644 --- a/Sources/FileClient/FileClient.swift +++ b/Sources/FileClient/FileClient.swift @@ -3,23 +3,47 @@ import DependenciesMacros import Foundation public extension DependencyValues { + /// Represents interactions with the file system. + /// var fileClient: FileClient { get { self[FileClient.self] } set { self[FileClient.self] = newValue } } } +/// Represents interactions with the file system. +/// +/// @DependencyClient public struct FileClient: Sendable { + + /// Copy an item from one location to another. public var copy: @Sendable (URL, URL) async throws -> Void + + /// Create a directory at the given location. 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 } + + /// 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? + + /// Return the user's home directory. public var homeDirectory: @Sendable () -> URL = { URL(filePath: "~/") } + + /// Check if an item is a directory or not. public var isDirectory: @Sendable (URL) async throws -> Bool + + /// Load a file from the given location. 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 + /// Find an ansible-vault file in the current directory, checking up to 1 level + /// deep in subfolders. public func findVaultFileInCurrentDirectory() async throws -> URL? { try await findVaultFile(URL(filePath: "./")) } @@ -30,23 +54,16 @@ extension FileClient: DependencyKey { public static var liveValue: Self { let manager = LiveFileClient() - return .init { - try await manager.copy($0, to: $1) - } createDirectory: { - try await manager.creatDirectory($0) - } fileExists: { url in - manager.fileExists(at: url) - } findVaultFile: { - try await manager.findVaultFile(in: $0) - } homeDirectory: { - 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) - } + return .init( + copy: { try await manager.copy($0, to: $1) }, + createDirectory: { try await manager.creatDirectory($0) }, + fileExists: { manager.fileExists(at: $0) }, + findVaultFile: { try await manager.findVaultFile(in: $0) }, + homeDirectory: { manager.homeDirectory() }, + isDirectory: { manager.isDirectory($0) }, + load: { try await manager.load(from: $0) }, + write: { try await manager.write($0, to: $1) } + ) } } diff --git a/Tests/ConfigurationClientTests/ConfigurationClientTests.swift b/Tests/ConfigurationClientTests/ConfigurationClientTests.swift index 8fef56b..98ee356 100644 --- a/Tests/ConfigurationClientTests/ConfigurationClientTests.swift +++ b/Tests/ConfigurationClientTests/ConfigurationClientTests.swift @@ -18,6 +18,7 @@ struct ConfigurationClientTests: TestCase { @Test(arguments: ["config.toml", "config.json"]) func generateConfigFile(fileName: String) async throws { try await withTestLogger(key: "generateConfigFile") { + $0.coders = .liveValue $0.fileClient = .liveValue } operation: { @Dependency(\.logger) var logger @@ -53,6 +54,7 @@ struct ConfigurationClientTests: TestCase { @Test(arguments: ["config.toml", "config.json", nil]) func loadConfigFile(fileName: String?) async throws { try await withTestLogger(key: "generateConfigFile") { + $0.coders = .liveValue $0.fileClient = .liveValue } operation: { @Dependency(\.logger) var logger diff --git a/Tests/FileClientTests/FileClientTests.swift b/Tests/FileClientTests/FileClientTests.swift index 29a298c..2b77fa3 100644 --- a/Tests/FileClientTests/FileClientTests.swift +++ b/Tests/FileClientTests/FileClientTests.swift @@ -23,7 +23,7 @@ struct FileClientTests { let fileClient = FileClient.liveValue 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)! #expect(output.cleanFilePath == vaultFilePath.cleanFilePath) @@ -43,7 +43,7 @@ struct FileClientTests { try await fileClient.createDirectory(subDir) 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)! #expect(output.cleanFilePath == vaultFilePath.cleanFilePath) diff --git a/Tests/PlaybookClientTests/PlaybookClientTests.swift b/Tests/PlaybookClientTests/PlaybookClientTests.swift index c869578..ae55130 100644 --- a/Tests/PlaybookClientTests/PlaybookClientTests.swift +++ b/Tests/PlaybookClientTests/PlaybookClientTests.swift @@ -22,12 +22,14 @@ struct PlaybookClientTests: TestCase { @Test(.tags(.repository)) func repositoryInstallation() async throws { - try await withDependencies { + try await withTestLogger(key: "repositoryInstallation") { $0.fileClient = .liveValue $0.asyncShellClient = .liveValue $0.commandClient = .liveValue } operation: { try await withTemporaryDirectory { tempDirectory in + @Dependency(\.fileClient) var fileClient + @Dependency(\.logger) var logger let pathUrl = tempDirectory.appending(path: "playbook") let playbookClient = PlaybookClient.liveValue @@ -35,7 +37,8 @@ struct PlaybookClientTests: TestCase { try? FileManager.default.removeItem(at: pathUrl) 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) } } @@ -61,8 +64,6 @@ struct PlaybookClientTests: TestCase { try await withMockConfiguration(captured, key: "runBuildProject") { @Dependency(\.playbookClient) var playbookClient - let configuration = Configuration.mock - try await playbookClient.run.buildProject(.init(projectDirectory: "/foo", shared: Self.sharedRunOptions)) let arguments = await captured.options!.arguments @@ -92,8 +93,6 @@ struct PlaybookClientTests: TestCase { @Dependency(\.logger) var logger @Dependency(\.playbookClient) var playbookClient - let configuration = Configuration.mock - try await playbookClient.run.createProject( .init( projectDirectory: "/project", diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100755 index 0000000..4131239 --- /dev/null +++ b/docker/Dockerfile @@ -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"] diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test new file mode 100644 index 0000000..5724c82 --- /dev/null +++ b/docker/Dockerfile.test @@ -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"] diff --git a/justfile b/justfile index 3cccc13..9e2ed4b 100644 --- a/justfile +++ b/justfile @@ -1,14 +1,28 @@ +docker_image_name := "swift-hpa" build mode="debug": swift build -c {{mode}} 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: swift test {{ARGS}} alias t := test +test-docker *ARGS: (build-docker-test) + @docker run --rm -it \ + --network host \ + {{docker_image_name}}:test \ + swift test {{ARGS}} + run *ARGS: swift run hpa {{ARGS}}