Skip to content

Epic: split pkg/compose into focused internal packages #14396

Open

Description

@ndeloof

Context

pkg/compose is one flat package: about 62 non-test files and 19.5k lines, where every operation is a method of the same composeService (214 methods over 46 files). Any operation can therefore reach anything else — the Docker client, the event bus, the prompt, any other operation. Nothing but convention keeps a read-only command from depending on lifecycle code, and every test builds a whole service.

The package also exports about 60 top-level symbols, among them internals (Plan, Operation, ObservedState, Graph, the *Hash helpers). The module is /v5: every exported symbol is a compatibility commitment, and the more of the engine lives in the public package, the harder it is to change.

This epic tracks two linked goals, one reviewable PR at a time:

  1. splitting pkg/compose into focused packages under internal/;
  2. reducing its exported surface, before SDK callers build on symbols that were never meant to be public. The two go together: moving a cluster into internal/ is also what stops its types from being public API, so the public-surface items below are tracked here rather than separately.

Approach

The pattern settled by the first moves:

  • each cluster becomes its own internal/ package, starting with the most isolated ones;
  • it declares what it needs from the engine as a small Backend interface, and cannot reach anything else (importing pkg/compose back would be an import cycle);
  • composeService implements that interface and keeps one-line forwarders, so the callers of the moved code don't change;
  • behavior is unchanged, and tests move with the code with the same assertions.

The lifecycle clusters (the plan engine, up, down, …) come last, once the start-phase series of #14081 has landed.

Progress

Preparation and first moves

Remaining moves

The order is a proposal and each item is to be confirmed against what it needs from composeService before it starts.

  • Extract the shared kernel: the small helpers (progress events, container naming, …) that pkg/compose still keeps its own copies of next to the ones in internal/progress and internal/naming.
  • Images: pull, push, images and image pruning.
  • publish, together with pkg/compose/transform.
  • build (classic and bake).
  • logs and attach.
  • The plan engine (plan, reconcile, observed_state, executor), after the start-phase series of Epic: converge the start phase into the plan engine #14081.
  • The lifecycle commands (up, down, start, stop, restart, run, …) on top of the engine.

Public surface

What pkg/compose exports today is a compatibility commitment of the /v5 module for anything that doesn't need to be. These items shrink it, and are best done together with the moves above, since a package that moves to internal/ no longer exports its types.

  • Take the engine's types (Plan, Operation, ObservedState, Graph) and the ServiceHash / NetworkHash / VolumeHash helpers out of the exported API of pkg/compose.
  • Fix or remove WithDryRun: it is exported but takes the unexported *composeService, so it can't be used from outside the package.
  • Move pkg/compose/transform under internal/: it is exported but only publish uses it.
  • Move pkg/e2e out of pkg/: its framework and scenario code (framework.go, scenario.go, checks.go, assert.go) is non-test code published under pkg/.

Non-goals

  • No behavior change: a move PR is a move. Fixes found on the way go in their own PR.
  • No new public API: what a package needs from the engine goes through its Backend interface, not through an exported symbol of pkg/compose.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions