Commit graph

6010 commits

Author SHA1 Message Date
hiroto.toyoda
727fa0e67d test(build): re-enable wrong ssh key id test
Some checks failed
ci / validate (lint) (push) Has been cancelled
ci / validate (validate-docs) (push) Has been cancelled
ci / validate (validate-go-mod) (push) Has been cancelled
ci / validate (validate-headers) (push) Has been cancelled
ci / validate (validate-mocks) (push) Has been cancelled
ci / binary (push) Has been cancelled
ci / bin-image-test (push) Has been cancelled
ci / test (push) Has been cancelled
ci / e2e (plugin, oldstable, graphdriver) (push) Has been cancelled
ci / e2e (standalone, oldstable, graphdriver) (push) Has been cancelled
ci / e2e (plugin, stable, containerd) (push) Has been cancelled
ci / e2e (plugin, stable, graphdriver) (push) Has been cancelled
ci / e2e (standalone, stable, containerd) (push) Has been cancelled
ci / e2e (standalone, stable, graphdriver) (push) Has been cancelled
merge / bin-image-prepare (push) Has been cancelled
merge / module-image (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
zizmor / zizmor (push) Has been cancelled
ci / binary-finalize (push) Has been cancelled
ci / coverage (push) Has been cancelled
ci / release (push) Has been cancelled
merge / bin-image (push) Has been cancelled
moby/buildkit#5558, which this test was disabled for, is fixed and
released well before the buildkit v0.26.3 currently vendored here.
Verified the re-enabled case passes (3 consecutive runs).

Signed-off-by: hiroto.toyoda <hiroto.toyoda@dena.com>
2026-08-28 09:39:00 +02:00
Nicolas De Loof
720da68475 chore(compose): comments state behavior, not history
Some checks failed
ci / validate (lint) (push) Waiting to run
ci / validate (validate-docs) (push) Waiting to run
ci / validate (validate-go-mod) (push) Waiting to run
ci / validate (validate-headers) (push) Waiting to run
ci / validate (validate-mocks) (push) Waiting to run
ci / binary (push) Waiting to run
ci / binary-finalize (push) Blocked by required conditions
ci / bin-image-test (push) Waiting to run
ci / test (push) Waiting to run
ci / e2e (plugin, oldstable, graphdriver) (push) Waiting to run
ci / e2e (standalone, oldstable, graphdriver) (push) Waiting to run
ci / e2e (plugin, stable, containerd) (push) Waiting to run
ci / e2e (plugin, stable, graphdriver) (push) Waiting to run
ci / e2e (standalone, stable, containerd) (push) Waiting to run
ci / e2e (standalone, stable, graphdriver) (push) Waiting to run
ci / coverage (push) Blocked by required conditions
ci / release (push) Blocked by required conditions
merge / bin-image-prepare (push) Waiting to run
merge / bin-image (push) Blocked by required conditions
merge / module-image (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
zizmor / zizmor (push) Waiting to run
docs-upstream / docs-yaml (push) Has been cancelled
docs-upstream / validate (push) Has been cancelled
Eleven comments in the reconciler and its neighbors justified decisions
by pointing at code that no longer exists ('matching the previous
ensureNetwork behavior', 'same way as convergence.go:138-160' — lines
that today hold unrelated code) instead of stating the behavior and its
reason. A reader following those pointers finds nothing, or worse,
something else.

Each is rewritten as a self-contained behavioral statement. Two carry
real weight:
- reconcileService's up-to-date branch now says explicitly that
  starting containers is NOT the plan's job (a separate start phase in
  start.go does it) — the previous wording invited 'fixing' the
  StateCreated case;
- sortContainers now documents that its ordering IS the scale-down
  policy (the tail it produces is what i >= expected trims), instead of
  referencing stale line numbers.

Part of #14074 (A: the code misdescribes its own structure).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-27 12:14:54 +02:00
Nicolas De Loof
e801d5fc77 chore(compose): rename convergence.go, whose convergence engine no longer exists
The convergence type this file was named after was removed in fbea647b9,
but the file kept its name and its doc comment ('convergence manages
service's container lifecycle') ended up attached to the unrelated
getScale(). Anyone asked to 'change the convergence logic' lands here,
while the actual reconciliation lives in reconcile.go.

The file is renamed to service_containers.go with a header stating what
it really contains: the per-service container helpers shared by both
lifecycle engines (naming, service-reference resolution, dependency
waiting, container creation, startup). getScale gets a doc comment that
describes getScale.

Part of #14074 (A: the code misdescribes its own structure).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-27 11:48:19 +02:00
Sebastiaan van Stijn
f3b0d952fd fix(deps): google.golang.org/grpc v1.83.2
Both v1.83.1 and v1.83.2 are security releases; update to keep the
scanners happy.

full diff: https://github.com/grpc/grpc-go/compare/v1.82.0...v1.83.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-08-27 11:21:32 +02:00
Sebastiaan van Stijn
ea5ff1896e chore(deps): github.com/sirupsen/logrus v1.10.2
update to reduce some transitive dependencies

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-08-27 11:21:32 +02:00
Nicolas De Loof
27b9995270 test(docs): compile the sdk.md examples
The fenced Go blocks in docs/sdk.md become real code under
docs/examples/sdk, built by the ordinary module build: API drift now
breaks compilation. A unit test pins the markdown to the compiled
files byte-for-byte (first block = main.go from its package clause
down, second block = customService's body), so wording drift breaks
the test instead of silently rotting the docs.

Epic #14074, section G.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-27 11:21:03 +02:00
Nicolas De Loof
926a61b4f7 docs(contributing): legibility guidelines — comments state behavior, not history
The 'why, history and context' guidance was how the section-A findings of
epic #14074 were born: comments justifying code by referencing since-
deleted code. Rule 4 now asks for behavioral statements, and a Legibility
section spells out the map-must-stay-truthful rules for comments, file
names and error messages.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-27 11:17:14 +02:00
Nicolas De Loof
8fbda262bb build: error messages name the real remedy, not a removed builder
The classic-builder rejections said 'set DOCKER_BUILDKIT=1 to use
BuildKit', a leftover from the internal BuildKit builder removed in
af579ebd4. Since then BuildKit builds go through the buildx plugin:
compose falls back to the classic builder when BuildKit is disabled
(DOCKER_BUILDKIT=0) OR when buildx is not installed — so the advertised
fix does nothing for users who are simply missing buildx.

The five messages now state the actual requirement (buildx installed,
DOCKER_BUILDKIT not forced to 0), and the silent-fallback warning when
buildx is missing now says what the fallback implies instead of a bare
'requires buildx plugin to be installed'.

Part of #14074 (A: the code misdescribes its own structure).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-27 11:05:09 +02:00
Nicolas De Loof
7af58e3626 test(e2e): gate the e2e suite behind the build tag the docs already promise
AGENTS.md documents 'Test unit: go test ./pkg/...' and 'E2E tests:
go test -tags e2e ./pkg/e2e/'. Both statements were false: no file in
pkg/e2e carried an e2e build constraint, so the -tags flag was a no-op
and the 'unit' command actually launched the full e2e suite — requiring
a Docker daemon and the locally built binary, creating and destroying
containers, for up to 20 minutes. CI only avoided this by grepping e2e
out of the package list (Dockerfile).

Every *_test.go in pkg/e2e now carries //go:build e2e (merged with the
existing !windows constraints), and the Makefile e2e targets pass the
tag. The documented commands become true:
- go test ./pkg/...          -> pkg/e2e reports [no test files]
- go test -tags e2e ./pkg/e2e -> runs the suite

AGENTS.md now also states the daemon/binary prerequisites explicitly.

Part of #14074 (A: the code misdescribes its own structure).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-27 11:02:07 +02:00
Nicolas De Loof
7b63e4fa33 docs(sdk): document the EventProcessor API that actually exists
sdk.md advertised a 'progress' package (progress.NewTTYWriter,
NewPlainWriter, NewJSONWriter, NewQuietWriter) that does not exist in
the repository, and typed WithEventProcessor against it. Anyone
following the doc writes code that cannot compile — or recreates the
phantom package.

The section now points at the real surface: api.EventProcessor, the
silent-by-default behavior when no processor is configured, and the
CLI's actual renderers in cmd/display (Full, Plain, JSON, Quiet) with
their real signatures.

Part of #14074 (A: the code misdescribes its own structure).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-27 10:57:44 +02:00
Guillaume Lours
03ad3cc1c2 fix(build): honor provenance/sbom false in per-service bake attest
Some checks are pending
ci / validate (lint) (push) Waiting to run
ci / validate (validate-docs) (push) Waiting to run
ci / validate (validate-go-mod) (push) Waiting to run
ci / validate (validate-headers) (push) Waiting to run
ci / validate (validate-mocks) (push) Waiting to run
ci / binary (push) Waiting to run
ci / binary-finalize (push) Blocked by required conditions
ci / bin-image-test (push) Waiting to run
ci / test (push) Waiting to run
ci / e2e (plugin, oldstable, graphdriver) (push) Waiting to run
ci / e2e (standalone, oldstable, graphdriver) (push) Waiting to run
ci / e2e (plugin, stable, containerd) (push) Waiting to run
ci / e2e (plugin, stable, graphdriver) (push) Waiting to run
ci / e2e (standalone, stable, containerd) (push) Waiting to run
ci / e2e (standalone, stable, graphdriver) (push) Waiting to run
ci / coverage (push) Blocked by required conditions
ci / release (push) Blocked by required conditions
docs-upstream / docs-yaml (push) Waiting to run
docs-upstream / validate (push) Blocked by required conditions
merge / bin-image-prepare (push) Waiting to run
merge / bin-image (push) Blocked by required conditions
merge / module-image (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
zizmor / zizmor (push) Waiting to run
When a service set `build.provenance: false` (or `sbom: false`), the
bake attest entry was silently omitted (the field is `omitempty`),
so BuildKit applied its defaults and attached a provenance attestation
anyway. The attestation changes the manifest-list digest on every build,
which caused containers to be needlessly recreated on `up --build`.

Emit `"type=<t>,disabled=true"` instead, which BuildKit's
`Attest.UnmarshalText` parses as `Disabled:true` and forwards to
BuildKit as an explicit disable signal.

Extract `appendAttest` to avoid duplicating the three-branch switch
for both provenance and sbom.

Fixes #14111

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
2026-08-25 17:46:03 +02:00
Nicolas De Loof
a472f714d5 registry: encode credentials with moby's authconfig package
EncodedAuth now delegates the X-Registry-Auth wire encoding to
moby/api/pkg/authconfig (same module, already required) instead of
hand-rolling the JSON+base64url pair, and push.go reuses it instead of
its own copy. The CLI and moby AuthConfig structs are field-for-field
identical, so a direct type conversion bridges them.

The explicit GetAuthConfigKey normalization drops out of EncodedAuth:
docker/cli's configfile.GetAuthConfig normalizes the Docker Hub domain
itself. It remains for the OCI resolver, whose credential callback
receives the network host actually contacted (registry-1.docker.io),
which the configfile does not map.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-25 17:27:26 +02:00
Nicolas De Loof
84dcd428ca remove buildx as a Go dependency
buildx was imported for two narrow purposes, both replaced by
docker/cli machinery compose already uses elsewhere:

- storeutil.GetProxyConfig -> configfile.ParseProxyConfig, the same
  lookup docker build uses (also picks up ALL_PROXY and lowercase
  variants)
- the dry-run image resolver (buildx builder + imagetools) -> a
  DistributionInspect through the daemon with credentials from the CLI
  config file, i.e. the same path a real pull/push would take; the
  encoded-auth helper moves to internal/registry, shared with pull.go

bake is unaffected: it already execs the buildx CLI plugin. Dropping
the module also drops the transitive aws-sdk-go-v2 footprint
(-328 lines of go.sum).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-25 17:27:26 +02:00
Guillaume Lours
6d3d68c1fa fix(display): stop ttyWriter.Done from hanging after context cancel
Fixes #14114: Done() blocked forever once the render goroutine had
already exited via ctx.Done() (e.g. after SIGTERM), since it sent on an
unbuffered channel with nobody left to receive.

Closing the channel instead of sending never blocks, but a bare close()
panics the moment ttyWriter runs a second Start/Done cycle on the same
shared bus - which already happens today (run's nested create/start,
publish's nested push, rm --stop's sequential stop then remove). Start
now hands out a fresh, once-only-closable signal each cycle instead of
a single shared channel, and run/publish stop opening a redundant
nested bracket around calls already inside one.

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
2026-08-25 11:38:50 +02:00
hiroto.toyoda
8ddbdc41a2 fix(watch): match Dockerfile/compose ignore patterns by literal name and basename
Signed-off-by: hiroto.toyoda <hiroto.toyoda@dena.com>
2026-08-24 14:48:22 +02:00
hiroto.toyoda
11d4aadf4f fix(watch): exclude Dockerfile and compose files from initial sync
Signed-off-by: hiroto.toyoda <hiroto.toyoda@dena.com>
2026-08-24 14:48:22 +02:00
Guillaume Lours
f729794270 docs(hooks): document pre_start hook container retention and cleanup
- compose_down.md: note that retained pre_start hook containers (those
  kept after a hook failure for post-mortem) are automatically removed
  by compose down, with the label filter to list them beforehand.
- compose_up.md: note the retention behavior on pre_start hook failure,
  how to inspect the retained container, and that it is cleaned up on
  the next compose up or compose down.

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
2026-08-20 18:57:02 +02:00
Guillaume Lours
54785c4be6 feat(hooks): clean up retained pre_start containers on compose down
A pre_start hook container retained after failure (introduced in the
previous commit) is created without ConfigHashLabel so getContainers()
and every downstream compose operation (down, ps, stop, rm) is blind
to it. Without explicit cleanup it would survive compose down.

This commit adds removePreStartHookContainers(), called from down()
after regular container teardown and before network removal (so hook
containers are gone before their networks are removed). The cleanup is
project-wide on a full compose down; scoped to the requested services
when compose down <svc...> is used. Individual ContainerRemove errors
are logged at warn level and do not abort the teardown.

Changes:
- pkg/compose/down.go: removePreStartHookContainers helper + call in down()
- pkg/compose/filters.go: add //nolint:unparam to hookFilter (param is
  architecturally correct; today only preStartHookType is in use)
- pkg/compose/down_test.go: update all existing down tests to expect the
  new ContainerList call; add TestDownRemovesRetainedPreStartHookContainers
  and TestDownHookContainerRemovalFailureIsNonFatal

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
2026-08-20 18:57:02 +02:00
Guillaume Lours
674010661a feat(hooks): retain failed pre_start containers for post-mortem
A failing pre_start hook container is now retained (AutoRemove: false)
so operators can run 'docker logs <id>' and 'docker ps -a' to diagnose
the failure. On success the container is removed explicitly, mirroring
the old AutoRemove behaviour including anonymous volumes.

Before each pre_start run, stale hook containers from a previous failed
run are detected via project+service+HookLabel filters and force-removed
so they do not accumulate.

Changes:
- pkg/api/labels.go: add HookLabel (com.docker.compose.hook)
- pkg/compose/filters.go: add hookFilter helper
- pkg/compose/pre_start.go:
  - AutoRemove: false in createPreStartContainer
  - HookLabel added to container labels
  - runPreStartHook: explicit ContainerRemove on success; retain on failure
  - removeOrphanPreStartContainers: new helper called in runPreStart
- Tests: update all existing pre_start tests for the new flow; add
  feature tests (success removes, failure retains, orphan cleanup) and
  coverage-gap tests (lowestNumberedContainer, waitPreStart cancel,
  preStartResultErr, streamPreStartLogs error paths, old-API network
  paths, ExecCreate/Attach/Inspect errors, hookExitError branches)

Coverage after: hook.go 100%, pre_start.go most functions 100%
(was 89% and 64% respectively per Codecov delta).

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
2026-08-20 18:57:02 +02:00
Guillaume Lours
bbec45855a fix(hooks): stderr-biased tail and pre_start parity
On top of the output-tail infrastructure from the previous commit:

Stderr bias: split the combined output tail into separate stdout and stderr
buffers. stdcopy.StdCopy(wOut, wErr, reader) now routes each stream to its
own outputTail. The error message prefers stderr (where hooks write their
actual error) and falls back to stdout when stderr is empty:

  db hook exited with status 1: Table 'service.sites' doesn't exist

A new hookExitError helper encapsulates this preference.

pre_start parity: streamPreStartLogs previously short-circuited and returned
immediately when listener was nil (detached mode / compose up -d), leaving
the tail buffers empty and failure errors without context. The log stream is
now always opened. The demultiplexed streams fill separate tailOut / tailErr
buffers. On non-zero exit the stderr-biased tail is appended to the error
(same behaviour as exec hooks).

Tests:
- TestRunHook_StderrBias: stdout noise stays out of the error when stderr
  has content.
- TestPreStart_DetachedModeAttachesLogs: ContainerLogs is called even with
  a nil listener.
- TestPreStart_FailureIncludesTail: a failing pre_start hook carries its
  stderr output in the returned error.

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
2026-08-20 18:57:02 +02:00
Nicolas De Loof
b857135cb9 fix: hold startMx on the start path actually exercised
startMx exists to serialize ContainerStart calls because the engine
assigns published ports from ranges non-atomically. It is held by the
plan executor's execStartContainer — a nearly dead code path (plans
only emit OpStartContainer for paused/dead containers) — and NOT by
startServiceContainer, the path every up/start actually runs. The
protection is thus inoperative today: concurrent services publishing
overlapping port ranges can race in the engine.

