mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-29 13:35:35 +00:00
hold: report blob size on read presigns so the appview can skip its S3 HEAD
distribution calls Stat before every blob GET and HEAD. The appview's Stat asked the hold for a presigned HEAD URL and then HEADed S3 with it purely to read Content-Length for the descriptor: two round trips to learn one number. The hold's getBlob response for OCI digests on GET and HEAD now carries "size". It comes from the records index when a layer record exists (a SQLite lookup on a new digest index, no network) and from a HeadObject otherwise, which is where config blobs land. If storage says the object does not exist the hold answers 404 instead of signing a URL that can only fail. The PUT and ATProto CID paths are untouched. The appview builds the descriptor from the reported size and makes no S3 request. When the field is absent it HEADs the presigned URL as before, so a new appview works against a hold that has not been upgraded, and an old appview ignores the extra field. A hold 404 maps to ErrBlobUnknown. Tests prove the index answered by leaving the mock bucket empty and counting zero HeadObject calls, prove the fallback with exactly one, and count requests reaching the fake S3 origin on the appview side rather than trusting the returned size. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Yf1ZVA7sXYhQNb9tCo1m5
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
61a934debb
commit
034ea5988b
@@ -33,6 +33,39 @@ This document lists all XRPC endpoints implemented in the Hold service (`pkg/hol
|
||||
|----------|--------|-------------|
|
||||
| `/xrpc/com.atproto.sync.getBlob` | GET/HEAD | Get blob (routes OCI vs ATProto) |
|
||||
|
||||
#### getBlob response shape
|
||||
|
||||
The endpoint routes on the `cid` parameter and answers differently per branch.
|
||||
|
||||
**ATProto blob** (`cid` is a CID): 307 redirect to a presigned URL.
|
||||
|
||||
**OCI blob** (`cid` starts with `sha256:`): JSON.
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "https://s3.example.com/...?X-Amz-Signature=...",
|
||||
"size": 27129344
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `url` | string | Presigned URL for the requested `method` (GET, HEAD or PUT). Always present on a 200. |
|
||||
| `size` | int64 | Blob size in bytes. Present on GET and HEAD only, and omitted when the size could not be resolved. |
|
||||
|
||||
`size` lets the AppView build an OCI descriptor without a second round trip: `ProxyBlobStore.Stat` used to fetch a presigned HEAD URL here and then HEAD it against S3 purely to read `Content-Length`. It is resolved from the hold's records index (any layer with an `io.atcr.hold.layer` record) and falls back to a `HeadObject` (image config blobs, and layers whose manifest notification has not landed yet).
|
||||
|
||||
The field is additive. A client that reads only `url` behaves exactly as before, and an AppView that gets no `size` falls back to HEADing the presigned URL. `size` is never sent for a PUT presign: the object does not exist yet.
|
||||
|
||||
On GET and HEAD, a blob that is not in storage is answered with **404** and a JSON error body instead of a presigned URL:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "BlobUnknown",
|
||||
"message": "blob not found"
|
||||
}
|
||||
```
|
||||
|
||||
### Owner/Crew Admin Required
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|
||||
Reference in New Issue
Block a user