feat(managementapi): serve container remote image and manifest reads (S17 Phase 6 plan: 19/42)
Why
The container image and manifest read routes answered 404 on a kind=remote
repository. The kind-dispatch scaffold in chore(managementapi): artifact-read kind-dispat... (!1582 - merged) • Hayley Swimelar • 19.4 left every
remote arm on that pending 404, and the cache read stores landed in
chore(datastore): add container remote image an... (!1629 - merged) • Hayley Swimelar • 19.4 with no consumer, so a remote repository's cached images and
manifests were unreachable through the management API even though the rows and
the queries to read them both existed.
This fills the image and manifest arms. Six routes stay on the pending 404: the tag list and tag detail, which step 20 closes; the untag, which step 35 closes; and the three bulk deletes, which step 33 closes.
What
include_referrers is accepted and inert on remote. No remote manifest row
stores a subject_digest, so no cached row is a referrer under the Phase 3 list
rule and the default list is already the full cached inventory. Both accepted
values return identical rows, and any other value still returns 400. This is
a property of the table, not a handler choice.
A remote manifest's size never covers layer bytes. The writer stores
int64(payload.Len()), so the column holds the manifest's own payload length
for an image manifest and a manifest list alike, and container_remote_blobs
carries no size column at all. The contract said the whole-tree reading until
review caught it, which would have had clients sizing cache storage from a
number orders of magnitude too small.
The corrected reading leaves ADR-007 the last document that disagrees.
docs/adr/007_database_schema.md line 655 still calls the column a subtree
total that converges as children arrive, in the present tense and with no
deferral. docs/specs/S16-container-remote.md line 183 gives this contract's
reading and defers the convergence to the relationship-population work in
#264, so
the mechanism is planned rather than built. The ADR amendment is owed to the
handbook and is not in this MR.
Images and manifests carry no cache key. ADR-009 line 173 puts freshness
tracking on tags, package files, and metadata files, and excludes
digest-addressed rows. The resource tests assert the exact field set so the key
cannot appear by accident.
Three operations move from 404 to 200 on kind=remote.
docs/dev/api-style.md line 134 classifies any status-code change as breaking.
No client can observe this one: create.go line 214 rejects a non-hosted kind
with 422 and forces hosted, and RepositoryStore.Update never assigns kind,
so no reachable repository holds that kind today.
The page-size clamp is the shared one. The remote arms route through
parseLimitParam, so a request above the server maximum is cut to it rather
than rejected, and only a non-numeric, fractional, zero, or negative limit
returns 400.
Step 20 is open against main as feat(managementapi): serve the container remote... (!1748 - merged) • Hayley Swimelar • 19.4.
The two share 11 files, so whichever merges second rebases. Step 20 carries the
tag routes' kind dispatch on its own containerRemoteTagReaders struct rather
than on this MR's containerReaders. Both declare ContainerRemoteImageReader,
in different files, so git merges the pair with no marker and the second
pipeline fails to compile until one is dropped.
Test plan
go test ./internal/managementapi/... ./cmd/artifact-registry/...
ARTIFACT_REGISTRY_DATABASE_TEST_DSN=<dsn> go test -tags=integration -count=1 ./internal/managementapi/...
golangci-lint run --build-tags=integration ./internal/managementapi/...The nil-image-id legs are mutation-verified: removing parsePathID's
id == uuid.Nil rejection turns both into 500s. The route-list totality test
is too, by injecting an unmirrored sixth route.
Spec coverage
| Acceptance criterion | Tests |
|---|---|
| #95 cached images and manifests serve with Phase 3 sorts, pagination, and error rules | TestContainerRemoteReadsIntegration_ImageKeysetWalk, _ManifestWalkAndNulls, _ImageDetailIsRepositoryScoped, TestContainerRoutes_RemoteRepositoryServesCachedRoutes |
#96 (closed) images and manifests omit the cache key |
TestContainerRemoteImageDetail_Returns200, TestContainerRemoteManifestList_ResourceFields (exact field set) |
#97 (closed) artifact_type and subject_digest serialize null, both include_referrers values return identical rows, and any other value 400s |
TestContainerRemoteManifestList_IncludeReferrersIsInert, _ResourceFields, _InvalidParams_Return400, TestContainerRemoteReadsIntegration_IncludeReferrersIsInert |
#99 (closed) every artifact route on a virtual repository returns 404 |
TestContainerRoutes_VirtualRepositoryIs404 |
| #100 (closed) a soft-deleted row is absent from every list and detail read | TestContainerRemoteImageList_EvictedRowIsAbsent, TestContainerRemoteManifestList_EvictedRowIsAbsent, _EvictedParentImageIsNotFound, TestContainerRemoteReadsIntegration_EvictedRowsAreAbsent |
| Page-size clamp at the server maximum | TestContainerRemoteImageList_ClampedPageIsCutToTheMaximum, TestContainerRemoteImageList_LimitBounds, TestContainerRemoteManifestList_LimitBounds |
| Tag routes stay pending | TestContainerRoutes_RemoteTagRoutesArePending404, TestContainerRemoteReadsIntegration_TagRoutesAre404 |
Diff size
2990 reviewable LOC, past the 500 the development model asks be justified.
Insertions split: tests 2468, production 493, contract 29. Wiring in cmd/ is
109 of those, 83 test and 26 production.
Splitting does not help. The production surface is one dispatch seam
(dispatchContainerRepositoryKind plus resolveContainerRepositoryScope) that
both route families consume, so an images-only MR lands the scope resolver with
no caller and a manifests-only MR lands without the parent-image finder its
eviction gate reads. Tests are 83% of the insertions because each new suite is
the hosted twin of the arm it mirrors, and the dropped-subtest rule wants the
twin to land with it.
e2e scenarios
No scenario is added or affected. The catalogs in docs/testing/ are
protocol-client driven while this phase's journeys are management-API driven,
and the monolith slices that consume these endpoints own those catalog
additions, the precedent the merged plan records for phase 4.
Related to #314