Skip to content

fix(up): add Service.containerName helper for explicit container_name#113

Open
YvanY0 wants to merge 1 commit into
Mcrich23:mainfrom
YvanY0:fix/up-respect-container-name
Open

fix(up): add Service.containerName helper for explicit container_name#113
YvanY0 wants to merge 1 commit into
Mcrich23:mainfrom
YvanY0:fix/up-respect-container-name

Conversation

@YvanY0

@YvanY0 YvanY0 commented Jun 21, 2026

Copy link
Copy Markdown

The bug

When a compose file sets an explicit container_name, the container starts correctly — container run --name reads the value. But ComposeUp has three other places that look up the running container by name (waitUntilServiceIsRunning, getIPForRunningService, stopOldStuff), and all three hard-code "\(projectName)-\(serviceName)" instead of reading container_name.

For example, given:

services:
  hermes:
    container_name: hermes

Run from a directory named hermes (no name: field), the container starts as hermes but the post-start poll waits for hermes-hermes and times out:

Error Domain=ContainerWait Code=1 "Timed out waiting for container 'hermes-hermes' to be running."

Issue #38 reported the same bug on the down side; PR #39 fixed ComposeDown.stopOldStuff but ComposeUp was not addressed.

The fix

Extract Service.containerName(projectName:serviceName:) — a single method that encapsulates the rule (prefer explicit container_name, fall back to "\(projectName)-\(serviceName)") — and route all five call sites through it:

  • 4 call sites in ComposeUp (configService, waitUntilServiceIsRunning, getIPForRunningService, stopOldStuff)
  • stopOldStuff in ComposeDown (refactored to use the same helper, replacing the inline if/else from PR Handle compose down with container_name case #39)

When container_name is not set the helper returns the same "\(projectName)-\(serviceName)" string as before; existing compose files without explicit container names are unaffected.

Verification

Tested locally against the compose file above:

Before: container-compose up -d reports Timed out waiting for container 'hermes-hermes' to be running.

After: the container starts and polls successfully with no timeout; container ls -a shows it in running state.

Tests

  • Static: ServiceNamingTests — two unit tests for the pure containerName helper (explicit name wins, default pattern when unset).
  • Dynamic: testUpWithExplicitContainerName — exercises ComposeUp.run() end-to-end with an explicit container_name (mirrors the pattern in PR feat: Providing tests for ComposeDown #50's testUpAndDownContainerName).

Fixes #38

@Cyb3rDudu

Copy link
Copy Markdown
Collaborator

Nice fix - correct root cause, complete coverage (verified no hardcoded patterns remain), and good call centralizing the rule.
Two asks:

  1. the new dynamic test passes even if waitUntilServiceIsRunning is reverted because its timeout is swallowed by the run-loop catch { print(error) } - can you assert the wait/IP path actually succeeded, or unit-test those two functions directly?
  2. the old Info: Using explicit container_name log was dropped - worth re-adding for debuggability.

@Cyb3rDudu Cyb3rDudu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice fix - correct root cause, complete coverage (verified no hardcoded patterns remain), and good call centralizing the rule.
Two asks:

the new dynamic test passes even if waitUntilServiceIsRunning is reverted because its timeout is swallowed by the run-loop catch { print(error) } - can you assert the wait/IP path actually succeeded, or unit-test those two functions directly?
the old Info: Using explicit container_name log was dropped - worth re-adding for debuggability.

@YvanY0

YvanY0 commented Jun 24, 2026

Copy link
Copy Markdown
Author

Nice fix - correct root cause, complete coverage (verified no hardcoded patterns remain), and good call centralizing the rule.
Two asks:

the new dynamic test passes even if waitUntilServiceIsRunning is reverted because its timeout is swallowed by the run-loop catch { print(error) } - can you assert the wait/IP path actually succeeded, or unit-test those two functions directly?
the old Info: Using explicit container_name log was dropped - worth re-adding for debuggability.

Thank you @Cyb3rDudu, I will evaluate and push the update based on your comment.

@YvanY0 YvanY0 force-pushed the fix/up-respect-container-name branch from e724ebe to 270532a Compare June 24, 2026 14:15
ComposeUp had three places (getIPForRunningService, waitUntilServiceIsRunning,
stopOldStuff) that hard-coded the container name as
"\(projectName)-\(serviceName)" instead of reading service.container_name.
When a compose file set an explicit container_name, the container started
correctly (container run --name used the right value), but the post-start
lookups waited for the wrong name and timed out after 30s.

Extract a single Service.containerName(projectName:serviceName:) helper and
replace all five call sites (four in ComposeUp, one in ComposeDown) with it.

Follow-ups from review:
- testUpWithExplicitContainerName now asserts the container has a network
  with an IPv4 address after up, which only happens if both
  waitUntilServiceIsRunning and getIPForRunningService resolve a real
  container. This catches a regression where the run-loop catch swallows
  wait timeouts (the container would still be running but have no
  networks attached).
- Re-add the "Info: Using explicit container_name" log in configService
  that was dropped when centralizing the rule.
@YvanY0 YvanY0 force-pushed the fix/up-respect-container-name branch from 270532a to e1c1cf9 Compare June 24, 2026 14:34
@YvanY0

YvanY0 commented Jun 24, 2026

Copy link
Copy Markdown
Author

Nice fix - correct root cause, complete coverage (verified no hardcoded patterns remain), and good call centralizing the rule. Two asks:

  1. the new dynamic test passes even if waitUntilServiceIsRunning is reverted because its timeout is swallowed by the run-loop catch { print(error) } - can you assert the wait/IP path actually succeeded, or unit-test those two functions directly?

testUpWithExplicitContainerName now also asserts that the container has a network with an IPv4 address after up. This only happens if both waitUntilServiceIsRunning and getIPForRunningService resolve a real container, so the test now actually fails if either path is broken.

  1. the old Info: Using explicit container_name log was dropped - worth re-adding for debuggability.

Restored. configService now prints Info: Using explicit container_name: when service.container_name != nil

One more thing I noticed but didn't address in this PR. The do/catch { print(error) } silently drops the error: print goes to stdout, the exit code stays 0, so unittest will think the deploy succeeded. For reference, both docker compose up and podman-compose exit non 0 on partial failure and keep starting other services. The "abort on first failure" behavior is opt-in via --abort-on-container-exit.

Shape I'd propose for a follow-up:

// Errors.swift
public enum ServiceStartupError

// ComposeUp.swift — configService
do {
    try await waitUntilServiceIsRunning(service, serviceName: serviceName)
    try await updateEnvironmentWithServiceIP(service, serviceName: serviceName)
} catch {
    throw ServiceStartupError.failed(serviceName: serviceName, underlying: error)
}

// ComposeUp.swift — run() service loop
for (serviceName, service) in services {
    do {
        try await configService(service, serviceName: serviceName, from: dockerCompose)
    } catch let error as ServiceStartupError {
        if case .failed(let name, let underlying) = error {
            startupFailures.append((name, underlying))
        }
    } catch {
        throw error  // preflight errors (imageNotFound, etc.) still propagate
    }
}
if !startupFailures.isEmpty {
    throw ServiceStartupError.multipleFailures(startupFailures)
}

This is out of the scope of this PR, we can create an new issue to track this if needed. Fixing this do/catch would also strengthen ask 1's testUpWithExplicitContainerName — a swallowed wait timeout would now throw out of run() and fail the test directly, not just rely on the network-IP assertion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

compose down doesn't support named containers (or custom named containers)

2 participants