Take the mutex around the real ContainerStart call and document the
contract on the variable. Starts of different services were already
serialized per-service (replicas start sequentially); this serializes
the API call across services too, matching what the plan engine will
do once the start phase converges (#14081).

Part of #14074 (section C) / #14081 (lot 0).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-20 15:30:03 +02:00
Nicolas De Loof
06f4f9d622 fix: dependency wait timeout surfaced only by luck
waitDependency returned nil whenever its context ended, so an expired
wait deadline was reported only when the 500ms poll ticker happened to
win the select race and the condition check then failed on the expired
context. Whether `up --wait --wait-timeout` failed or silently
succeeded after the timeout was a coin toss, and the "timeout waiting
for dependencies" mapping in waitDependencies was unreachable on the
direct path.

Return the context error when the deadline expired; a plain
cancellation (Ctrl-C) is still not reported as a dependency failure.
The unit test pins both sides deterministically (a deadline shorter
than the first tick used to be swallowed with certainty); the e2e
scenario documents the user-visible contract: `up --wait
--wait-timeout` on a never-healthy service fails with "application not
healthy after Xs" while the container keeps running.

Part of #14074 (section C) / #14081 (lot 0).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-20 15:16:57 +02:00
Nicolas De Loof
899d040f3f ci: validate the generated mocks are in sync
`pkg/mocks` was only kept in sync by discipline: nothing failed when an
interface in `pkg/api` changed without regenerating. Add a
`mocks-validate` Dockerfile stage on the vendor-validate model, wire it
as a bake target, a `make validate-mocks` shortcut and a new entry in
the CI validate matrix.

Part of #14074 (section G).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-20 15:16:43 +02:00
Nicolas De Loof
6443bc1da2 chore(mocks): fix the stale mockgen invocation and pin its version
The `mocks` target still passed the pre-rename `Service` argument to
mockgen (the interface is `Compose` since the rename) and installed
whatever mockgen happened to be on PATH. Run it through `go run` with a
pinned version so regeneration is reproducible; v0.4.0 is the last
version usable here, as the package mode introduced in v0.5 copies
parameter names from the mocked source and moby's APIClient names its
variadic options `_`, which produces invalid Go code.

Regenerated pkg/mocks with the fixed invocation (header-only change).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-20 15:16:43 +02:00
Nicolas De Loof
cccb9f1f7e cli: display.Mode always resolves to the mode actually rendered
The display.Mode package global was left as "auto" on the most common
path (auto mode with an interactive or redirected terminal), so readers
like `up`'s NavigationMenu gate saw an unresolved value; that gate also
compared against the string literal "plain" instead of the constant.
applyDisplayMode wrote a mode that selectEventProcessor could then
contradict (--ansi always with a redirected stderr rendered Plain while
Mode said tty).

selectEventProcessor becomes the single resolution point: every branch
assigns the mode matching the renderer it returns, so after command
setup the global never holds ModeAuto. applyDisplayMode loses its
redundant writes and its misleading name (now applyAnsiMode); the
contract is documented on the Mode variable, and the existing
selectEventProcessor tests now pin the resolved mode for every branch.

Side effect, deliberate: with stderr redirected (auto -> plain
rendering) the navigation menu is no longer offered; it used to slip
through because Mode still read "auto".

Part of #14074 (section E).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-20 15:10:15 +02:00
Nicolas De Loof
0d7d22585a test: unit-lock the imperative start path
The imperative start engine (startService, startServiceContainer,
restart, waitDependencies) is only locked by e2e tests today. Before
converging it into the plan engine (#14081), pin its observable
behavior with unit characterization tests: start idempotence and the
silent no-op on running replicas, the "no container to start" error,
pre_start gating (mixed replicas skip the hook, all-stopped runs it
once on the lowest-numbered replica), the inject -> start -> post_start
sequence with Started only emitted after hooks, restart's depends_on
restart:true selection and its pre_stop -> ContainerRestart ->
post_start order, --wait's completed_successfully condition mapping,
and waitDependencies' missing/failing dependency semantics for
required and optional dependencies.

No production change.

Part of #14074 (section C) / #14081 (lot 0).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-20 15:09:16 +02:00
Guillaume Lours
3a62ae63ce fix(ps): honor psFormat from Docker CLI config when no --format flag given
The --format flag defaulted to "table" instead of "", so the
opts.Format == "" guard in runPs never fired for a bare compose ps.
Setting the default to "" lets the existing config-fallback path work,
matching the behaviour of docker ps.

Fixes #13643
Supersedes #14032

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
2026-08-20 14:05:34 +02:00
dependabot[bot]
c594d34f00 build(deps): bump github/codeql-action/upload-sarif
Bumps [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) from 4.37.6 to 4.37.7.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](5595ccaf91...ff2f1c621b)

---
updated-dependencies:
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-20 12:38:22 +02:00
Guillaume Lours
9e4a3c7457 fix(network): pass IPAM options when creating networks
createNetwork built the IPAM object in two separate blocks; the second
silently overwrote createOpts.IPAM with a fresh empty struct and never
forwarded n.Ipam.Options to the daemon. Consolidate into a single block
covering driver, config pool, and options.

Fixes #13785
Supersedes #13936

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
2026-08-20 12:36:23 +02:00
Nicolas De Loof
5671532583 api: RunOptions documents its partial embedding and its double life as ExecOptions
RunOptions embeds CreateOptions, which suggests every creation option
applies to the one-off's dependencies; in reality only Build,
IgnoreOrphans, RemoveOrphans and QuietPull are propagated
(startDependencies builds a fresh CreateOptions from exactly those
four). Setting Recreate or Inherit on a RunOptions was a silent no-op.
The same struct also serves Exec, which honors only the exec-relevant
subset of its ~20 fields.

Both facts are now stated on the type, the embedding and the
Exec-only Index field.

Part of #14074 (B: pkg/api promises things the implementation does not
honor).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-20 12:32:25 +02:00
Nicolas De Loof
62eb97e8e5 api: StartOptions says which half of it Start actually reads
StartOptions fuses two contracts: the fields s.start honors (Project,
AttachTo-as-project-source, Wait, WaitTimeout) and the fields only Up's
foreground session reads (Attach, OnExit, ExitCodeFrom, Watch,
NavigationMenu). Passing the latter to Start compiled, returned no
error, and did nothing.

