Skip to content

feat(rest): images pull + list end-to-end (POL-32)#637

Open
G4614 wants to merge 4 commits into
boxlite-ai:mainfrom
G4614:feat/rest-images-pull-mvp
Open

feat(rest): images pull + list end-to-end (POL-32)#637
G4614 wants to merge 4 commits into
boxlite-ai:mainfrom
G4614:feat/rest-images-pull-mvp

Conversation

@G4614
Copy link
Copy Markdown
Contributor

@G4614 G4614 commented Jun 1, 2026

REST/SDK runtime support image pull/list

Test plan

cargo nextest run -p boxlite-cli --test rest_images — 4 stub-driven tests:

scenario pre-fix post-fix
pull --profile p1 alpine:latest Image operations not supported over REST API, exit 1 POST /v1/images/pull with {"reference":"alpine:latest"}; stdout Pulled: alpine:latest + ID: sha256:...
pull --profile p1 -q alpine:latest same stdout id only (useful for IMAGE_ID=$(...))
images --profile p1 same GET /v1/images, renders table with docker.io/library/alpine latest
pull against server 422 image_pull_failed n/a exit≠0 + stderr includes server error msg

Additional server-side coverage:

  • BOXLITE_DEPS_STUB=1 cargo test -p boxlite-cli serve::handlers::images::tests
    • verifies POST /v1/images/pull response correlation matches the pulled manifest digest, not the user's raw reference (alpine vs docker.io/library/alpine:latest)
    • verifies the handler returns 500 with the requested ref + missing digest when pull succeeds but the cache listing does not contain the pulled digest

Two-side:

  • git checkout HEAD -- pull.rs images.rs reverts the CLI branching (server + REST client + tests preserved) → rest_images 4/4 fail. Restore → 4/4 pass.
  • Temporarily changing the server helper back to reference matching (i.reference == req_reference) makes pulled_image_response_matches_by_manifest_digest_not_user_reference fail with 500 vs expected 200. Restore digest matching (i.id == manifest_digest) → server handler tests pass.

Known follow-ups

  1. SDKs (Node / Python / C / Go) still expose only the local ImageObject shape; REST exposure is a separate PR.
  2. No streaming progress for image pull (OpenAPI doesn't ask for it either); current behavior is sync-block-until-pulled.
  3. GET /v1/images/{id} and the HEAD variant from OpenAPI aren't wired here; follow-up.

Implements the OpenAPI image endpoints (POST /v1/images/pull, GET
/v1/images) that have been spec'd but not wired. Server handlers
delegate to the local image manager — the REST server is itself a
local runtime behind HTTP, so the path that backs `boxlite pull`
over loopback is the same one that now backs `boxlite --profile p1
pull` over the wire.

Client side, `BoxliteRuntime::pull_image_remote` / `list_images_remote`
return the in-process `ImageInfo` shape via the new wire DTOs in
`rest::types`. Local callers keep `images()?.pull()` (which returns
the heavier `ImageObject` the SDKs depend on) — we don't unify the
two trait paths because `ImageObject` carries blob-store pointers
that have no meaning over the wire.

`boxlite pull` and `boxlite images` now branch on `is_rest()` and
route accordingly. SDKs (Node, Python, C) keep their local-only
shape — POL-32 is the CLI-side coverage; SDK exposure is follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@G4614 G4614 force-pushed the feat/rest-images-pull-mvp branch from 8133eaf to 8493590 Compare June 1, 2026 08:55
G4614 and others added 2 commits June 1, 2026 09:20
`pull_image` handler used `images.iter().find(|i| i.reference == req.reference)`
to look up the just-cached metadata. But `ImageManager::pull(ref)`
takes the *user's input* (e.g. `"alpine"`) while `ImageStore` keys the
cache index on the *resolved* reference yielded by `ReferenceIter`
(`docker.io/library/alpine:latest`, picked by registry-fallback in
`src/boxlite/src/images/store.rs:170-204`). Result: every unqualified
or partially-qualified pull (`alpine`, `alpine:latest`, `library/alpine`)
returned 500 InternalError "pull succeeded but image X did not appear
in the cache listing" — exactly the response when the lookup misses.

`ImageObject::reference()` still carries the user input (preserved for
backwards compat with the local `pull` CLI display), so we can't use
it as the join key either. The stable id is the manifest digest:
`ImageInfo::id` is `cached.manifest_digest`, and `ImageObject` already
has the same digest on `self.manifest.manifest_digest` — just not
exposed publicly.

Changes:
  - `src/boxlite/src/images/object.rs`: add
    `pub fn manifest_digest(&self) -> &str`, documenting why a caller
    needs to correlate by digest rather than reference.
  - `src/cli/src/commands/serve/handlers/images.rs`:
    - Bind the `ImageObject` returned by `handle.pull()` instead of
      discarding it.
    - Find list entry by `i.id == pulled.manifest_digest()`.
    - Surface the digest in the (now-unreachable-by-construction)
      500 fallback message so an operator who somehow hits it has
      something to grep.

Test gap (knowingly accepted): the existing `tests/rest_images.rs`
suite stubs the SERVER side from the CLI's POV, so it doesn't
exercise the actual `pull_image` handler. A handler-side test
needs a real `ImageManager` + registry scaffold (the existing
`serve` test module acknowledges "we can't construct a full
AppState without BoxliteRuntime" at mod.rs:1152). Leaving that
to a follow-up since the diff is mechanical and the bug is
unambiguous from the code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@G4614 G4614 marked this pull request as ready for review June 2, 2026 10:14
@G4614 G4614 marked this pull request as draft June 2, 2026 10:19
@G4614 G4614 force-pushed the feat/rest-images-pull-mvp branch from 545639a to c32c9ac Compare June 2, 2026 10:38
@G4614 G4614 marked this pull request as ready for review June 2, 2026 10:57
@cla-assistant
Copy link
Copy Markdown

cla-assistant Bot commented Jun 6, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ G4614
❌ Gamnaam Song


Gamnaam Song seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant