From 4b6c5b93a47939dd13526ebd81274ba523aaba47 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Tue, 10 Mar 2026 12:15:28 -0700 Subject: [PATCH 1/3] update to container 0.10 and switch to ContainerClient ClientContainer has been changed to ContainerClient. See PR: https://github.com/apple/container/pull/1139 --- Package.resolved | 63 +++++++++++-------- Sources/Container-Compose/Application.swift | 2 +- .../Commands/ComposeDown.swift | 9 ++- .../Commands/ComposeUp.swift | 13 ++-- .../ComposeDownTests.swift | 8 +-- .../ComposeUpTests.swift | 16 ++--- 6 files changed, 64 insertions(+), 47 deletions(-) diff --git a/Package.resolved b/Package.resolved index 0167755..9b5298d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swift-server/async-http-client.git", "state" : { - "revision" : "4b99975677236d13f0754339864e5360142ff5a1", - "version" : "1.30.3" + "revision" : "2fc4652fb4689eb24af10e55cabaa61d8ba774fd", + "version" : "1.32.0" } }, { @@ -16,7 +16,7 @@ "location" : "https://github.com/mcrich23/container", "state" : { "branch" : "add-command-option-group-function-macro", - "revision" : "1d4e030cd537fd8325f2a91fd05e0bec9419753f" + "revision" : "a3bf110f407bb8772d56f149f76668fe6f2d3067" } }, { @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/containerization.git", "state" : { - "revision" : "c3fe889a2f739ee4a9b0faccedd9f36f3862dc29", - "version" : "0.24.5" + "revision" : "d3ff56e5dd93a4573ce7950f3428710bbe467f9c", + "version" : "0.27.0" } }, { @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/grpc/grpc-swift.git", "state" : { - "revision" : "8f57f68b9d247fe3759fa9f18e1fe919911e6031", - "version" : "1.27.1" + "revision" : "ac715c584bb1e2e5cdfb7684ccb46fab8dafc641", + "version" : "1.27.4" } }, { @@ -78,8 +78,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-async-algorithms.git", "state" : { - "revision" : "6c050d5ef8e1aa6342528460db614e9770d7f804", - "version" : "1.1.1" + "revision" : "9d349bcc328ac3c31ce40e746b5882742a0d1272", + "version" : "1.1.3" } }, { @@ -96,8 +96,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-certificates.git", "state" : { - "revision" : "7d5f6124c91a2d06fb63a811695a3400d15a100e", - "version" : "1.17.1" + "revision" : "24ccdeeeed4dfaae7955fcac9dbf5489ed4f1a25", + "version" : "1.18.0" } }, { @@ -105,8 +105,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e", - "version" : "1.3.0" + "revision" : "8d9834a6189db730f6264db7556a7ffb751e99ee", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-configuration", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-configuration.git", + "state" : { + "revision" : "be76c4ad929eb6c4bcaf3351799f2adf9e6848a9", + "version" : "1.2.0" } }, { @@ -123,8 +132,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-distributed-tracing.git", "state" : { - "revision" : "baa932c1336f7894145cbaafcd34ce2dd0b77c97", - "version" : "1.3.1" + "revision" : "dc4030184203ffafbb2ec614352487235d747fe0", + "version" : "1.4.1" } }, { @@ -150,8 +159,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "2778fd4e5a12a8aaa30a3ee8285f4ce54c5f3181", - "version" : "1.9.1" + "revision" : "bbd81b6725ae874c69e9b8c8804d462356b55523", + "version" : "1.10.1" } }, { @@ -159,8 +168,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "5e72fc102906ebe75a3487595a653e6f43725552", - "version" : "2.94.0" + "revision" : "e932d3c4d8f77433c8f7093b5ebcbf91463948a0", + "version" : "2.95.0" } }, { @@ -177,8 +186,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-http2.git", "state" : { - "revision" : "c2ba4cfbb83f307c66f5a6df6bb43e3c88dfbf80", - "version" : "1.39.0" + "revision" : "b6571f3db40799df5a7fc0e92c399aa71c883edd", + "version" : "1.40.0" } }, { @@ -213,8 +222,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "c169a5744230951031770e27e475ff6eefe51f9d", - "version" : "1.33.3" + "revision" : "86970144a0b86068c81ff48ee29b3f97cae0b879", + "version" : "1.36.0" } }, { @@ -222,8 +231,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-service-context.git", "state" : { - "revision" : "1983448fefc717a2bc2ebde5490fe99873c5b8a6", - "version" : "1.2.1" + "revision" : "d0997351b0c7779017f88e7a93bc30a1878d7f29", + "version" : "1.3.0" } }, { @@ -231,8 +240,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swift-server/swift-service-lifecycle.git", "state" : { - "revision" : "1de37290c0ab3c5a96028e0f02911b672fd42348", - "version" : "2.9.1" + "revision" : "89888196dd79c61c50bca9a103d8114f32e1e598", + "version" : "2.10.1" } }, { diff --git a/Sources/Container-Compose/Application.swift b/Sources/Container-Compose/Application.swift index 5106f30..a986d48 100644 --- a/Sources/Container-Compose/Application.swift +++ b/Sources/Container-Compose/Application.swift @@ -19,7 +19,7 @@ import ArgumentParser public struct Main: AsyncParsableCommand { private static let commandName: String = "container-compose" - private static let version: String = "0.9.0" + private static let version: String = "0.10.0" public static var versionString: String { "\(commandName) version \(version)" } diff --git a/Sources/Container-Compose/Commands/ComposeDown.swift b/Sources/Container-Compose/Commands/ComposeDown.swift index 68210e5..a12faa8 100644 --- a/Sources/Container-Compose/Commands/ComposeDown.swift +++ b/Sources/Container-Compose/Commands/ComposeDown.swift @@ -119,20 +119,23 @@ public struct ComposeDown: AsyncParsableCommand { } print("Stopping container: \(containerName)") - guard let container = try? await ClientContainer.get(id: containerName) else { + + let client = ContainerClient() + + guard let container = try? await client.get(id: containerName) else { print("Warning: Container '\(containerName)' not found, skipping.") continue } do { - try await container.stop() + try await client.stop(id: container.id) print("Successfully stopped container: \(containerName)") } catch { print("Error Stopping Container: \(error)") } if remove { do { - try await container.delete() + try await client.delete(id: container.id) print("Successfully removed container: \(containerName)") } catch { print("Error Removing Container: \(error)") diff --git a/Sources/Container-Compose/Commands/ComposeUp.swift b/Sources/Container-Compose/Commands/ComposeUp.swift index 317381b..5cb9931 100644 --- a/Sources/Container-Compose/Commands/ComposeUp.swift +++ b/Sources/Container-Compose/Commands/ComposeUp.swift @@ -186,7 +186,8 @@ public struct ComposeUp: AsyncParsableCommand, @unchecked Sendable { let containerName = "\(projectName)-\(serviceName)" - let container = try await ClientContainer.get(id: containerName) + let client = ContainerClient() + let container = try await client.get(id: containerName) let ip = container.networks.compactMap { $0.ipv4Gateway.description }.first return ip @@ -205,7 +206,8 @@ public struct ComposeUp: AsyncParsableCommand, @unchecked Sendable { let deadline = Date().addingTimeInterval(timeout) while Date() < deadline { - let container = try? await ClientContainer.get(id: containerName) + let client = ContainerClient() + let container = try? await client.get(id: containerName) if container?.status == .running { return } @@ -226,16 +228,17 @@ public struct ComposeUp: AsyncParsableCommand, @unchecked Sendable { for container in containers { print("Stopping container: \(container)") - guard let container = try? await ClientContainer.get(id: container) else { continue } + let client = ContainerClient() + guard let container = try? await client.get(id: container) else { continue } do { - try await container.stop() + try await client.stop(id: container.id) } catch { print("Error Stopping Container: \(error)") } if remove { do { - try await container.delete() + try await client.delete(id: container.id) } catch { print("Error Removing Container: \(error)") } diff --git a/Tests/Container-Compose-DynamicTests/ComposeDownTests.swift b/Tests/Container-Compose-DynamicTests/ComposeDownTests.swift index d983bfc..0da8b9a 100644 --- a/Tests/Container-Compose-DynamicTests/ComposeDownTests.swift +++ b/Tests/Container-Compose-DynamicTests/ComposeDownTests.swift @@ -35,7 +35,7 @@ struct ComposeDownTests { ]) try await composeUp.run() - var containers = try await ClientContainer.list() + var containers = try await ContainerClient().list() .filter({ $0.configuration.id.contains(project.name) }) @@ -49,7 +49,7 @@ struct ComposeDownTests { var composeDown = try ComposeDown.parse(["--cwd", project.base.path(percentEncoded: false)]) try await composeDown.run() - containers = try await ClientContainer.list() + containers = try await ContainerClient().list() .filter({ $0.configuration.id.contains(project.name) }) @@ -75,7 +75,7 @@ struct ComposeDownTests { ]) try await composeUp.run() - var containers = try await ClientContainer.list() + var containers = try await ContainerClient().list() .filter({ $0.configuration.id.contains(containerName) }) @@ -91,7 +91,7 @@ struct ComposeDownTests { var composeDown = try ComposeDown.parse(["--cwd", project.base.path(percentEncoded: false)]) try await composeDown.run() - containers = try await ClientContainer.list() + containers = try await ContainerClient().list() .filter({ $0.configuration.id.contains(containerName) }) diff --git a/Tests/Container-Compose-DynamicTests/ComposeUpTests.swift b/Tests/Container-Compose-DynamicTests/ComposeUpTests.swift index 08b1cb1..201dd7d 100644 --- a/Tests/Container-Compose-DynamicTests/ComposeUpTests.swift +++ b/Tests/Container-Compose-DynamicTests/ComposeUpTests.swift @@ -37,7 +37,9 @@ struct ComposeUpTests { try await composeUp.run() // Get these containers - let containers = try await ClientContainer.list() + let client = ContainerClient() + + let containers = try await client.list() .filter({ $0.configuration.id.contains(tempLocation.deletingLastPathComponent().lastPathComponent) }) @@ -96,7 +98,7 @@ struct ComposeUpTests { // try await composeUp.run() // // // Get the containers created by this compose file -// let containers = try await ClientContainer.list() +// let containers = try await ContainerClient().list() // .filter({ // $0.configuration.id.contains(folderName) // }) @@ -175,7 +177,7 @@ struct ComposeUpTests { // try await composeUp.run() // // // Get the containers created by this compose file -// let containers = try await ClientContainer.list() +// let containers = try await ContainerClient().list() // .filter({ // $0.configuration.id.contains(folderName) // }) @@ -195,7 +197,7 @@ struct ComposeUpTests { try await composeUp.run() // Get the containers created by this compose file - let containers = try await ClientContainer.list() + let containers = try await ContainerClient().list() .filter { $0.configuration.id.contains(folderName) } @@ -257,7 +259,7 @@ struct ComposeUpTests { var composeUp = try ComposeUp.parse(["-d", "--cwd", tempLocation.deletingLastPathComponent().path(percentEncoded: false)]) try await composeUp.run() - let containers = try await ClientContainer.list() + let containers = try await ContainerClient().list() .filter { $0.configuration.id.contains(folderName) } guard let appContainer = containers.first(where: { $0.configuration.id == "\(folderName)-app" }) else { @@ -284,7 +286,7 @@ struct ComposeUpTests { var composeUp = try ComposeUp.parse(["-d", "--cwd", project.base.path(percentEncoded: false)]) try await composeUp.run() - var containers = try await ClientContainer.list() + var containers = try await ContainerClient().list() .filter({ $0.configuration.id.contains(project.name) }) @@ -299,7 +301,7 @@ struct ComposeUpTests { var composeDown = try ComposeDown.parse(["--cwd", project.base.path(percentEncoded: false)]) try await composeDown.run() - containers = try await ClientContainer.list() + containers = try await ContainerClient().list() .filter({ $0.configuration.id.contains(project.name) }) From bb281a97b4f8c08015043e7bef0891f2067662f3 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Tue, 10 Mar 2026 12:27:52 -0700 Subject: [PATCH 2/3] update timing on waitUntilServiceIsRunning to be more true --- Sources/Container-Compose/Commands/ComposeUp.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Container-Compose/Commands/ComposeUp.swift b/Sources/Container-Compose/Commands/ComposeUp.swift index 5cb9931..dea941c 100644 --- a/Sources/Container-Compose/Commands/ComposeUp.swift +++ b/Sources/Container-Compose/Commands/ComposeUp.swift @@ -204,15 +204,14 @@ public struct ComposeUp: AsyncParsableCommand, @unchecked Sendable { let containerName = "\(projectName)-\(serviceName)" let deadline = Date().addingTimeInterval(timeout) + let client = ContainerClient() while Date() < deadline { - let client = ContainerClient() + try await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000)) let container = try? await client.get(id: containerName) if container?.status == .running { return } - - try await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000)) } throw NSError( From a2545be40d23e57b2457deb3e726ca51f35f0615 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Tue, 10 Mar 2026 12:28:42 -0700 Subject: [PATCH 3/3] update compose up tests to bring down the container after each run if possible --- .../ComposeUpTests.swift | 265 +++++++++--------- 1 file changed, 139 insertions(+), 126 deletions(-) diff --git a/Tests/Container-Compose-DynamicTests/ComposeUpTests.swift b/Tests/Container-Compose-DynamicTests/ComposeUpTests.swift index 201dd7d..62ef9e0 100644 --- a/Tests/Container-Compose-DynamicTests/ComposeUpTests.swift +++ b/Tests/Container-Compose-DynamicTests/ComposeUpTests.swift @@ -24,6 +24,11 @@ import TestHelpers @Suite("Compose Up Tests - Real-World Compose Files", .containerDependent, .serialized) struct ComposeUpTests { + func stopInstance(location: URL) async throws { + var composeDown = try ComposeDown.parse(["--cwd", location.path(percentEncoded: false)]) + try await composeDown.run() + } + @Test("Test WordPress with MySQL compose file") func testWordPressCompose() async throws { let yaml = DockerComposeYamlFiles.dockerComposeYaml1 @@ -82,107 +87,109 @@ struct ComposeUpTests { // Check Volume #expect(dbContainer.configuration.mounts.map(\.destination) == ["/var/lib/"]) print("") + + try? await stopInstance(location: tempLocation.deletingLastPathComponent()) } // TODO: Reenable -// @Test("Test three-tier web application with multiple networks") -// func testThreeTierWebAppWithNetworks() async throws { -// let yaml = DockerComposeYamlFiles.dockerComposeYaml2 -// -// let tempLocation = URL.temporaryDirectory.appending(path: "Container-Compose_Tests_\(UUID().uuidString)/docker-compose.yaml") -// try? FileManager.default.createDirectory(at: tempLocation.deletingLastPathComponent(), withIntermediateDirectories: true) -// try yaml.write(to: tempLocation, atomically: false, encoding: .utf8) -// let folderName = tempLocation.deletingLastPathComponent().lastPathComponent -// -// var composeUp = try ComposeUp.parse(["-d", "--cwd", tempLocation.deletingLastPathComponent().path(percentEncoded: false)]) -// try await composeUp.run() -// -// // Get the containers created by this compose file -// let containers = try await ContainerClient().list() -// .filter({ -// $0.configuration.id.contains(folderName) -// }) -// -// guard let nginxContainer = containers.first(where: { $0.configuration.id == "\(folderName)-nginx" }), -// let appContainer = containers.first(where: { $0.configuration.id == "\(folderName)-app" }), -// let dbContainer = containers.first(where: { $0.configuration.id == "\(folderName)-db" }), -// let redisContainer = containers.first(where: { $0.configuration.id == "\(folderName)-redis" }) -// else { -// throw Errors.containerNotFound -// } -// -// // --- NGINX Container --- -// #expect(nginxContainer.configuration.image.reference == "docker.io/library/nginx:alpine") -// #expect(nginxContainer.configuration.publishedPorts.map({ "\($0.hostAddress):\($0.hostPort):\($0.containerPort)" }) == ["0.0.0.0:80:80"]) -// #expect(nginxContainer.networks.map(\.hostname).contains("frontend")) -// -// // --- APP Container --- -// #expect(appContainer.configuration.image.reference == "docker.io/library/node:18-alpine") -// -// let appEnv = parseEnvToDict(appContainer.configuration.initProcess.environment) -// #expect(appEnv["NODE_ENV"] == "production") -// #expect(appEnv["DATABASE_URL"] == "postgres://\(dbContainer.networks.first!.address.split(separator: "/")[0]):5432/myapp") -// -// #expect(appContainer.networks.map(\.hostname).sorted() == ["backend", "frontend"]) -// -// // --- DB Container --- -// #expect(dbContainer.configuration.image.reference == "docker.io/library/postgres:14-alpine") -// let dbEnv = parseEnvToDict(dbContainer.configuration.initProcess.environment) -// #expect(dbEnv["POSTGRES_DB"] == "myapp") -// #expect(dbEnv["POSTGRES_USER"] == "user") -// #expect(dbEnv["POSTGRES_PASSWORD"] == "password") -// -// // Verify volume mount -// #expect(dbContainer.configuration.mounts.map(\.destination) == ["/var/lib/postgresql/"]) -// #expect(dbContainer.networks.map(\.hostname) == ["backend"]) -// -// // --- Redis Container --- -// #expect(redisContainer.configuration.image.reference == "docker.io/library/redis:alpine") -// #expect(redisContainer.networks.map(\.hostname) == ["backend"]) -// } + // @Test("Test three-tier web application with multiple networks") + // func testThreeTierWebAppWithNetworks() async throws { + // let yaml = DockerComposeYamlFiles.dockerComposeYaml2 + // + // let tempLocation = URL.temporaryDirectory.appending(path: "Container-Compose_Tests_\(UUID().uuidString)/docker-compose.yaml") + // try? FileManager.default.createDirectory(at: tempLocation.deletingLastPathComponent(), withIntermediateDirectories: true) + // try yaml.write(to: tempLocation, atomically: false, encoding: .utf8) + // let folderName = tempLocation.deletingLastPathComponent().lastPathComponent + // + // var composeUp = try ComposeUp.parse(["-d", "--cwd", tempLocation.deletingLastPathComponent().path(percentEncoded: false)]) + // try await composeUp.run() + // + // // Get the containers created by this compose file + // let containers = try await ContainerClient().list() + // .filter({ + // $0.configuration.id.contains(folderName) + // }) + // + // guard let nginxContainer = containers.first(where: { $0.configuration.id == "\(folderName)-nginx" }), + // let appContainer = containers.first(where: { $0.configuration.id == "\(folderName)-app" }), + // let dbContainer = containers.first(where: { $0.configuration.id == "\(folderName)-db" }), + // let redisContainer = containers.first(where: { $0.configuration.id == "\(folderName)-redis" }) + // else { + // throw Errors.containerNotFound + // } + // + // // --- NGINX Container --- + // #expect(nginxContainer.configuration.image.reference == "docker.io/library/nginx:alpine") + // #expect(nginxContainer.configuration.publishedPorts.map({ "\($0.hostAddress):\($0.hostPort):\($0.containerPort)" }) == ["0.0.0.0:80:80"]) + // #expect(nginxContainer.networks.map(\.hostname).contains("frontend")) + // + // // --- APP Container --- + // #expect(appContainer.configuration.image.reference == "docker.io/library/node:18-alpine") + // + // let appEnv = parseEnvToDict(appContainer.configuration.initProcess.environment) + // #expect(appEnv["NODE_ENV"] == "production") + // #expect(appEnv["DATABASE_URL"] == "postgres://\(dbContainer.networks.first!.address.split(separator: "/")[0]):5432/myapp") + // + // #expect(appContainer.networks.map(\.hostname).sorted() == ["backend", "frontend"]) + // + // // --- DB Container --- + // #expect(dbContainer.configuration.image.reference == "docker.io/library/postgres:14-alpine") + // let dbEnv = parseEnvToDict(dbContainer.configuration.initProcess.environment) + // #expect(dbEnv["POSTGRES_DB"] == "myapp") + // #expect(dbEnv["POSTGRES_USER"] == "user") + // #expect(dbEnv["POSTGRES_PASSWORD"] == "password") + // + // // Verify volume mount + // #expect(dbContainer.configuration.mounts.map(\.destination) == ["/var/lib/postgresql/"]) + // #expect(dbContainer.networks.map(\.hostname) == ["backend"]) + // + // // --- Redis Container --- + // #expect(redisContainer.configuration.image.reference == "docker.io/library/redis:alpine") + // #expect(redisContainer.networks.map(\.hostname) == ["backend"]) + // } -// @Test("Parse development environment with build") -// func parseDevelopmentEnvironment() throws { -// let yaml = DockerComposeYamlFiles.dockerComposeYaml4 -// -// let decoder = YAMLDecoder() -// let compose = try decoder.decode(DockerCompose.self, from: yaml) -// -// #expect(compose.services["app"]??.build != nil) -// #expect(compose.services["app"]??.build?.context == ".") -// #expect(compose.services["app"]??.volumes?.count == 2) -// } + // @Test("Parse development environment with build") + // func parseDevelopmentEnvironment() throws { + // let yaml = DockerComposeYamlFiles.dockerComposeYaml4 + // + // let decoder = YAMLDecoder() + // let compose = try decoder.decode(DockerCompose.self, from: yaml) + // + // #expect(compose.services["app"]??.build != nil) + // #expect(compose.services["app"]??.build?.context == ".") + // #expect(compose.services["app"]??.volumes?.count == 2) + // } -// @Test("Parse compose with secrets and configs") -// func parseComposeWithSecretsAndConfigs() throws { -// let yaml = DockerComposeYamlFiles.dockerComposeYaml5 -// -// let decoder = YAMLDecoder() -// let compose = try decoder.decode(DockerCompose.self, from: yaml) -// -// #expect(compose.configs != nil) -// #expect(compose.secrets != nil) -// } + // @Test("Parse compose with secrets and configs") + // func parseComposeWithSecretsAndConfigs() throws { + // let yaml = DockerComposeYamlFiles.dockerComposeYaml5 + // + // let decoder = YAMLDecoder() + // let compose = try decoder.decode(DockerCompose.self, from: yaml) + // + // #expect(compose.configs != nil) + // #expect(compose.secrets != nil) + // } -// @Test("Parse compose with healthchecks and restart policies") -// func parseComposeWithHealthchecksAndRestart() async throws { -// let yaml = DockerComposeYamlFiles.dockerComposeYaml6 -// -// let tempLocation = URL.temporaryDirectory.appending(path: "Container-Compose_Tests_\(UUID().uuidString)/docker-compose.yaml") -// try? FileManager.default.createDirectory(at: tempLocation.deletingLastPathComponent(), withIntermediateDirectories: true) -// try yaml.write(to: tempLocation, atomically: false, encoding: .utf8) -// let folderName = tempLocation.deletingLastPathComponent().lastPathComponent -// -// var composeUp = try ComposeUp.parse(["-d", "--cwd", tempLocation.deletingLastPathComponent().path(percentEncoded: false)]) -// try await composeUp.run() -// -// // Get the containers created by this compose file -// let containers = try await ContainerClient().list() -// .filter({ -// $0.configuration.id.contains(folderName) -// }) -// dump(containers) -// } + // @Test("Parse compose with healthchecks and restart policies") + // func parseComposeWithHealthchecksAndRestart() async throws { + // let yaml = DockerComposeYamlFiles.dockerComposeYaml6 + // + // let tempLocation = URL.temporaryDirectory.appending(path: "Container-Compose_Tests_\(UUID().uuidString)/docker-compose.yaml") + // try? FileManager.default.createDirectory(at: tempLocation.deletingLastPathComponent(), withIntermediateDirectories: true) + // try yaml.write(to: tempLocation, atomically: false, encoding: .utf8) + // let folderName = tempLocation.deletingLastPathComponent().lastPathComponent + // + // var composeUp = try ComposeUp.parse(["-d", "--cwd", tempLocation.deletingLastPathComponent().path(percentEncoded: false)]) + // try await composeUp.run() + // + // // Get the containers created by this compose file + // let containers = try await ContainerClient().list() + // .filter({ + // $0.configuration.id.contains(folderName) + // }) + // dump(containers) + // } @Test("Test compose with complex dependency chain") func TestComplexDependencyChain() async throws { @@ -235,11 +242,13 @@ struct ComposeUpTests { // App isn't set to run long term #expect(webContainer.status == .running) #expect(dbContainer.status == .running) + + try? await stopInstance(location: tempLocation.deletingLastPathComponent()) } - - @Test("Test container created with non-default CPU and memory limits") - func testCpuAndMemoryLimits() async throws { - let yaml = """ + + @Test("Test container created with non-default CPU and memory limits") + func testCpuAndMemoryLimits() async throws { + let yaml = """ version: "3.8" services: app: @@ -250,26 +259,28 @@ struct ComposeUpTests { cpus: "1" memory: "512MB" """ - - let tempLocation = URL.temporaryDirectory.appending(path: "Container-Compose_Tests_\(UUID().uuidString)/docker-compose.yaml") - try? FileManager.default.createDirectory(at: tempLocation.deletingLastPathComponent(), withIntermediateDirectories: true) - try yaml.write(to: tempLocation, atomically: false, encoding: .utf8) - let folderName = tempLocation.deletingLastPathComponent().lastPathComponent - - var composeUp = try ComposeUp.parse(["-d", "--cwd", tempLocation.deletingLastPathComponent().path(percentEncoded: false)]) - try await composeUp.run() - - let containers = try await ContainerClient().list() - .filter { $0.configuration.id.contains(folderName) } - - guard let appContainer = containers.first(where: { $0.configuration.id == "\(folderName)-app" }) else { - throw Errors.containerNotFound - } - - #expect(appContainer.configuration.resources.cpus == 1) - #expect(appContainer.configuration.resources.memoryInBytes == 512.mib()) + + let tempLocation = URL.temporaryDirectory.appending(path: "Container-Compose_Tests_\(UUID().uuidString)/docker-compose.yaml") + try? FileManager.default.createDirectory(at: tempLocation.deletingLastPathComponent(), withIntermediateDirectories: true) + try yaml.write(to: tempLocation, atomically: false, encoding: .utf8) + let folderName = tempLocation.deletingLastPathComponent().lastPathComponent + + var composeUp = try ComposeUp.parse(["-d", "--cwd", tempLocation.deletingLastPathComponent().path(percentEncoded: false)]) + try await composeUp.run() + + let containers = try await ContainerClient().list() + .filter { $0.configuration.id.contains(folderName) } + + guard let appContainer = containers.first(where: { $0.configuration.id == "\(folderName)-app" }) else { + throw Errors.containerNotFound } - + + #expect(appContainer.configuration.resources.cpus == 1) + #expect(appContainer.configuration.resources.memoryInBytes == 512.mib()) + + try? await stopInstance(location: tempLocation.deletingLastPathComponent()) + } + @Test("Test compose up with explicit IP port mapping") func testComposeUpWithExplicitIPPortMapping() async throws { let yaml = """ @@ -280,34 +291,36 @@ struct ComposeUpTests { ports: - "127.0.0.1:18081:80" """ - + let project = try DockerComposeYamlFiles.copyYamlToTemporaryLocation(yaml: yaml) - + var composeUp = try ComposeUp.parse(["-d", "--cwd", project.base.path(percentEncoded: false)]) try await composeUp.run() - + var containers = try await ContainerClient().list() .filter({ $0.configuration.id.contains(project.name) }) - + guard let webContainer = containers.first(where: { $0.configuration.id == "\(project.name)-web" }) else { throw Errors.containerNotFound } - + #expect(webContainer.status == .running) #expect(webContainer.configuration.publishedPorts.map({ "\($0.hostAddress):\($0.hostPort):\($0.containerPort)" }) == ["127.0.0.1:18081:80"]) - + var composeDown = try ComposeDown.parse(["--cwd", project.base.path(percentEncoded: false)]) try await composeDown.run() - + containers = try await ContainerClient().list() .filter({ $0.configuration.id.contains(project.name) }) - + #expect(containers.count == 1) #expect(containers.filter({ $0.status == .stopped }).count == 1) + + try? await stopInstance(location: project.base) } enum Errors: Error {