Each session field is now marked 'honored by Up's foreground session
only; ignored by Start'. Two implicit behaviors become explicit along
the way: Attach doubles as the detached/interactive mode switch (nil
means Up returns once containers are started), and AttachTo has two
unrelated meanings (project reconstruction source for Start, log
scoping for Up).

Part of #14074 (B: pkg/api promises things the implementation does not
honor).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-20 12:32:25 +02:00
Nicolas De Loof
00ce78a055 api: state the scoping invariant Services fields never had
Three structs carry a Services field with three different meanings:
ProjectLoadOptions.Services narrows the loaded project (the only real
filter), CreateOptions.Services only selects which recreation policy
applies to which service while the WHOLE project converges, and
StartOptions.Services only scopes Up's log monitor and is ignored by
Start. Nothing said so: an SDK consumer calling
Create(project, CreateOptions{Services: []string{"web"}}) reasonably
expected to create web alone, and got the full project.

The interface doc now states the underlying invariant — methods act on
the whole project they receive; scoping is done by narrowing the
project, and downstream Services fields are intent markers, not
filters — and each of the three fields documents its actual effect.

Part of #14074 (B: pkg/api promises things the implementation does not
honor).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-20 12:32:25 +02:00
Guillaume Lours
ea8d9bc3db fix(watch): sync all files on initial_sync, regardless of mtime
initialSyncFiles and initialSyncDirectory filtered out any host file
whose mtime predated the image creation time — which is exactly the
files that initial_sync is meant to copy. The imageCreatedTime lookup
(ContainerList + ImageInspect) was also a silent failure point when the
container list returned nothing. Remove the filter and the helper
entirely: sync all files that are not ignored or bind-mounted.

