Repository navigation
fix(sandbox): negotiate the Docker Engine API version (#457) - #476
Open
lilyshen0722 wants to merge 1 commit into
Open
lilyshen0722 wants to merge 1 commit into
lilyshen0722 wants to merge 1 commit into
Conversation
…ab#457) The Engine client wrote /v1.54 into every request path. On Docker 29.1.3 (Engine API 1.52) every Sandbox create failed at its first request, GET /v1.54/volumes/{name}, with HTTP 400 "client version 1.54 is too new". Ephemeral-AI-Lab#459 made that message visible; this makes the client speak the Engine's version. The first request of a Docker client now reads the Engine's version once, with an unversioned GET /version, and every request of that client and its clones uses /v1.<minor>/ at the Engine's ApiVersion, up to 1.54. The call sites pass unversioned paths; Docker::send adds the prefix and transmit is the old send. An Engine below 1.48 is refused before any versioned request: an Unsupported failure with attempted false whose diagnostic names both versions ("Engine API 1.47 is older than the 1.48 to 1.54 this client speaks"). If GET /version cannot be read (not 200, not JSON, no ApiVersion) the client uses 1.54 as before, so nothing that works now regresses and the Engine's own refusal still reaches the caller. The probe is a separate read, never a retry: a request that fails is not sent again at another version. The floor comes from running it. The version history shows nothing the client uses changing after 1.30 (copyUIDGID) except the multiplexed-stream Content-Type on streams (1.42), and nothing in 1.53 or 1.54 touches a field it sends. But the daemon upload fails on Engines 23.0 to 27.5 (API 1.42 to 1.47), in PUT /containers/{id}/archive: "getent unable to find entry "0:0" in passwd database", the container's User under copyUIDGID. Engine 28.0 (API 1.48) and every later one tested pass. Tests: engine_protocol.rs gains four cases on a scripted Engine: a 1.52 Engine gets GET /version, then /v1.52 volume and create requests; a 1.56 Engine gets /v1.54; a 1.47 Engine is refused in the Volume phase with attempted false and both versions in the failure, /version the only request sent; a 404 on /version gets /v1.54. All four fail on the old client, which never asks /version. The peer in support/engine.rs and the Engine in engine_keepalive.rs answer that first request themselves (API 1.54) and leave it out of what finish() returns, so the existing tests keep their assertions; a peer with no responses expects no request at all. sandbox_create_debug_shows_docker_api_version_refusal_without_negotiating is renamed ..._without_retrying: it still proves the refused request is not resent. cargo test -p layerfs-sandbox: 36 pass. Rustfmt and Clippy -D warnings (workspace without layerfs-node) clean. Real SDK walk (LayerFS.init, open, sandbox.create, sandbox.close; debian:bookworm-slim sha256:7ff6ba31) in docker dind: Engine 29.1.3 (1.52) fails on main with the 400 above and passes now; 29.9.2 (1.56) and 28.0.4 (1.48) to 28.3.3 (1.51) pass; 27.5.1 (1.47) is refused with the message above. No tests deleted. Production LOC: 102200 -> 102288 (delta +88) Scope: first-party core, engine and API production source. Core 38241 and api 2646 unchanged; engine: 61313 -> 61401 (layerfs-sandbox 3329 -> 3417). Method: tools/production_loc.py --root on git archive of the first parent and the staged tree (5c6fe6f -> 563acd1). Reference absent (retired R9c; last counted 65,417 at tree 498dd19). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
We hit this while evaluating LayerFS for our agents' workspaces. On a fresh Ubuntu 24.04 VM, with the Docker 29.1.3 it came with (Engine API 1.52), every
sandbox.create()failed with a 400 before any container started. #459 made the reason visible; this makes the client speak the Engine's version.Fixes #457. The docs said the SDK does not negotiate on purpose, and you invited a fix, so here it is.
What changes
Dockerclient reads the Engine's version once with an unversionedGET /version. That client and its clones then use/v1.<minor>/at the Engine'sApiVersion, up to 1.54. Call sites pass unversioned paths;Docker::sendadds the prefix andtransmitis the oldsend.attempted: falseand a message naming both versions.GET /versioncan't be read (not 200, not JSON, noApiVersion), the client uses 1.54 as before. Nothing that works today changes, and the Engine's own refusal still reaches the caller through Surface Docker error details in sandbox failures #459.api/README.md,api/docs/agent.mdand the71-architecture note describe the new range.Why 1.48: Docker's version history shows nothing this client sends changing in 1.53 or 1.54. But on Engines 23.0 to 27.5 (API 1.42 to 1.47) the daemon upload fails in
PUT /containers/{id}/archivewithgetent unable to find entry "0:0" in passwd database(the container'sUserundercopyUIDGID). Engine 28.0 (API 1.48) and every later one tested pass. We didn't test 1.53 to 1.55, because no Engine release reports those versions.One choice you may want differently:
RuntimeError::Unsupportedcarries only a&'static str, so the refusal puts its version text in the failure'serror_bodydiagnostic. If you'd rather not reuse that field for a client-side message, I can add a variant toRuntimeErrorinstead.Before and after
Real SDK walk:
LayerFS.init,open,sandbox.create,sandbox.close, withdebian:bookworm-slim@sha256:7ff6ba31527b9e336162fa061746c366c4b2032f4d2cff2078bea43f78efe3f8, indocker:<version>-dind.Before, on main with Docker 29.1.3 (API 1.52):
sandbox create stopped at Create,phase: Volume,status: Some(400), withAfter:
Engines 28.0.4 (1.48) to 28.3.3 (1.51) pass. Engine 27.5.1 (1.47) is refused with
Engine API 1.47 is older than the 1.48 to 1.54 this client speaks.Tests
engine_protocol.rsgains four cases on a scripted Engine: a 1.52 Engine, a 1.56 Engine, a 1.47 Engine (refused, with/versionthe only request sent) and a 404 on/version. All four fail on main.support/engine.rsandengine_keepalive.rsanswer the version request themselves, so existing assertions are unchanged...._without_negotiatingbecomes..._without_retrying, which is what it still proves.cargo test -p layerfs-sandbox --lockedgives 36 passed. Each test command ran under a 120 s timeout.cargo fmt --all -- --checkand Clippy with-D warnings(workspace, withoutlayerfs-node) are clean, as istools/check_product_boundary.py --skip-sdk.Production LOC: 102200 -> 102288 (delta +88), all in
layerfs-sandbox(3329 -> 3417); the method is in the commit message.I work on Commonly (commonly.me), and we ran into this ourselves; the fix was written with AI assistance and the repro and tests were run as described.