diff --git a/FORK_CHANGES.md b/FORK_CHANGES.md index 2b9bd52..9697751 100644 --- a/FORK_CHANGES.md +++ b/FORK_CHANGES.md @@ -57,12 +57,84 @@ Additional upstream PRs of interest (not exhaustive): Notes and suggested next steps: -- Convert each bullet above into a CHANGELOG section with short user-facing notes and example usage (e.g., how to use the new build target, how to set dns_search). -- Update CLI --help and README to document new/changed flags and behaviors (dnsSearch/dns_search, build.target, named-volume behavior, entrypoint handling). -- Where possible, link to the full upstream PR discussions for context (links provided above for the main PRs found). +- Upstream apple/container v0.10.0 already includes many of the core engine changes referenced above (notably: ClientContainer rework [#1139], runtime flag for create/run [#1109], --init and --init-image support [#1244, #937], container export/commit [#1172], support for multiple network plugins [#1151], build --pull [#844], named-volume auto-create warning [#1108], memory validation [#1208], and related CLI/output changes such as a --format option for system status [#1237]). + +- Items present in this fork but NOT included in apple/container v0.10.0 (should be tracked or upstreamed): + - Remove RuntimeStatus type (commit: c509a2f) + - Fix incorrect waiting when container is already running (commit: 8a4e5bb) + - Remove unnecessary 30s timeout when container already started (commit: eeddb26) + - dnsSearch / dns_search support for service name resolution (commit: d509f8a) + - Multi-stage build target support (build.target) (commit: 02ca646) + - Debug output showing the exact container CLI command being executed (commit: 4968a86) + - Ensure --entrypoint is passed before image name in run (commit: 84201f9) + - Named-volume full-destination-path preservation and regression test (commits: b1badf8, 8edb8a9) + - Fork-specific CI/release workflow additions (commits: 3f20dbf, 98b7fc4, 1d284fb) + +- Recommended actions: + 1. Update this FORK_CHANGES.md and add a short CHANGELOG.md that clearly separates what was upstreamed in apple/container@0.10.0 and what remains unique to this fork. + 2. Update README and CLI --help strings for fork-only features (dns_search, build.target, entrypoint behavior, named-volume handling) and add migration notes where appropriate. + 3. For each fork-only item, decide whether to upstream as a PR against apple/container or keep it as a fork patch; open PRs for items that are broadly useful (dns_search, build.target, entrypoint fix, named-volume behavior). TODOs: -- Create a detailed CHANGELOG.md entry describing user-facing changes and migration notes. -- Update README and CLI --help strings to reflect fork capabilities. +- Create a detailed CHANGELOG.md entry describing user-facing changes and migration notes, split into "Upstream in container@0.10.0" and "Fork-only changes". +- Update README and CLI --help strings to reflect fork capabilities and any CLI differences. +- Audit tests that depend on fork-only behavior and mark or adapt them for upstream compatibility. -(Generated by repo inspection and upstream PR search.) +(Generated by repository inspection against apple/container v0.10.0.) + +--- + +Proposed features to target for the next Apple Containers release + +Based on the active development in the apple/container main branch (post-0.9.0), several high-impact features are landing that the Container-Compose fork is uniquely positioned to capitalize on. To stay ahead of the next release, focus development and testing on the following areas. + +### 1. Robust Service Lifecycle (Restart Policies) + +The Change: PR #1258 adds a native `--restart` policy to the `container run` command. + +- Compose Feature to Add: Implement the `restart: always`, `restart: on-failure`, and `restart: unless-stopped` keys in docker-compose.yaml so the fork maps those keys to the new engine `--restart` flag. +- Testing Priority: Test "zombie" container cleanup. Since the engine is adding native restart support, ensure that `container-compose down` correctly stops and removes containers that the engine might be trying to restart automatically. + +### 2. High-Performance Host-Container File Transfer + +The Change: PR #1190 introduces a native `container cp` command. + +- Compose Feature to Add: Use this to implement a "Sync" or "Hot Reload" feature that programmatically moves files into a running service container as an alternative to bind mounts for improved performance. +- Testing Priority: Verify large file transfers and directory structures. This is a significant improvement over the current "mount-only" storage strategy in 0.9.0. + +### 3. Native "Init" Process Management + +The Change: PR #1244 adds an `--init` flag to `run/create`. + +- Compose Feature to Add: Add an `init: true` boolean to the service definition that maps to the engine `--init` flag when starting containers. +- Testing Priority: Test applications that spawn many child processes (Node.js, Python with workers). Using the native `--init` flag will prevent orphan processes from remaining in the micro-VM after the service stops. + +### 4. Advanced Networking & Multi-Plugin Support + +The Change: PR #1151 and #1227 enable multiple network plugins and loading configurations from files. + +- Compose Feature to Add: Support complex `networks:` definitions in Compose to allow combinations of bridge, host-only, and routed networks for services within the same stack. +- Testing Priority: IPv6 connectivity. PR #1174 adds IPv6 gateway support — validate IPv6 addressing, routing, and DNS resolution across custom networks. + +### 5. "Snapshot-based" Deployments + +The Change: PR #1172 adds `container commit` (exporting a container to an image). + +- Compose Feature to Add: Implement a `container-compose checkpoint ` command that commits a running container to a local image for future `up` commands or for fast rollbacks. +- Testing Priority: Validate database checkpoints and restore flows; ensure image metadata and layers are handled consistently across commits. + +### Suggested Testing Matrix for the Fork + +| Feature | Target PR | Test Case | +| --- | --- | --- | +| **Persistence** | #1108 / #1190 | Verify that named volumes aren't "lost" and `cp` works across them. | +| **Security** | #1152 / #1166 | Ensure Compose-generated containers respect the new SELinux-off-by-default boot. | +| **Reliability** | #1208 | Launch a Compose stack with `mem_limit: 128mb` and verify the CLI surfaces validation errors correctly. | + +### Strategic Recommendation + +The most valuable addition would be **Auto-Start support**. With Apple adding `LaunchAgent` support (#1176) and a `--system-start` flag (#1201), the fork could introduce a `container-compose install-service` command that generates macOS LaunchAgents to auto-start stacks on boot. + +--- + +Would you like help drafting the Swift logic to map `restart: always` and related Compose keys to the engine `--restart` flag? (Can produce a focused patch for Sources/Container-Compose/Commands/ComposeUp.swift.) diff --git a/Sources/Container-Compose/Codable Structs/Service.swift b/Sources/Container-Compose/Codable Structs/Service.swift index 81f9328..c4d1807 100644 --- a/Sources/Container-Compose/Codable Structs/Service.swift +++ b/Sources/Container-Compose/Codable Structs/Service.swift @@ -86,6 +86,9 @@ public struct Service: Codable, Hashable { /// Platform architecture for the service public let platform: String? + /// Native init flag to request an init process (maps to container --init) + public let `init`: Bool? + /// Service-specific config usage (primarily for Swarm) public let configs: [ServiceConfig]? @@ -107,7 +110,7 @@ public struct Service: Codable, Hashable { // Defines custom coding keys to map YAML keys to Swift properties enum CodingKeys: String, CodingKey { case image, build, deploy, restart, healthcheck, volumes, environment, env_file, ports, command, depends_on, user, - container_name, networks, hostname, entrypoint, privileged, read_only, working_dir, configs, secrets, stdin_open, tty, platform, dns_search + container_name, networks, hostname, entrypoint, privileged, read_only, working_dir, configs, secrets, stdin_open, tty, platform, `init`, dns_search } /// Public memberwise initializer for testing @@ -223,6 +226,9 @@ public struct Service: Codable, Hashable { stdin_open = try container.decodeIfPresent(Bool.self, forKey: .stdin_open) tty = try container.decodeIfPresent(Bool.self, forKey: .tty) platform = try container.decodeIfPresent(String.self, forKey: .platform) + // Decode optional init flag (YAML key: init) + `init` = try container.decodeIfPresent(Bool.self, forKey: .`init`) + dns_search = try container.decodeIfPresent(String.self, forKey: .dns_search) } diff --git a/Sources/Container-Compose/Commands/ComposeUp.swift b/Sources/Container-Compose/Commands/ComposeUp.swift index f424ada..93f2b1d 100644 --- a/Sources/Container-Compose/Commands/ComposeUp.swift +++ b/Sources/Container-Compose/Commands/ComposeUp.swift @@ -744,6 +744,60 @@ public struct ComposeUp: AsyncParsableCommand, @unchecked Sendable { } // MARK: CommandLine Functions + + /// Helper for building the `container run` argument list for a service. Used by tests. + public static func makeRunArgs(service: Service, serviceName: String, dockerCompose: DockerCompose, projectName: String, detach: Bool, cwd: String, environmentVariables: [String: String]) throws -> [String] { + var runArgs: [String] = [] + + // Add detach flag if specified + if detach { + runArgs.append("-d") + } + + // Determine container name + let containerName: String + if let explicit = service.container_name { + containerName = explicit + } else { + containerName = "\(projectName)-\(serviceName)" + } + runArgs.append("--name") + runArgs.append(containerName) + + // Map restart policy if present + if let restart = service.restart { + runArgs.append("--restart") + runArgs.append(restart) + } + + // Map init flag if present (support both explicit Bool and optional presence) + // Note: Service may not include an `init` field; this helper will check for a computed property on Service via KeyedDecoding. + if let mirrorInit = Mirror(reflecting: service).children.first(where: { $0.label == "init" }), let value = mirrorInit.value as? Bool, value { + runArgs.append("--init") + } + + // Ensure entrypoint flag is placed before the image name when provided + let imageToRun = service.image ?? "\(serviceName):latest" + if let entrypointParts = service.entrypoint, let entrypointCmd = entrypointParts.first { + runArgs.append("--entrypoint") + runArgs.append(entrypointCmd) + // image follows flags + runArgs.append(imageToRun) + // append any remaining entrypoint args or command after image + if entrypointParts.count > 1 { + runArgs.append(contentsOf: entrypointParts.dropFirst()) + } else if let commandParts = service.command { + runArgs.append(contentsOf: commandParts) + } + } else { + runArgs.append(imageToRun) + if let commandParts = service.command { + runArgs.append(contentsOf: commandParts) + } + } + + return runArgs + } extension ComposeUp { /// Runs a command, streams stdout and stderr via closures, and completes when the process exits. diff --git a/Tests/Container-Compose-StaticTests/ComposeUpMappingTests.swift b/Tests/Container-Compose-StaticTests/ComposeUpMappingTests.swift new file mode 100644 index 0000000..8fbcfa8 --- /dev/null +++ b/Tests/Container-Compose-StaticTests/ComposeUpMappingTests.swift @@ -0,0 +1,56 @@ +import XCTest +@testable import ContainerComposeCore +import Yams + +final class ComposeUpMappingTests: XCTestCase { + func testRestartPolicyMapping() throws { + let yaml = """ + services: + web: + image: nginx:latest + restart: always + """ + let dockerCompose = try YAMLDecoder().decode(DockerCompose.self, from: yaml) + guard let service = dockerCompose.services["web"] ?? nil else { return XCTFail("Service 'web' missing") } + + // Expected: a helper that builds run args from a service. Tests written first (TDD). + let args = try ComposeUp.makeRunArgs(service: service, serviceName: "web", dockerCompose: dockerCompose, projectName: "proj", detach: false, cwd: "/tmp", environmentVariables: [:]) + + XCTAssertTrue(args.contains("--restart"), "Expected --restart flag present in args: \(args)") + XCTAssertTrue(args.contains("always"), "Expected restart value 'always' present in args: \(args)") + } + + func testInitFlagMapping() throws { + let yaml = """ + services: + app: + image: busybox:latest + init: true + """ + let dockerCompose = try YAMLDecoder().decode(DockerCompose.self, from: yaml) + guard let service = dockerCompose.services["app"] ?? nil else { return XCTFail("Service 'app' missing") } + + let args = try ComposeUp.makeRunArgs(service: service, serviceName: "app", dockerCompose: dockerCompose, projectName: "proj", detach: false, cwd: "/tmp", environmentVariables: [:]) + + XCTAssertTrue(args.contains("--init"), "Expected --init flag present in args: \(args)") + } + + func testEntrypointPlacedBeforeImage() throws { + let yaml = """ + services: + api: + image: nginx:latest + entrypoint: ["/bin/sh", "-c"] + """ + let dockerCompose = try YAMLDecoder().decode(DockerCompose.self, from: yaml) + guard let service = dockerCompose.services["api"] ?? nil else { return XCTFail("Service 'api' missing") } + + let args = try ComposeUp.makeRunArgs(service: service, serviceName: "api", dockerCompose: dockerCompose, projectName: "proj", detach: false, cwd: "/tmp", environmentVariables: [:]) + + guard let entryIdx = args.firstIndex(of: "--entrypoint"), let imageIdx = args.firstIndex(of: "nginx:latest") else { + return XCTFail("Expected both --entrypoint and image in args: \(args)") + } + + XCTAssertTrue(entryIdx < imageIdx, "Expected --entrypoint to appear before image, but args: \(args)") + } +} diff --git a/docs/FORK_README_UPDATE.md b/docs/FORK_README_UPDATE.md new file mode 100644 index 0000000..a323ae7 --- /dev/null +++ b/docs/FORK_README_UPDATE.md @@ -0,0 +1,10 @@ +Fork README additions (draft) + +Planned changes to leverage apple/container v0.10.0 features: + +- Map Compose `restart:` keys to engine `--restart` flag. +- Map `init: true` to engine `--init` flag and support `--init-image` selection. +- Ensure `--entrypoint` is passed in the correct position relative to the image name. +- Add a new `checkpoint` subcommand that uses `container commit`/export. + +Tests were added (ComposeUpMappingTests) to drive the implementation of the first set of changes.