Fixes #13725
Supersedes #14031

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
2026-08-20 12:05:36 +02:00
Guillaume Lours
e773a0789e docs: update CONTRIBUTING.md to reflect current tooling and practices
Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
2026-08-20 12:04:50 +02:00
Nicolas De Loof
24059c688c chore: explicit, unique, greppable names for internal identifiers
Rename-only change, no behavior difference, no exported identifier
renamed (only parameter names of a few exported functions, which are
not part of the Go API).

- names that hide what a value is: parameters holding a *name* or *ID*
  while named like the object — project→projectName, service→serviceName,
  container→containerID — so string vs struct is readable at every call
  site without opening the signature
- misleading receivers: monitor methods used c (reads as
  client/container/CLI) → m; jsonWriter used p while its ttyWriter
  sibling uses w → w
- cryptic abbreviations with non-trivial scope: nw (meant both
  network.Summary and types.NetworkConfig) → network/networkConfig,
  svc → service/serviceName/serviceCopy, oc/ocs → observedContainer(s),
  cnts → serviceContainers, cnx → attachResponse
- container.Summary loop variables unified on ctr (was a c/ctr mix);
  fillBindMounts also dropped its p/s/m single-letters, one of which
  was shadowed by a loop variable of a different type
- option-struct identifiers unified on options (opts remains only for
  variadic functional options); ambiguous ones get a precise name
  (bindOptions, networkCreateOptions, projectOptionsFns)

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-20 11:17:11 +02:00
Nicolas De Loof
3ee4b5c793 api: ScaleOptions carries the replica counts it always pretended to
The Compose interface promised 'Scale manages numbers of container
instances running per service', but ScaleOptions had no replica count:
callers had to know they must mutate project.Services[x] via SetScale
before calling, and ScaleOptions.Services only tuned the recreation
policy. An SDK consumer following the interface doc got a no-op
convergence of the whole project.

