Skip to content

refactor(docker): split driver into cohesive files; fix label match in FindContainerJSON#521

Merged
skevetter merged 2 commits into
mainfrom
wet-walrus
Jun 24, 2026
Merged

refactor(docker): split driver into cohesive files; fix label match in FindContainerJSON#521
skevetter merged 2 commits into
mainfrom
wet-walrus

Conversation

@skevetter

@skevetter skevetter commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Refactors the Docker driver for cohesion and fixes a latent bug found while reviewing the surrounding helper layer. Two logical commits.

1. Refactor — refactor(docker): split driver into cohesive files and tighten signatures

Splits the 1322-line pkg/driver/docker/docker.go into responsibility-grouped files, following the package's existing build.go/diagnostics.go convention:

File Responsibility
docker.go struct, NewDockerDriver factory, core helpers
lifecycle.go container lifecycle operations
runargs.go docker run argument construction
useruid.go container UID/GID remapping

Idiomatic cleanups (no new abstractions — the consumer-side driver.DockerDriver/Driver interfaces already exist):

  • Flatten runArgsBuilder's half-fluent/half-error chain into a uniform step list.
  • Collapse the 9-arm JetBrains IDE switch into a constructor lookup table.
  • Rename the config2/provider2 import aliases to pkgconfig/provider.

Promotes CommandDevContainer's 7 positional args to a driver.CommandParams struct on the Driver interface, consistent with the existing RunDockerDevContainerParams/BuildRequest pattern. Updates the docker, custom, and kubernetes drivers, the delivery adapter, and all call sites. Tests split to mirror the source.

2. Bug fix — fix(docker): match all labels in FindContainerJSON fallback

FindContainerJSON is the fallback container lookup used when docker ps --filter fails. Two defects fixed:

  • The label loop reassigned the match result each iteration instead of requiring every label to match, so a multi-label query honored only the last label and could select the wrong container.
  • containers[0] was accessed whenever inspect returned no error, but InspectContainers can return an empty slice, causing a panic.

Adds a regression test covering both cases (verified red against the old code, green against the fix).

