Skip to content

fix: recover Docker containers hidden from list - #7078

Open
jstar0 wants to merge 2 commits into
obot-platform:mainfrom
jstar0:fix/docker-inspect-conflict-fallback
Open

fix: recover Docker containers hidden from list#7078
jstar0 wants to merge 2 commits into
obot-platform:mainfrom
jstar0:fix/docker-inspect-conflict-fallback

Conversation

@jstar0

@jstar0 jstar0 commented Jun 30, 2026

Copy link
Copy Markdown

Summary

Refs #6543

Changes

  • Add an exact-name ContainerInspect fallback when Docker-compatible backends report a create name conflict but the name-filtered container list returns no match.
  • Convert the inspect response into the existing container summary shape so created containers can be adopted and started through the normal deployment path.
  • Keep ordinary container not-found lookups list-only so deploy, ensure, shutdown, and details paths do not gain an extra Docker API call.
  • Preserve valid published ports and exposed ports while ignoring empty or non-numeric host bindings.
  • Order inspected port bindings deterministically and prefer IPv4 loopback bindings for host-port selection.
  • Add regression coverage for the conflict/list-miss case, field preservation, invalid bindings, binding selection, and the ordinary not-found path.

Scope and risk

The inspect fallback is limited to the existing bounded create-conflict retry path. It requires an exact container-name match and does not change Kubernetes behavior, public APIs, or runtime configuration.

Verification

go test ./pkg/mcp -run 'Test(CreateAndStartContainerUsesInspectFallbackForCreatedNameConflict|GetContainerNotFoundDoesNotInspect|InspectResponseToSummaryPreservesDeploymentFields|InspectResponseToSummaryOrdersPublishedPorts|InspectResponseToSummaryKeepsLabelsWritable)$' -count=1
go test ./pkg/mcp -count=1
go test ./pkg/mcp ./pkg/controller/handlers/systemmcpserver -count=1
go test ./... -count=1
git diff --check

Copilot AI review requested due to automatic review settings June 30, 2026 12:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses a Docker-compatible backend edge case where container creation fails with a name conflict, but a subsequent name-filtered container list returns no match (observed with rootless Podman). It adds a ContainerInspect fallback to “adopt” the existing Created-state container and proceed through the normal start/deploy path, with regression tests covering the conflict/list-miss scenario and inspect-to-summary field preservation.

Changes:

  • Add ContainerInspect fallback in getContainer when name-filtered ContainerList yields no match.
  • Convert inspect responses into the existing container.Summary shape so downstream deployment logic can reuse existing containers.
  • Add tests covering inspect fallback adoption and field preservation in the inspect-derived summary.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
pkg/mcp/docker.go Adds inspect fallback in getContainer and introduces inspectResponseToSummary helper for adopting containers not returned by list.
pkg/mcp/docker_test.go Adds regression tests for the name-conflict + list-miss scenario and for inspect-to-summary field preservation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/mcp/docker.go
Comment on lines +736 to +738
if inspect.NetworkSettings != nil {
summary.NetworkSettings.Networks = inspect.NetworkSettings.Networks
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 3126636. The inspect conversion now derives Summary.Ports from NetworkSettings.Ports, including multiple host bindings and exposed ports without host bindings. Regression coverage verifies both cases.

Comment thread pkg/mcp/docker.go
Comment on lines +729 to +732
if inspect.Config != nil {
summary.Image = inspect.Config.Image
summary.Labels = maps.Clone(inspect.Config.Labels)
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 3126636. The initialized labels map is preserved when inspect Config.Labels is nil, and a regression test verifies the result remains writable.

Comment thread pkg/mcp/docker_test.go Outdated
var startCalls atomic.Int32

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/v1.52")

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 3126636. The mock now strips any numeric Docker API version prefix, and the regression test explicitly runs the client at API version 1.51.

Copilot AI review requested due to automatic review settings July 13, 2026 09:34
@jstar0
jstar0 force-pushed the fix/docker-inspect-conflict-fallback branch from be1cb8c to 3126636 Compare July 13, 2026 09:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread pkg/mcp/docker.go
Comment on lines +726 to +734
for _, binding := range bindings {
publicPort, _ := strconv.ParseUint(binding.HostPort, 10, 16)
summary.Ports = append(summary.Ports, container.Port{
IP: binding.HostIP,
PrivatePort: privatePort,
PublicPort: uint16(publicPort),
Type: port.Proto(),
})
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in e428eb3. inspectResponseToSummary now skips bindings whose HostPort is empty or non-numeric, while preserving exposed ports that have no host bindings. The regression test includes both invalid cases and verifies that no synthetic PublicPort=0 entries are emitted.

Comment thread pkg/mcp/docker.go
Comment on lines +680 to +684
inspect, err := d.client.ContainerInspect(ctx, name)
if cerrdefs.IsNotFound(err) {
return nil, nil
}
if err != nil {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in e428eb3. getContainer remains list-only; exact-name ContainerInspect is now called only after ContainerCreate returns a conflict or already-exists error and the list lookup misses. TestGetContainerNotFoundDoesNotInspect locks the ordinary not-found path at zero inspect calls, while the original conflict regression still requires exactly one inspect.

Copilot AI review requested due to automatic review settings July 15, 2026 15:54
@jstar0
jstar0 force-pushed the fix/docker-inspect-conflict-fallback branch from 3126636 to e428eb3 Compare July 15, 2026 15:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread pkg/mcp/docker.go
Comment on lines +703 to +727
if inspect.NetworkSettings != nil {
summary.NetworkSettings.Networks = inspect.NetworkSettings.Networks
for port, bindings := range inspect.NetworkSettings.Ports {
privatePort := uint16(port.Int())
if len(bindings) == 0 {
summary.Ports = append(summary.Ports, container.Port{
PrivatePort: privatePort,
Type: port.Proto(),
})
continue
}
for _, binding := range bindings {
publicPort, err := strconv.ParseUint(binding.HostPort, 10, 16)
if err != nil {
continue
}
summary.Ports = append(summary.Ports, container.Port{
IP: binding.HostIP,
PrivatePort: privatePort,
PublicPort: uint16(publicPort),
Type: port.Proto(),
})
}
}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in 749d037. inspectResponseToSummary now orders ports deterministically by private port and protocol, prioritizes published bindings, and prefers IPv4 loopback/IPv4 before IPv6 with stable IP and public-port tie-breakers. TestInspectResponseToSummaryOrdersPublishedPorts starts from an IPv6-first binding list, asserts the full order, and verifies getHostPort selects the 127.0.0.1 binding.

Copilot AI review requested due to automatic review settings July 15, 2026 16:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread pkg/mcp/docker.go
@@ -1071,6 +1176,12 @@ func (d *dockerBackend) createAndStartContainer(ctx context.Context, server Serv
if getErr != nil {
return "", 0, fmt.Errorf("failed to create container: %w", err)
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.

2 participants