ScaleOptions gains Replicas (service -> count); the backend applies it
to the model itself before converging, and derives the targeted
services from its keys when Services is not set. The CLI now does what
a CLI should: parse SERVICE=REPLICAS tuples and hand them to the
backend, instead of pre-mutating the model. Passing a pre-mutated
project with an empty Replicas map still behaves as before, so
existing SDK callers are unaffected.

Part of #14074 (B: pkg/api promises things the implementation does not
honor).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-20 10:16:06 +02:00
Nicolas De Loof
9a70c817fa fix(build): TTY progress on Windows — hand the real stdout to buildkit
The bake progress display goes through containerd/console, which on
Windows only accepts the exact os.Stdin/Stdout/Stderr values (pointer
identity in newMaster) and rejects everything else with "creating a
console from a file is not supported on windows". The _console wrapper
introduced to satisfy buildkit's console.File type-assert therefore
could never pass that check: TTY progress silently fell back to plain
in auto mode, and --progress=tty / --ansi always failed hard with
"failed to get console".

When the CLI stream was constructed from a real file — the interactive
case, where it wraps os.Stdout — hand that genuine *os.File to
NewDisplay so the identity check passes. File-less streams keep the
wrapper, which works on Unix where only the descriptor matters.

Fixes #14086

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-19 18:06:55 +02:00
Guillaume Lours
8bd79da4bc e2e: fix test resource isolation and make standalone parallelism configurable
Several e2e tests used fixed host ports (8070, 8080, 8090, etc.), global
buildx builders registered with --use, and a hardcoded IPC container name.
Under parallel execution these shared daemon-level resources caused port
conflicts, buildkitd container-name collisions, and intermittent failures.
Separately, the standalone make target hardcoded -parallel=1, forcing the
entire standalone suite to run serially even though the same tests run in
parallel in plugin mode.

Changes:
- Convert fixed host ports in four fixtures (network-test, sentences,
  build-test, volume-test) to ephemeral bindings; add ServicePublishedPort
  helper to framework.go to resolve the actual mapped port at runtime.
- Give each buildx-builder test a unique daemon-scoped name via BuilderName
  helper, preventing container-name collisions between parallel goroutines.
  Applies to TestBuildPlatformsWithCorrectBuildxConfig, TestBuildPrivileged,
  TestBuildBuilder, TestBuildEntitlements, TestBuildTLS.
- Rename the fixed ipc_mode_container to a project-scoped name
  (ipc_e2e-src) and update the ipc-test fixture accordingly.
- Replace the hardcoded -parallel=1 in make e2e-compose-standalone with
  E2E_PARALLEL_PLUGIN?=4 and E2E_STANDALONE_PARALLEL?=4, aligning both
  modes with the effective GOMAXPROCS on 4-core GitHub Actions runners.
  Both variables are overridable (E2E_STANDALONE_PARALLEL=1 restores
  serial local debugging).
- Raise the poll delay in watch_test.go long-running polls (90s/120s
  timeout) from the default 100ms to 1s; bump cascade_test.go and
  publish_test.go from 100ms to 500ms. Reduces CPU pressure without
  affecting effective test duration.

