cli/grpc: add gRPC connections to the Docker Engine's API endpoint - #7183
cli/grpc: add gRPC connections to the Docker Engine's API endpoint#7183ndeloof wants to merge 4 commits into
Conversation
Allow packages outside cli/context/docker to build a TLS client configuration for a docker endpoint, such as the new cli/grpc package which needs it to establish ALPN-negotiated HTTP/2 connections. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Alongside its HTTP API, the daemon serves gRPC services on the same endpoint (e.g. BuildKit's control API, and services published by daemon extensions in the future). Since API v1.53 gRPC is served natively over HTTP/2: h2c on non-TLS connections, ALPN-negotiated HTTP/2 on TLS connections. Older daemons only expose gRPC through the deprecated "POST /grpc" HTTP/1.1 upgrade endpoint. Connect() hides those transport details behind the current docker context: it reuses the API client's dialer (unix/npipe sockets, plain tcp, ssh:// and other connection helpers) for h2c, performs its own ALPN-negotiated TLS handshake for tcp:// endpoints with TLS material, and falls back to the legacy upgrade endpoint when the daemon is older than API v1.53. This gives CLI plugins such as buildx and compose a single, shared way to reach the daemon's gRPC services; buildx currently hand-rolls the legacy hijack path. google.golang.org/grpc moves from indirect to direct dependency; the grpc/health packages are vendored for the tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Fix revive's import-shadowing on the DialHijack parameter name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Replace docker/cli with the cli-grpc branch (ndeloof/cli@ed1a1a1c0a81) providing cli/grpc.Connect, the single way for CLI plugins to establish a gRPC connection to the daemon's API endpoint whatever the transport of the current context. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Demo counterpart of the in-tree compose extension built on top of the moby extensions framework (moby/moby#53021): at the start of up, push the resolved Compose project (YAML) to the com.docker.compose.stack.v0 gRPC service the extended daemon exposes on its API endpoint. The connection goes through cli/grpc.Connect (docker/cli#7183), which hides the transport of the current context (unix/npipe socket, tcp with or without TLS, connection helpers) and the native-h2c vs legacy upgrade negotiation. Best effort: Unimplemented (no extension) and transport errors (no h2c) are debug-logged and up proceeds unchanged. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
An intermediary between the client and the daemon may not relay HTTP/2 even though the daemon's API version advertises native gRPC support — Docker Desktop's API proxy does not, at the time of writing. Probe native connections with a health-check RPC (any RPC-level outcome, including Unimplemented, proves the transport) and fall back to the legacy upgrade endpoint on transport-level failure. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Replace docker/cli with the cli-grpc branch (ndeloof/cli@b90d88ea3ad1) providing cli/grpc.Connect, the single way for CLI plugins to establish a gRPC connection to the daemon's API endpoint whatever the transport of the current context, falling back to the legacy upgrade endpoint when an intermediary does not relay HTTP/2. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Demo counterpart of the in-tree compose extension built on top of the moby extensions framework (moby/moby#53021): at the start of up, push the resolved Compose project (YAML) to the com.docker.compose.stack.v0 gRPC service the extended daemon exposes on its API endpoint. The connection goes through cli/grpc.Connect (docker/cli#7183), which hides the transport of the current context (unix/npipe socket, tcp with or without TLS, connection helpers) and the native-h2c vs legacy upgrade negotiation. Best effort: Unimplemented (no extension) and transport errors are debug-logged and up proceeds unchanged. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
|
Pushed b90d88e: native connections are now probed with a health-check RPC and fall back to the legacy upgrade endpoint on transport-level failure. Rationale: an intermediary may not relay HTTP/2 even when the daemon's API version advertises native support — Docker Desktop's API proxy does exactly that (engine reports API 1.55, h2c dial fails with "server preface … looked like an HTTP/1.1 header", while the legacy upgrade reaches the engine's gRPC server fine, so it is the proxy, not the engine). Covered by |
- What I did
Added a
cli/grpcpackage providing a single way to establish a gRPC client connection to the daemon's API endpoint, for CLI plugins consuming the daemon's gRPC services (BuildKit's control API today, services published by daemon extensions — moby/moby#53021 — tomorrow).Connect()(or the lower-levelDial()) hides the transport details of the current docker context:ssh://and other connection helpers), or an ALPN-negotiated TLS handshake fortcp://endpoints with TLS material;POST /grpcHTTP/1.1 upgrade endpoint for older daemons, as buildx currently hand-rolls.Also exports
Endpoint.TLSConfig()incli/context/docker(previously private), needed for the ALPN path.google.golang.org/grpcmoves from indirect to direct dependency.- How I did it
Daemon API version is probed with a plain
Ping; the connection is then established withgrpc.NewClientusing either a custom dialer (h2c, legacy upgrade) or gRPC's TLS credentials (ALPN). See the package documentation for details.Note — capability signal in Ping
Native connections are probed with a health-check RPC before being returned, because the API version alone cannot be trusted: an intermediary may not relay HTTP/2 even when the daemon advertises native support (Docker Desktop's API proxy today — its engine reports API 1.55 while only the legacy upgrade path gets through). The probe costs one round-trip per
Connecton the native path. If the daemon ever exposes an explicit capability signal in thePingresponse (e.g. aBuilder-Version-style header advertising native gRPC, which an intermediary that doesn't relay HTTP/2 would strip or rewrite), it should replace both the version gate and the probe.- How to verify it
go test ./cli/grpc/...covers the three paths (h2c over the client dialer, TLS+ALPN, legacy hijack fallback) against a real gRPC server.- Human readable description for the release notes
Created with: Claude Code