Commit Graph
14 Commits
Author SHA1 Message Date
Nicolas De Loof eb0fb65e0c docs(provider), e2e: image distribution documented and exercised
extension.md gains the Image distribution section: invocation contract
(--image/--digest/--created/--source/--policy), the get-image request
and its chunked image-stream answer (zero-chunk terminated, stdin
exclusivity and drain-before-next-answer contract), the two-branch
decision guidance (digest as identity, created as ordering fallback,
reproducible builds caveat), and the support-by-presence metadata
convention is now stated as the general rule. The example provider
implements pull end to end — request, stock chunked reader, hash
recorded at PROVIDER_PULL_MARKER — and the e2e scenario locks the
whole path: up builds the service image and streams it to the provider
under the local verdict, compose pull re-invokes it under the
freshness contract.

Signed-off-by: Nicolas De Loof <[email protected]>
2026-10-02 17:35:20 +02:00
Nicolas De Loof bad7616c8f fix: close serveDemo stdout pipe on read failure
Addresses docker-agent review on #14258 (discussion_r4092089046):
when ReadString fails after Start() succeeds, the pipe read-end was
leaked -- nothing else calls Wait() on this deliberately long-lived
subprocess. Mirrors the existing close on the Start() failure path.

Signed-off-by: Nicolas De Loof <[email protected]>
2026-09-24 14:41:28 +02:00
Nicolas De Loof c4a0e9bf88 feat: get-relay-info tells providers where to bind for the relay
A provider running its service LOCALLY has to pick the address its
published endpoint binds, and on a standalone Linux engine no address
is both relay-reachable and off the LAN (#14257):
host.docker.internal resolves there to the bridge gateway, which a
loopback-only listener cannot accept, while the wildcard exposes the
port on every host interface. The bundled example provider worked
around it by binding 0.0.0.0.

The new get-relay-info message closes that gap, with compose owning
the platform knowledge so the provider just binds what is announced.
Compose answers with one JSON line listing the networks the relay
would join — the dependents' networks, exactly as selected for the
relay deployment — each with the address a locally-run endpoint should
bind to be reachable from the relay: the network's engine-assigned
gateway on a standalone engine (an address the host owns on that
bridge, reachable from the relay but not from the LAN), and 127.0.0.1
under Docker Desktop — the networks live inside the VM there, and the
host's own loopback is, factually, where a host process is reached
through the Desktop proxy. Resolution is lazy (Desktop detection and
network inspects run only when a provider asks — a remote-resource
provider never does) and best-effort: a missing gateway means "bind
elsewhere".

Compose also announces the message types it accepts in the
COMPOSE_PROVIDER_MESSAGES environment variable of the provider
process: an unknown message stays a fatal protocol error by design
(a provider REQUIRING an unsupported message must fail loudly), and
the announcement is how a provider adapts instead of failing. The
example provider binds the first announced gateway, falling back to
the wildcard only when none is announced; through it,
TestProviderPublishEndpoint exercises the gateway path on Linux CI
and the loopback path on Docker Desktop.

Signed-off-by: Nicolas De Loof <[email protected]>
2026-09-24 14:41:28 +02:00
Nicolas De LoofandClaude Fable 5 de2148f09e fix(relay): reap idle half-open forwards instead of pinning them forever
A peer that never closes after receiving the relayed FIN used to pin
the forward's goroutine pair and both TCP connections until SIGKILL —
and with them the drain in main. Force-closing both ends as soon as one
direction finishes would have thrown out TCP half-close (a client that
FINs its request and then reads a long response), so the surviving
direction now runs under an idle grace instead: a read deadline
re-armed before every Read once the other direction is done. Active
streams are never cut — only pairs sitting idle past the grace are
reaped. Locked by two tests: the silent-peer pair is reaped at the
grace, and a response still streaming after the client's half-close
survives well past it.

The demo provider's comment also spells out why serve-demo is neither
Wait()ed nor a zombie: the provider exits within seconds, so init has
long adopted — and reaps — the subprocess when its three minutes are up.

Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
2026-09-16 17:03:23 +02:00
Nicolas De LoofandClaude Fable 5 60cf6fc02f docs(provider): document the demo endpoint subprocess deliberate lifetime
The serve-demo subprocess is the demo's provisioned resource: it must
outlive the provider invocation — consumers reach it through the relay
after up returns — and it reaps itself after three minutes. Spell that
out where a reader (or reviewer) would otherwise expect a Kill/Wait.

Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
2026-09-16 17:03:23 +02:00
Nicolas De LoofandClaude Fable 5 29cb687b7a fix(provider): down removes the relay with the provider service
down handled a provider service by running the plugin alone — but the
service may own a project container, the relay deployed when it
published endpoints. Left running, it kept the project network in use
and `down -v` failed with "Resource is still in use".

The relay is now part of the service's deprovisioning: its containers
are removed first, then the plugin removes the provider's resource —
mirroring up, which provisions the resource before deploying the relay.

The example provider's down used to answer with a hardcoded error (a
leftover no test relied on): it now succeeds, with the failure
simulation kept behind PROVIDER_DOWN_FAILURE.

Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
2026-09-16 17:03:23 +02:00
Nicolas De Loof aab5819147 feat(provider): publish-endpoint deploys a network relay for provider services
A provider's resource lives outside the compose network: consumers could
only reach it through injected variables carrying a host-published
address — nothing like the compose-native experience of addressing a
service by name at its well-known port.

A provider may now publish where each endpoint of its resource actually
listens:

    {"type": "publish-endpoint", "message": "80=localhost:49152"}

The endpoint is announced as seen from the provider's host: the relay —
the component that knows it runs inside a container — rewrites loopback
or unspecified upstream hosts to host.docker.internal (resolved through
its injected host-gateway extra_host); routable addresses pass through.

When at least one endpoint is published, compose deploys a relay
container in place of the service: a minimal TCP forwarder (new relay/
directory, published as docker/compose-relay, overridable with
COMPOSE_RELAY_IMAGE for internal registries) joining the networks of the
services that depend on the provider service, aliased with the service
name. Consumers then use http://<service>:<port> as if the service were
a regular container.

The relay is a first-class project container — canonical name, standard
compose labels including config-hash (label-driven commands run without
the compose file keep seeing the service: ps, logs, stop, down) — plus
the com.docker.compose.relay label declaring its role:

- the reconciler already leaves provider services' containers alone, and
  the relay's identity hash (image + routes) makes up idempotent: kept
  when routes are unchanged, recreated otherwise;
- process-level commands (exec, cp) refuse a relay — there is no service
  process in it to act on;
- the up monitor excludes relays from the containers whose termination
  ends an attached up: they are long-lived infrastructure and would
  otherwise keep 'up' waiting forever.

The example provider demonstrates the flow behind PROVIDER_DEMO_ENDPOINT
(a detached helper serving a fixed HTTP response), backed by an e2e
scenario asserting the compose-native address works and exec is refused.

Signed-off-by: Nicolas De Loof <[email protected]>
2026-09-16 17:03:23 +02:00
Nicolas De Loof a255af9368 feat(provider): get-service-config provider request
Providers could not see the definition of the service they manage:
options had to be duplicated between the compose file and the provider,
or the provider had to re-resolve the model on its own.

A provider may now emit {"type": "get-service-config"} on stdout;
compose answers on the provider's stdin with one JSON line holding the
resolved canonical configuration of the provider's own service,
straight from the in-memory model. The message can be repeated; each
occurrence is answered with one line. Detection is by construction: a
compose that predates the message aborts on it and never writes to
stdin, so the provider treats EOF as 'unsupported, upgrade compose'.

The example provider demonstrates the round trip, backed by an e2e
scenario; unit tests drive executePlugin against a helper-process
provider and cover the injection.

Signed-off-by: Nicolas De Loof <[email protected]>
2026-09-08 14:53:14 +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 <[email protected]>
2026-08-27 11:21:03 +02:00
Yohta KimuraandClaude Opus 4.6 332e0add14 Add rawsetenv message type for provider plugins
Providers can now send rawsetenv messages to inject environment
variables into dependent services without the automatic service name
prefix. This enables use cases where applications require exact
variable names that cannot be altered.

Closes #13727

Signed-off-by: Yohta Kimura <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-06-23 15:01:33 +02:00
Guillaume Lours 672dc14d29 feat: add stop lifecycle hook for external providers
Provider-backed services were silently skipped on `docker compose stop`,
leaving external resources running after the user expected the stack to
be paused (e.g. after Ctrl+C during `up --watch`).

Compose now invokes `<provider> compose stop <service>` for providers
that advertise a `stop` block in their `metadata` subcommand output.
Providers that do not advertise stop (or do not implement metadata at
all) are silently skipped, preserving backward compatibility with
existing providers that pre-date this hook.

Closes #13772

Signed-off-by: Guillaume Lours <[email protected]>
2026-05-18 11:11:38 +02:00
Guillaume Lours 5a063b7510 fix provider concurrent environment map accesses
Signed-off-by: Guillaume Lours <[email protected]>
2025-06-30 19:07:10 +02:00
Guillaume Lours 40f5786e68 add support of metadata subcommand for provider services
This command will let Compose and external tooling know about which parameters should be passed to the Compose plugin

Signed-off-by: Guillaume Lours <[email protected]>
2025-06-05 15:12:32 +02:00
Nicolas De Loof 8a2cb90a39 example provider implementation
Signed-off-by: Nicolas De Loof <[email protected]>
2025-05-21 15:59:30 +02:00