The logging-driver fixture retains its fixed port 24224: the Docker fluentd
log driver encodes fluentd-address at service-start time, making ephemeral
resolution non-trivial. TestLoggingDriver is serial so there is no
collision risk in practice.

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
2026-08-19 17:41:02 +02:00
Deniss Solnce
f8ed846e4d fix(hooks): include hook output in the error when a hook fails
A failing service hook returns only its exit code:

    dependency failed to start: db hook exited with status 1

The output that explains why is not there, and for a hook running without a
listener it does not exist anywhere: runHook created the exec with
AttachStdout/AttachStderr false, so the daemon discarded both streams and
runWaitExec only polled for the exit code. There was nothing left to look at
afterwards.

Now stdout and stderr are always attached, and the last lines of them are kept
in a bounded buffer. On non-zero exit that tail goes into the error:

    db hook exited with status 1: SQLSTATE[42S02]: Base table or view not found

A successful hook is unchanged - output goes to the listener as before, nothing
is added anywhere. With a listener the lines still stream out, the buffer just
tees them.

The buffer keeps the last 10 lines and 2 KiB, drops the oldest content and never
short-writes, so a chatty hook cannot block on it and cannot grow it.

runWaitExec is removed: it existed only for the unattached branch, and that
branch is what threw the output away.

Signed-off-by: Deniss Solnce <dennis.lapchenko@gmail.com>
2026-08-19 15:08:25 +02:00
Nicolas De Loof
8b654eba78 docs: document engine container event sequences in up monitor
Map, per container lifecycle scenario, the exact event sequences the
engine emits and the monitor may receive: natural exit, policy-driven
restart (die/start only, no restart action, silence during backoff),
external stop of a running vs restarting container, ContainerRestart,
docker kill, OOM kill, rm -f and rm.

Mapped from moby's daemon sources (LogContainerEvent call sites and
handleContainerExit/containerStop/containerRestart/kill paths) and
verified against a live engine v29.6.1 with 'docker events'. Notably:
the relative order of stop and die on a container stop is not
guaranteed, and no event at all is emitted while a container sits in
restart backoff.

Related to #13985 / #13990.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-19 13:46:30 +02:00
Endika Iglesias
f519180b95 fix(watch): sync into a symlinked directory instead of failing
Signed-off-by: Endika Iglesias <endika2@gmail.com>
2026-08-18 17:25:13 +02:00
Nicolas De Loof
115898dc13 test(e2e): address review — project-name collisions, file modes, regex compilation
- projectNameFor maps the subtest separator to a double dash so
  TestFoo/bar and TestFooBar cannot resolve to the same compose project
  and interfere when running in parallel
- copyDir preserves the source file mode instead of forcing 0644, so an
  executable testdata script stays executable in the scenario's project
  directory
- OutputMatches and OutputMatchesCount compile their pattern once at
  construction: a malformed pattern panics where the check is declared,
  and Eventually loops no longer recompile it on every tick

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-18 16:49:56 +02:00
Nicolas De Loof
aef01ae584 test(e2e): batch snapshot inspect with per-container fallback, explicit suite timeout
The per-container inspect of the previous fix doubled the suite's
wall-clock on the containerd CI job — one docker CLI process per
container per snapshot — pushing it past go test's default 10m and
killing the run mid-flight. Snapshots go back to one batch inspect,
falling back to individual inspects only when the batch fails because
a helper container vanished. The e2e make targets also pass an
explicit -timeout 20m so a slow job fails on its own results, not on
the default timeout.

TestUpStopWithLogsMixed becomes deterministic: with a bounded ping the
service could exit on its own before the abort reached it and the
pre_stop hook never ran; it now pings forever so the abort always
interrupts it.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-18 16:49:56 +02:00
Nicolas De Loof
880c5a2f1d test(e2e): snapshot must survive vanishing helper containers
The plugin/stable/containerd CI job caught a race the DSL had: hook
helper containers carry the project and service labels but no replica
number, and can be destroyed between the snapshot's ps listing and its
batch inspect — which then failed and blanked the entire snapshot,
making ServiceState report 'service has no container' while the
container was right there. Containers are now inspected one by one,
tolerating disappearances, and helper containers (neither numbered as
a replica nor one-off) are excluded from snapshots altogether so they
can't transiently pollute service checks either.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-18 16:49:56 +02:00
Nicolas De Loof
1fa9ae160a test(e2e): image-identity, images and model scenarios
- TestUpIdempotentContainerdStore joins the scenario style of its
  corner-case siblings: Serial + ContainerdImageStore requirement, the
  regression story in a comment, NotRecreated as the invariant. The
  requireContainerdStore helper and the last identity fixture go away.