Verification

  • go build ./..., go vet, and the affected test suites all pass.
  • golangci-lint run --new-from-rev=main ./... reports 0 new issues (matches CI's only-new-issues behavior).

Summary by CodeRabbit

  • New Features

    • Docker containers now support automatic user UID/GID synchronization with the host system
    • Enhanced container run argument construction and improved lifecycle management capabilities
  • Bug Fixes

    • Fixed container label filtering logic to correctly require all specified labels to match

…ures

Split pkg/driver/docker/docker.go (1322 LOC) into responsibility-grouped
files matching the package's existing build.go/diagnostics.go convention:
- docker.go: struct, factory, core helpers
- lifecycle.go: container lifecycle operations
- runargs.go: docker run argument construction
- useruid.go: container UID/GID remapping

Idiomatic cleanups (no new abstractions):
- Flatten runArgsBuilder's half-fluent chain into a uniform step list.
- Collapse the per-IDE JetBrains switch into a constructor lookup table.
- Rename the config2/provider2 import aliases to pkgconfig/provider.

Promote CommandDevContainer's 7 positional args to a driver.CommandParams
struct on the Driver interface, consistent with RunDockerDevContainerParams.
Updates the docker, custom, and kubernetes drivers, the delivery adapter,
and all call sites.

Tests split to mirror the source (runargs_test.go, useruid_test.go).
FindContainerJSON is the fallback container lookup used when
'docker ps --filter' fails. Two defects:

- The label loop reassigned the match result each iteration instead of
  requiring every label to match, so a multi-label query honored only the
  last label and could select the wrong container.
- containers[0] was accessed whenever inspect returned no error, but
  InspectContainers can return an empty slice, causing a panic.

Require all labels to match and skip entries with no inspect result. Adds
a regression test covering both cases.
@netlify

netlify Bot commented Jun 24, 2026

Copy link
Copy Markdown

Deploy Preview for images-devsy-sh canceled.

Name Link
🔨 Latest commit 394c3dd
🔍 Latest deploy log https://app.netlify.com/projects/images-devsy-sh/deploys/6a3b23d2cff0fd0008c8f20f

@netlify

netlify Bot commented Jun 24, 2026

Copy link
Copy Markdown

Deploy Preview for devsydev canceled.

Name Link
🔨 Latest commit 394c3dd
🔍 Latest deploy log https://app.netlify.com/projects/devsydev/deploys/6a3b23d2d442ff0008e2c2a1

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR introduces a CommandParams struct in pkg/driver/types.go and updates the Driver interface's CommandDevContainer signature to accept it, propagating the change across Docker, Kubernetes, and custom driver implementations and all call sites. It also splits the monolithic pkg/driver/docker/docker.go into separate lifecycle.go, runargs.go, and useruid.go files, each with dedicated tests. Additionally, FindContainerJSON is corrected to require all labels to match.

Changes

Driver Interface Refactor and Docker Driver Split

Layer / File(s) Summary
CommandParams struct and Driver interface update
pkg/driver/types.go
Adds the exported CommandParams struct (WorkspaceID, User, Command, Stdin, Stdout, Stderr) and updates CommandDevContainer in the Driver interface to accept a single *CommandParams instead of discrete positional arguments.
CommandDevContainer callers migrated to CommandParams
pkg/agent/delivery/factory.go, pkg/devcontainer/run.go, pkg/devcontainer/setup.go, pkg/devcontainer/delete_test.go, pkg/driver/custom/custom.go, pkg/driver/kubernetes/driver.go
All call sites and driver implementations construct and pass *driver.CommandParams instead of positional arguments; the factory wraps the driver command builder, and the Kubernetes driver extracts fields from params when building the exec command.
docker.go trimmed: imports and NewDockerDriver signature updated
pkg/driver/docker/docker.go, pkg/driver/docker/docker_test.go
Rewrites imports to use provider directly, changes NewDockerDriver's first parameter type, and removes the methods now split into dedicated files; test file is trimmed of no-longer-applicable imports and test methods.
Docker lifecycle.go: CommandDevContainer, container management, RunDockerDevContainer
pkg/driver/docker/lifecycle.go
New file implementing CommandDevContainer (find + ensure running + exec), container start/stop/delete/push/tag, image inspect/tag retrieval, container logs, EnsureImage (inspect then pull), RunDockerDevContainer orchestration, and the internal startContainer helper.
runargs.go: docker run argument builder and helpers
pkg/driver/docker/runargs.go, pkg/driver/docker/runargs_test.go
New file adding runArgsBuilder and buildRunArgs with all flag-building steps (ports, workspace mount, user, env, init, privileged, Podman/userns, capabilities, mounts, IDE volumes, labels, GPU, platform, entrypoint, image/cmd), bind-cache busting, mount consistency stripping, GPU resolution, Podman userns logic, and EnsurePath; accompanied by tests covering capabilities, mount helpers, version gating, and platform injection.
useruid.go: container user UID/GID update
pkg/driver/docker/useruid.go, pkg/driver/docker/useruid_test.go
New file implementing UpdateContainerUserUID orchestration, skip/update decision helpers, passwd/group file parsing and rewriting via docker cp, temp file lifecycle, and permission application; accompanied by tests covering all skip/update decision paths, user selection priority, and update requirements.

FindContainerJSON Label Matching Bug Fix

Layer / File(s) Summary
FindContainerJSON: AND label matching and inspect error handling
pkg/docker/helper.go, pkg/docker/helper_test.go
Fixes the label-filter loop to break on the first mismatching label (AND semantics) and skips container IDs when inspection returns an error or empty results; adds TestFindContainerJSON_MatchesAllLabels verifying exclusive AND behavior and safe empty-inspect handling.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(135, 206, 235, 0.5)
    note over Caller,UpdateContainerUserUID: RunDockerDevContainer flow
    participant Caller
    participant RunDockerDevContainer as dockerDriver.RunDockerDevContainer
    participant EnsureImage as dockerDriver.EnsureImage
    participant buildRunArgs as runArgsBuilder.buildRunArgs
    participant startContainer as startContainer
    participant UpdateContainerUserUID

    Caller->>RunDockerDevContainer: RunDockerDevContainer(ctx, params)
    RunDockerDevContainer->>EnsureImage: EnsureImage(ctx, options)
    EnsureImage-->>RunDockerDevContainer: image ready (pulled if missing)
    RunDockerDevContainer->>buildRunArgs: buildRunArgs(workspaceId, options, parsedConfig)
    buildRunArgs-->>RunDockerDevContainer: []string docker run args
    RunDockerDevContainer->>startContainer: startContainer(ctx, args, workspaceFolder)
    startContainer-->>RunDockerDevContainer: container started
    RunDockerDevContainer->>UpdateContainerUserUID: UpdateContainerUserUID(ctx, workspaceId, config)
    UpdateContainerUserUID-->>RunDockerDevContainer: UID/GID updated
    RunDockerDevContainer-->>Caller: nil or error
  end
Loading
sequenceDiagram
  rect rgba(255, 200, 100, 0.5)
    note over Caller,dockerDriver: CommandDevContainer (updated signature)
    participant Caller
    participant Driver as Driver interface
    participant dockerDriver as dockerDriver.CommandDevContainer
    participant ensureContainerRunning
    participant DockerExec as docker exec

    Caller->>Driver: CommandDevContainer(ctx, *CommandParams)
    Driver->>dockerDriver: dispatch with params
    dockerDriver->>ensureContainerRunning: ensureContainerRunning(ctx, container)
    ensureContainerRunning-->>dockerDriver: container running
    dockerDriver->>DockerExec: exec params.Command as params.User
    DockerExec-->>dockerDriver: stdout/stderr to params.Stdout/Stderr
    dockerDriver-->>Caller: error or nil
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • devsy-org/devsy#265: Modifies pkg/agent/delivery/factory.go's CommandFunc adapter to wire driver command execution, directly overlapping with this PR's update of the same adapter to pass *driver.CommandParams.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures both main changes: the Docker driver refactoring into cohesive files and the FindContainerJSON label matching bug fix.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@skevetter
skevetter marked this pull request as ready for review June 24, 2026 00:48

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
pkg/docker/helper_test.go (1)

122-147: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an explicit inspect-error assertion path in this regression test.

This test validates the empty-inspect branch, but the new guard also handles InspectContainers errors. Adding one container whose inspect exits non-zero would pin both fixed branches in one place.

Suggested test extension
 func TestFindContainerJSON_MatchesAllLabels(t *testing.T) {
@@
-  // last label (an earlier label differs); c3 inspect returns an empty array.
+  // last label (an earlier label differs); c3 inspect returns an empty array;
+  // c4 inspect returns an error.
   bin := writeScript(t, tmp, "docker-fake", `#!/bin/sh
 case "$1" in
-  ps) printf 'c1\nc2\nc3\n' ;;
+  ps) printf 'c1\nc2\nc3\nc4\n' ;;
   inspect)
     case "$4" in
       c1) echo '[{"ID":"c1","Config":{"Labels":{"a":"x","b":"y"}}}]' ;;
       c2) echo '[{"ID":"c2","Config":{"Labels":{"a":"zzz","b":"y"}}}]' ;;
       c3) echo '[]' ;;
+      c4) exit 1 ;;
     esac ;;
 esac
 `)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/docker/helper_test.go` around lines 122 - 147, The regression test
TestFindContainerJSON_MatchesAllLabels currently validates the empty-inspect
branch (c3 returning an empty array) but does not test the error path when
InspectContainers fails. Add a fourth container (e.g., c4) to the docker-fake
script's inspect case statement that exits with a non-zero status to simulate an
inspect error, and update the final assertion to confirm that the function
properly handles both the empty-inspect result and the inspect-error cases while
still correctly filtering and returning only the c1 container that matches all
labels.
pkg/driver/docker/runargs.go (1)

477-496: 🎯 Functional Correctness | 🔵 Trivial

EnsurePath mutates the caller's Mount in place and should operate on a copy.

It rewrites path.Source on the shared pointer (options.WorkspaceMount) and returns the same pointer. While the current call path creates fresh RunOptions on each invocation, the mutation pattern is fragile and violates the principle of immutability for function inputs. Consider operating on a copy to make the function idempotent and easier to reason about.

♻️ Avoid in-place mutation
 func (d *dockerDriver) EnsurePath(path *config.Mount) *config.Mount {
 	// Local Windows to remote Linux over TCP requires manual path conversion.
 	if runtime.GOOS == "windows" {
 		for _, v := range d.Docker.Environment {
 			if strings.Contains(v, "DOCKER_HOST=tcp://") {
 				unixPath := path.Source
 				unixPath = strings.Replace(unixPath, "C:", "c", 1)
 				unixPath = strings.ReplaceAll(unixPath, "\\", "/")
 				unixPath = "/mnt/" + unixPath
-
-				path.Source = unixPath
-
-				return path
+				clone := *path
+				clone.Source = unixPath
+				return &clone
 			}
 		}
 	}
 	return path
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/driver/docker/runargs.go` around lines 477 - 496, The EnsurePath method
is mutating the input path parameter directly by modifying path.Source in place.
To fix this, create a copy of the input *config.Mount parameter at the start of
the function and perform all modifications on the copy instead of the original.
Update all assignments to path.Source to assign to the copy's Source field
instead, and ensure both return statements return the copy rather than the
original path pointer. This will make the function idempotent and prevent
unintended mutations of the caller's data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/driver/docker/lifecycle.go`:
- Around line 250-255: The log.Infof calls that log docker command arguments
expose sensitive information such as environment variables and secrets in the
logs. Create a helper function to redact sensitive arguments (typically those
following -e or --env flags) from the args slice before logging, and use this
redacted version in both the log.Infof call around the first docker command
logging and the second location where docker arguments are logged. This prevents
secrets and PII from being leaked into the logs while still providing useful
debugging information about the command being executed.

---

Nitpick comments:
In `@pkg/docker/helper_test.go`:
- Around line 122-147: The regression test
TestFindContainerJSON_MatchesAllLabels currently validates the empty-inspect
branch (c3 returning an empty array) but does not test the error path when
InspectContainers fails. Add a fourth container (e.g., c4) to the docker-fake
script's inspect case statement that exits with a non-zero status to simulate an
inspect error, and update the final assertion to confirm that the function
properly handles both the empty-inspect result and the inspect-error cases while
still correctly filtering and returning only the c1 container that matches all
labels.

In `@pkg/driver/docker/runargs.go`:
- Around line 477-496: The EnsurePath method is mutating the input path
parameter directly by modifying path.Source in place. To fix this, create a copy
of the input *config.Mount parameter at the start of the function and perform
all modifications on the copy instead of the original. Update all assignments to
path.Source to assign to the copy's Source field instead, and ensure both return
statements return the copy rather than the original path pointer. This will make
the function idempotent and prevent unintended mutations of the caller's data.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cd59bc22-b410-4619-b8a4-54937f9a1688

📥 Commits

Reviewing files that changed from the base of the PR and between 5c32784 and 394c3dd.

📒 Files selected for processing (16)
  • pkg/agent/delivery/factory.go
  • pkg/devcontainer/delete_test.go
  • pkg/devcontainer/run.go
  • pkg/devcontainer/setup.go
  • pkg/docker/helper.go
  • pkg/docker/helper_test.go
  • pkg/driver/custom/custom.go
  • pkg/driver/docker/docker.go
  • pkg/driver/docker/docker_test.go
  • pkg/driver/docker/lifecycle.go
  • pkg/driver/docker/runargs.go
  • pkg/driver/docker/runargs_test.go
  • pkg/driver/docker/useruid.go
  • pkg/driver/docker/useruid_test.go
  • pkg/driver/kubernetes/driver.go
  • pkg/driver/types.go

Comment on lines +250 to +255
log.Infof(
"running docker command: command=%s, args=%s, cwd=%s",
d.Docker.DockerCommand,
strings.Join(args, " "),
dir,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Redact docker run arguments from startup/error logs.

Line 250 and Line 263 log the full args list. These arguments can include sensitive env values, so this leaks secrets/PII to logs.

🔧 Proposed fix
 	log.Infof(
-		"running docker command: command=%s, args=%s, cwd=%s",
+		"running docker command: command=%s, cwd=%s",
 		d.Docker.DockerCommand,
-		strings.Join(args, " "),
 		dir,
 	)
@@
 		log.Errorf(
-			"docker container failed to start: error=%v, command=%s, args=%s, cwd=%s",
+			"docker container failed to start: error=%v, command=%s, cwd=%s",
 			err,
 			d.Docker.DockerCommand,
-			strings.Join(args, " "),
 			dir,
 		)

Also applies to: 262-267

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/driver/docker/lifecycle.go` around lines 250 - 255, The log.Infof calls
that log docker command arguments expose sensitive information such as
environment variables and secrets in the logs. Create a helper function to
redact sensitive arguments (typically those following -e or --env flags) from
the args slice before logging, and use this redacted version in both the
log.Infof call around the first docker command logging and the second location
where docker arguments are logged. This prevents secrets and PII from being
leaked into the logs while still providing useful debugging information about
the command being executed.

@skevetter
skevetter merged commit 1bacaf9 into main Jun 24, 2026
58 checks passed
@skevetter
skevetter deleted the wet-walrus branch June 24, 2026 01:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant