feat(ecr): Batch 3 — OCI v2 Distribution, real docker push/pull#719
Merged
vieiralucas merged 3 commits intomainfrom Apr 23, 2026
Merged
feat(ecr): Batch 3 — OCI v2 Distribution, real docker push/pull#719vieiralucas merged 3 commits intomainfrom
vieiralucas merged 3 commits intomainfrom
Conversation
Ships the OCI Distribution v2 HTTP surface (`/v2/...`) and the
`GetAuthorizationToken` JSON op so `aws ecr get-login-password |
docker login localhost:4566 && docker push` now round-trips against
fakecloud. This is the differentiator Batch 2 set up — real
content-addressed blob and manifest storage + the HTTP layer Docker
speaks on top of it.
Endpoints:
- GET /v2/ API version probe
- GET /v2/<name>/tags/list list image tags
- HEAD/GET/DELETE /v2/<name>/blobs/<digest> blob access
- POST /v2/<name>/blobs/uploads/ start blob upload
(?digest= for
single-POST)
- PATCH /v2/<name>/blobs/uploads/<uuid> chunked append
- PUT /v2/<name>/blobs/uploads/<uuid>?digest= finalize
- DELETE /v2/<name>/blobs/uploads/<uuid> cancel
- HEAD/GET/PUT/DELETE /v2/<name>/manifests/<ref> manifest access
(ref = tag or
sha256: digest)
Behaviour:
- Basic auth challenge returns 401 with `WWW-Authenticate` + the
`Docker-Distribution-Api-Version: registry/2.0` header Docker
expects. Tokens issued by `GetAuthorizationToken` are accepted
(user = `AWS`); `test*` dev-bypass users also accepted.
- Every response carries `Docker-Distribution-Api-Version` and, where
relevant, `Docker-Content-Digest`, `Docker-Upload-UUID`, `Location`,
and `Range` headers matching the OCI spec.
- PUT manifest computes the sha256 of the uploaded bytes, stores the
image by digest, and (if ref isn't a `sha256:` digest) adds a tag
pointing at it. DELETE by tag removes only the tag when other tags
still reference the digest.
- Uploads integrate with the Batch 2 layer state machine: PATCH
appends, PUT validates `digest` and commits atomically. Retrying
with a bad digest on PUT no longer drops upload state.
- GetAuthorizationToken returns `base64("AWS:<uuid>")` with a 12h
expiry matching AWS's documented shape.
- Dispatcher pre-routes `/v2/*` to ECR before the apigateway
fallback so Docker clients (which don't carry SigV4) land in the
right handler.
Tests:
- 5 E2E tests exercise the full Docker push/pull flow over raw
reqwest (API probe, unauthorized, chunked upload + manifest push +
tag list + SDK crossover, get-authorization-token, tag-name push).
- 1 new conformance test for GetAuthorizationToken; 22/58 ECR ops
now implemented at 100% (690/1789 variants).
- Baseline bumped to 60644 / 61743 (98.2%). No regressions on other
services.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
2 issues found across 11 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="crates/fakecloud-ecr/src/oci.rs">
<violation number="1" location="crates/fakecloud-ecr/src/oci.rs:533">
P2: `blob_upload_cancel` is missing the repository-name ownership check that `blob_upload_patch` and `blob_upload_finish` both enforce. A cancel request scoped to one repository can delete an upload belonging to a different repository.</violation>
</file>
<file name="crates/fakecloud-e2e/tests/ecr_oci.rs">
<violation number="1" location="crates/fakecloud-e2e/tests/ecr_oci.rs:47">
P3: Drop the Authorization header here so this test actually covers the unauthenticated `/v2/` case.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
- blob_upload_cancel now refuses to delete an upload scoped to a different repository. Previously a DELETE targeting `repo-b` could tear down an in-flight upload owned by `repo-a` because the handler only checked the upload id. Matches the ownership check the PATCH and PUT handlers already enforce. E2E test covers the cross-repo case. - Renamed the negative-auth test to describe what it actually verifies (bad credentials -> 401), since it does send an Authorization header.
Missing Authorization on /v2/ endpoints now returns 401 with a WWW-Authenticate header, matching the Docker CLI's two-phase login contract (unauth GET /v2/ -> 401 challenge -> Basic retry). Prior behaviour silently accepted missing credentials as a dev convenience, which broke the challenge flow and also meant the negative tests never actually exercised the unauth path. Adds an E2E test that drops the Authorization header entirely.
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.
Summary
GetAuthorizationTokenso real Docker clients candocker login,docker push, anddocker pullagainstlocalhost:4566— the thing neither LocalStack Pro nor Moto does well.Endpoints
/v2//v2/<name>/tags/list/v2/<name>/blobs/<digest>/v2/<name>/blobs/uploads/?digest=single-POST)/v2/<name>/blobs/uploads/<uuid>/v2/<name>/blobs/uploads/<uuid>?digest=/v2/<name>/blobs/uploads/<uuid>/v2/<name>/manifests/<ref>Behaviour
Basic base64("AWS:<token>")tokens issued byGetAuthorizationTokenare accepted;test*dev-bypass users too. Unauth returns 401 withWWW-Authenticate+Docker-Distribution-Api-Versionheaders.BatchCheckLayerAvailability/DescribeImagessees it, and vice versa./v2/*pre-routes to ECR before the apigateway fallback, since Docker clients don't carry SigV4.Test plan
cargo build --workspacecargo fmt --checkcargo clippy --workspace --all-targets -- -D warningscargo test -p fakecloud-e2e --test ecr_oci— 5 passing (full push/pull round-trip via raw reqwest)cargo test -p fakecloud-conformance --test ecr— 22 passingcargo run -p fakecloud-conformance --release -- run --services ecr— 22/58 ops, 100% passcargo run -p fakecloud-conformance --release -- check— no regressionsFollow-up
Batch 4: lifecycle policy evaluation, scanning, registry ops, pull-through cache, replication, CloudFormation
AWS::ECR::Repository+ Lambda CodeUri image integration, website landing + marketing.Summary by cubic
Adds OCI Distribution v2 endpoints and
GetAuthorizationTokento ECR so real Docker clients can login, push, and pull againstlocalhost:4566. Also fixes unauthenticated/v2/to return a proper 401 challenge and prevents cross-repository upload cancellation.New Features
/v2/*for tags, blobs (HEAD/GET/DELETE, uploads POST/PATCH/PUT/DELETE), and manifests (HEAD/GET/PUT/DELETE).Basicauth viaGetAuthorizationToken(AWS:<token>) and setsDocker-Distribution-Api-Versionheaders.?digest=./v2/*to ECR in the core dispatcher (no SigV4). Adds e2e push/pull and a conformance test; ECR now at 22/58 ops; no regressions.Bug Fixes
WWW-Authenticateon/v2/whenAuthorizationis missing, matching Docker's login flow.DELETE /v2/<name>/blobs/uploads/<uuid>when the upload belongs to a different repository (covered by new e2e test).Written for commit e5dc9f8. Summary will update on new commits.