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

Edited by Hayley Swimelar

Merge request reports

Loading
Loading