- TestImagesAfterImageRemoved scopes its throwaway tag to the project
  and observes container state before pulling the image record away.
- TestComposeModel inlines its model; verified in CI where the
  docker-model plugin is installed (the local runner cannot pull the
  model, as with the legacy test).

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-18 16:49:56 +02:00
Nicolas De Loof
447157274e test(e2e): build scenarios — the 'Built' family inlines its models
TestBuildDependsOn, Subset, DependentImage, SubDependencies,
LongOutputLine, DependentImageWithProfile, Escaped, Tags and Secrets
become scenarios: models inline (the long-line Dockerfile generated
with strings.Repeat instead of 34 copy-pasted lines), image names
project-scoped with Defer cleanup, and the secrets build asserts
ImageExists on top of the Dockerfile's own diff checks.

Kept legacy: TestLocalComposeBuild (DOCKER_BUILDKIT=0/1 matrix over a
shared fixture with fixed names and host port), the buildx-builder
family (SSH, platforms, privileged, entitlements, builder, TLS) which
provisions builders and dind daemons, and TestBuildImageDependencies
(classic/bake matrix). Their fixtures under build-test/ stay; the
single-purpose subdirectories are removed.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-18 16:49:56 +02:00
Nicolas De Loof
a7293130c6 test(e2e): compose command scenarios — rm, compatibility, config, orphan scoping
- TestRm, TestRemoveOrphaned and TestUnnecessaryResources observe which
  containers exist instead of grepping docker ps.
- TestCompatibility drops the heavyweight sentences fixture: underscore
  naming shows on any model.
- TestConfig/TestConfigInterpolate build their expected rendering from
  the scenario's own project name and directory; OutputMatches joins
  the vocabulary for TestInitContainer's ordering assertion.
- TestStopWithDependenciesAttached inlines its model.
- Kept legacy: TestLocalComposeUp (fixed port, HTTP, shared sentences
  fixture), TestDownComposefileInParentFolder (project name inferred
  from a fixture-relative directory), TestAttachRestart (long-running
  attached up), the COMPOSE_FILE-from-.env family and TestNestedDotEnv
  (they exercise invocation without -f, which ComposeCmd always sets).
- Fixtures init-container/, external/ and dependencies/ are removed.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-18 16:49:56 +02:00
Nicolas De Loof
37f57744de test(e2e): exec and provider scenarios
- TestExec locks the container-selection order (service container over
  one-off) with one-off state observed at each step.
- providers: a shared providerScenario helper wires the
  example-provider binary into PATH; the env assertions anchor on the
  'test-1  | ' log prefix, replacing the hand-rolled env parser, and
  the stop-hook sentinel file is observed with FileExists.
- Fixtures exec/ and providers/ are removed.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-18 16:49:56 +02:00
Nicolas De Loof
f4f6ac5324 test(e2e): volume and build-once scenarios
- volumes: external-volume switching, volume recreation on definition
  change and image volumes become scenarios; the two-compose-file
  fixtures collapse into one model with an interpolated volume name or
  label, so the changed definition is visible in the test. The rebuild
  scenario locks the Recreated expectation and scopes the source image
  to the project. TestLocalComposeVolume (fixed host port, HTTP) and
  TestProjectVolumeBind (host file written mid-test) stay legacy.
- build-once: the three run --build regression cases each get a
  scenario; OutputMatchesCount joins the vocabulary to count the build
  reports, replacing the hand-rolled regex counter, and image names are
  project-scoped instead of random suffixes.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-18 16:49:56 +02:00
Nicolas De Loof
ee7f323fed test(e2e): run and up scenarios
- compose run splits into seven scenarios (one-off basics, ports, deps,
  optional deps, quiet pull, --pull always, chained build contexts).
  The orphan-warning cases fold into the basic scenario: an exited
  one-off IS the orphan the next run warns about, no second compose
  file needed. Piped-stdin and stop-signal tests stay legacy.
- up/compose_up: unhealthy service, dependency exit (both now also
  locking that dependents stay in created state), build dependencies
  (project-scoped image name instead of the fixture's global one),
  optional dependency, --all-resources, profile targeting (dropping the
  fixture's global container_names), image-by-ID, exit-code-from (42
  and 143), port ranges, stdout/stderr separation (StderrContains joins
  the vocabulary) and logging-driver reconfiguration (now locking the
  Recreated expectation). The Ctrl-C dependency test stays legacy.
- Fixtures start-fail/, resources/, stop/, port-range/, stdout-stderr/,
  logging-driver/, profiles/ and most of dependencies/ and run-test/
  are removed.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-08-18 16:49:56 +02:00