Repository navigation
Make Docker / OCI a format plugin over a host image store - #462
Merged
Merged
Conversation
Docker / OCI implements FormatPlugin with storage Images, split as raw was: crates/formats/docker owns the distribution-spec protocol (paths, grammar, manifests, error answers, charts, response headers, upstream URLs), and the image store, the token service, authorization, GC and the Nexus import move to the server as crates/server/src/images. The plugin contract gains images formats: Storage::Images and IMAGES_MOUNT, image_path to split a request's name and endpoint, image queries (manifests, blobs, tags, referrers, upload sessions, manifest puts and deletes) answered as results, the ServeBlob and ServeManifest actions, image_error, parse_manifest, config_summary and artifact_files, and upstream operations whose fetches carry an Accept header and a token scope, the host exchanging an upstream's Bearer tokens. Host checks ask for images storage instead of the docker id: DOCKER_FORMAT and Repository::is_docker are gone. GET /admin/formats lists Docker from its descriptor. docs/format-plugins.md gets an Images formats section in place of "Why Docker is not a plugin". Fixes #447 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CxwRK8xYPsMDBiMkySxcHf
…aude/447-docker-plugin # Conflicts: # crates/formats/docker/src/push.rs # crates/formats/docker/src/service.rs # crates/server/src/images/import.rs
…aude/447-docker-plugin # Conflicts: # DEVELOPMENT.md # README.md # crates/core/tests/proxy_read.rs # crates/formats/README.md # crates/formats/nuget/src/upstream.rs # crates/formats/nuget/tests/upstream.rs # crates/plugin-api/src/types.rs # crates/server/Cargo.toml # crates/server/src/admin/repositories.rs # crates/server/src/plugins.rs # crates/server/src/state.rs # docs/format-plugins.md
# Conflicts: # crates/server/src/state.rs # docs/format-plugins.md
schmitch
enabled auto-merge
October 24, 2026 15:48
Member
Author
|
That's a race in the test harness, not in this PR's code, and it can hit any PR. #469 fixes it on its own, per Generated by Claude Code |
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.
Fixes #447
Part of #442. #456 (#443), #458 (#444) and #461 (#445) have merged, and this PR targets
main.mainis merged in, npm (#463 and #465) included.What changes
Docker / OCI is a
FormatPluginwithstorage: Images, split the way raw was in #431: the format crate speaks the protocol, and the host keeps the store.The contract (
forge-plugin-api, newimagesmodule, images formats only):Storage::ImagesandIMAGES_MOUNT(/v2). The descriptor checks require an images format to mount at/v2and allow only one images format per server.dockeris no longer a reserved id: Docker is a built-in plugin.image_path(method, path): the format splits the path below/v2into the name and the endpoint, and says what the request needs (read, write, delete). The host resolves and authorizes with that, then callshandlewith the endpoint as the path and the resolved image asRequest::image(next to npm'sRequest::body, which an images format never gets).Image queries:
Manifest,Blob,Tags,Referrers;StartUpload(with a cross-repository mount),UploadStatus,AppendUpload,FinishUpload,CancelUpload,PutManifest,DeleteManifest,DeleteBlob.Every answer is a
Result<_, ImageError>.Actions
ServeBlobandServeManifest.image_error: the format renders refusals and failures (typedImageError) as OCI errors. The host adds its Bearer challenge.parse_manifest,config_summary(an image's platform, a chart's metadata, merged into the summary) andartifact_files(a chart's README and values, over a blob the host lends).Upstream operations:
ImageName,ImageManifest,ImageBlob,ImageTagsandImageReferrers.Fetchgains a tokenscope, next to theacceptnpm added;Fetch::newandacceptingbuild them.FetchResultgains thestatusandlinkheaders.testing::converse_answeredruns such conversations against canned answers.The format (
crates/formats/docker):DESCRIPTOR(docker, mount/v2, all three kinds,Storage::Images), plus:protocol.rs: every endpoint'shandleandrender, with the response headers;error.rs: OCI error codes, bodies and statuses;upstream.rs: upstream URLs, Docker Hub'slibrary/names, tag pages throughLink, referrers.It keeps the path, name, reference, digest, manifest and chart code it already held. It implements
reserved_prefix, so a group's reserved prefixes are image-name prefixes (acme/), and itsupstream_tokenisNone: the image store answers an upstream's Bearer challenge itself. It now depends onforge-plugin-api(plusserde,urlandflate2) and nothing of the host.The image store (
crates/server/src/images, moved from the format crate) holds:dispatch.rsanswers/v2/,/v2/tokenand/v2/_catalogitself. It runs every other request as a conversation with the format, and keeps only an allowlist of the response headers the format sets (locationandlinkonly under/v2/).Host checks by storage, not by id.
DOCKER_FORMATandRepository::is_docker()are gone; checks askFormatRegistry::stores_imagesorAppState::storage. This covers:reserved_prefix, Docker's included, sonormalize_prefixeslost its Docker flag. The Docker options (host, immutable tags, a group's writable member) apply to a repository whose format stores images, and the writable member must be a hosted repository of that format, as npm's groups now have;Config::default_upload_limit(Storage)).GET /admin/formatslists Docker from its descriptor (storage: images, now with its version and description). The registry's routes come from the images plugin's mount.oci_dao::find_repository_by_hosttakes the format id. The platform-only summary queries gave way toconfig_media_typeandcomplete_config_summary;.sqlxis regenerated.The only
"docker"left in host code outside tests is the Nexus importer's list of formats it copies (nexus_import::FORMATS). It is Nexus's vocabulary: it is matched against the Nexus source repository's format. Which store an import writes to is decided by storage.npm. npm's upstream answers the image operations with a failure, as NuGet's does. The plugin dispatcher (
/npm,/v3,/raw) refuses image queries and actions as a plugin's misuse, and the registry refuses npm's new actions (change,read body,auth,forward) the same way.Docs.
docs/format-plugins.mdhas an Images formats section in place of Why Docker is not a plugin.docs/docker.md,AGENTS.md,README.md,DEVELOPMENT.mdandcrates/formats/README.mdno longer call Docker native. Changelog:docs/changes/2.0.0/447.feature.md.Notes for review
crates/server/tests/registry.rschanges in three lines, and its assertions don't change:token::MAX_SCOPESandgc::sweep_oncemoved with the image store, so they areforge_server::images::{token, gc}now.tests/admin.rsgets the same path change fortoken.RetentionWorkerwith NuGet as the format of every repository. The worker used to recognize Docker by its id; it now asks the repository's format whether it stores images, as it already did for files formats. So the test gets the server's resolver (RegistryResolver), the oneworkers.rsuses.browse::list_reposstill names no client URL for an images repository. Its images have their own pull names (/browse/images), so it doesn't show/v2/<repo>./browse/chartdoes it withparse_manifest(a manifest that names files is a chart),config_summary(metadata under the summary'schartkey) andartifact_files.Testing
On the head with
mainmerged, npm (#463) included:cargo fmt --check,cargo clippy --workspace --all-targets -- -D warnings,cargo xtask changelog --check, andcargo sqlx prepare --check --workspaceagainst a database migrated to 0047 all pass..github/scripts/test-parallel.sh --workspace: all 108 test binaries pass,npm.rsincluded..github/scripts/oci-e2e.shpasses with CI's pinned crane v0.20.2, cosign v3.0.2 and Helm v4.3.1, aregistry:2upstream and BuildKit. It covers:main(anonymous mount is skipped there too);ls,manifestandcopy, with mounted blobs;template, through the proxy and the group;.github/scripts/npm-e2e.shpasses locally with Node 22 and pnpm 10.28, not CI's Node 26 and pnpm 11.5. It covers web and legacy logins, publish, install, dist-tags, deprecation, unpublish, the group's scope guard and audits.browse_repos_by_format_with_client_urls: Docker repositories got a/v2/<repo>URL;multi_arch_index_and_referrers: a rendered response carried the host'sContent-Typeahead of the format's;npm.rs(13) pass again.dockerandimage-smokejobs. They run in CI now that this targetsmain.🤖 Generated with Claude Code
https://claude.ai/code/session_01CxwRK8xYPsMDBiMkySxcHf