Skip to content

refactor: move exec, cp, export and commit to internal/containerops - #14397

Merged
ndeloof merged 2 commits into
docker:mainfrom
ndeloof:refactor/internal-container-ops
Oct 27, 2026
Merged

ndeloof merged 2 commits into
docker:mainfrom
ndeloof:refactor/internal-container-ops

Conversation

@ndeloof

@ndeloof ndeloof commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

What this PR does, in one sentence

Moves exec, cp, export and commit — the operations that act from the outside on the container of a service — out of the flat pkg/compose package into internal/containerops, the next step of splitting pkg/compose after internal/inspect, internal/projectloader, internal/relay and internal/provider.

Context

pkg/compose is one package where every operation is a method of the same composeService and can therefore reach anything else. The earlier moves (#14331, #14333) settled the pattern for clusters that only needed the Docker client and the event bus. These four operations are the first ones moved that talk to the user: cp reads stdin and writes stdout when the path is -, export refuses to write a tar archive to a terminal, and exec hands the whole terminal over to the Docker CLI. They are also easy to detach (about 600 lines, and nothing else in the package calls them: they are only reached through the public API), so this is a good place to settle how the terminal crosses the boundary before logs, attach and run need it.

What the PR brings

  • internal/containerops, with a Backend of seven methods: the Docker client, the concurrency bound, the event bus, the dry-run switch, the container lookups (Containers, SpecifiedContainer) and the Docker CLI.
  • The terminal reaches the package through the Docker CLI the Backend already exposes, not through per-stream accessors: stdin, stdout and the TTY all come from CLI().In() / CLI().Out(). exec needs the whole CLI anyway (it runs the Docker CLI's own exec implementation), and the operations that follow (attach, run, logs) need more than a reader and a writer — raw mode, terminal size — so narrow Stdin()/Stdout() interfaces would only have been widened by the next move.
  • composeService implements the interface in pkg/compose/containerops.go and keeps Exec, Copy, Export and Commit as one-line forwarders; the progress wrapper of the last three stays there. No caller changes, and no other existing file of pkg/compose is touched apart from dropping checkRelayTarget, whose only callers moved.
  • Behavior is unchanged. The code moved with git mv: undoing the mechanical substitutions (receiver, calls through the Backend) on the new files gives back the old ones, except for the imports, the doc comments, the removed progress wrapper and one error event built with progress.Error instead of the package's own copy. The cp concurrency test is untouched apart from its call, and still runs through the forwarders; the exec, cp, export and commit end-to-end scenarios pass in both standalone and plugin mode.
  • Two small things are borrowed rather than copied a fourth time: the container name from internal/naming, the error event from internal/progress, and the OneOff enum of internal/inspect for the container lookups. The five-line bounded fan-out of cp is copied from pkg/compose (it is still used by pause and kill there); both belong to the shared kernel that a later step extracts.

Why this is the right next brick

It is the smallest cluster that exercises a terminal, the one thing the earlier moves never had to cross: it fixes the answer on an easy case, with no shared state and no lifecycle hook to cross, and nothing in flight touches these files, so no open pull request has to be rebased.

Part of #14396

🤖 Generated with Claude Code

…containerops

These four operations act from the outside on the container of a service.
They only need the Docker client, the concurrency bound, the event bus, the
dry-run switch, the Docker CLI (which owns the terminal streams) and the two
container lookups, so they move out of the flat pkg/compose package into
internal/containerops behind a Backend interface declaring exactly that.

composeService implements the interface in a new file and keeps Exec, Copy,
Export and Commit as one-line forwarders (the progress wrapper of the last
three stays there), so no caller changes. The now unused checkRelayTarget
forwarder is dropped. Behavior is unchanged: the cp test still runs through
the forwarders.

Part of docker#14396

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Cover the container selection and the refusals of Copy through a fake
Backend: the replica asked for, one-off containers only with --all, a copy
from a service reading its first container only, a service without
container, copying between services and a copy with no service at all.

Part of docker#14396

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
@ndeloof
ndeloof requested review from a team as code owners October 27, 2026 10:07

@docker-agent docker-agent 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.

Assessment: 🟢 APPROVE

@ndeloof
ndeloof requested a balanced review from Copilot October 27, 2026 10:17
@codecov

codecov Bot commented Oct 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.04651% with 12 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/containerops/cp.go 73.91% 4 Missing and 2 partials ⚠️
internal/containerops/export.go 60.00% 4 Missing ⚠️
internal/containerops/containerops.go 81.81% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

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.

🟢 Approval recommended

No active review issues were identified for the container-operations package split.

No open issues 🎉

What changed in this PR

Continues the package split tracked in #14396 by moving exec, cp, export, and commit into internal/containerops, while retaining the existing public API and progress wrappers in pkg/compose.

Changes:

  • Introduces a backend interface for Docker access, terminal streams, container lookups, progress events, and execution settings.
  • Moves the four operations into the internal package and adds forwarding methods in composeService.
  • Adds copy-target selection tests and routes the existing concurrency test through the public Copy method.
File Description
pkg/​compose/​relay.go Removes the relay-check wrapper whose callers moved.
pkg/​compose/​cp_test.go Exercises copy concurrency through the public forwarder.
pkg/​compose/​containerops.go Adds backend adaptation and public operation forwarders.
internal/​containerops/​export.go Moves export logic and uses shared progress helpers.
internal/​containerops/​exec.go Moves exec logic with backend-provided Docker CLI access.
internal/​containerops/​cp.go Moves copy logic to backend-provided clients and streams.
internal/​containerops/​cp_test.go Tests copy target selection and unsupported directions.
internal/​containerops/​containerops.go Defines the backend, service, and bounded concurrency helper.
internal/​containerops/​commit.go Moves commit logic behind the backend interface.

Review effort: Balanced

@ndeloof
ndeloof merged commit 3640112 into docker:main Oct 27, 2026
64 of 67 checks passed
@ndeloof
ndeloof deleted the refactor/internal-container-ops branch October 27, 2026 10:19

@docker-agent docker-agent 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.

Assessment: 🟢 APPROVE

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.

4 participants