1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// SPDX-License-Identifier: Apache-2.0
//! `ferro-oci-server`
//!
//! OCI Distribution Spec v1.1 (`opencontainers/distribution-spec`) and
//! Docker Registry HTTP API v2 (`docker/distribution`) for `FerroRepo`.
//!
//! Phase 1 scope (wired in this crate):
//!
//! - `GET /v2/` version check and auth challenge (spec §3.2);
//! - `GET /v2/_catalog` repository catalog with `n`/`last` pagination
//! (spec §3.5);
//! - `GET /v2/{name}/tags/list` tag listing with pagination (spec §3.6);
//! - `GET|HEAD|DELETE /v2/{name}/blobs/{digest}` (spec §3.2 / §4.9);
//! - `POST|PATCH|PUT /v2/{name}/blobs/uploads/{uuid?}` — monolithic
//! and chunked uploads (spec §4.3–§4.8);
//! - `GET|HEAD|PUT|DELETE /v2/{name}/manifests/{reference}` (spec
//! §3.2 / §4.4 / §4.9);
//! - `GET /v2/{name}/referrers/{digest}` referrers API (spec §3.3).
//!
//! The Phase 1 exit gate is 100 % pass on the
//! `opencontainers/distribution-spec` conformance suite and interop
//! with `docker`, `podman`, `crane`, `skopeo`, and `nerdctl`.
//!
//! # Quick start
//!
//! Build an [`AppState`] from a blob store and a metadata plane, hand
//! it to [`router()`], optionally merge in the Kubernetes health probes
//! from [`probe_routes`], and serve it with `axum`:
//!
//! ```no_run
//! use std::sync::Arc;
//!
//! use ferro_blob_store::InMemoryBlobStore;
//! use ferro_oci_server::{AppState, InMemoryRegistryMeta, probe_routes, router};
//!
//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
//! let state = AppState::new(
//! Arc::new(InMemoryBlobStore::new()),
//! Arc::new(InMemoryRegistryMeta::new()),
//! );
//! let app = router(state).merge(probe_routes());
//! let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
//! axum::serve(listener, app).await?;
//! # Ok(())
//! # }
//! ```
//!
//! After that, `docker push localhost:8080/myimage:latest` (or
//! `podman` / `crane` / `skopeo`) works against the running server.
//!
//! # Integration story
//!
//! - **Storage** — blob bytes live behind
//! [`ferro_blob_store::BlobStore`] (use the bundled
//! [`ferro_blob_store::FsBlobStore`] for a filesystem registry or
//! [`ferro_blob_store::InMemoryBlobStore`] for tests). Metadata
//! (manifests, tags, upload sessions, referrers) lives behind the
//! [`RegistryMeta`] trait; [`InMemoryRegistryMeta`] ships as the
//! single-node reference impl, and you can supply a
//! SQLite/Postgres-backed impl of your own.
//! - **Auth** — handlers are open by design. Layer authentication and
//! authorization as `tower` middleware *above* the [`router()`].
//! - **Deployment** — a runnable `ferro-oci-server` binary ships with
//! this crate (see `src/bin/ferro-oci-server.rs`); it reads
//! `FERRO_OCI_LISTEN` and `FERRO_OCI_STORAGE_DIR` from the
//! environment, exposes the `/live`, `/healthz`, and `/ready` probes,
//! and shuts down gracefully on `SIGTERM`/`SIGINT`.
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use MAX_BODY_BYTES;
pub use ;
/// Crate name, exposed for diagnostics and `/metrics` labelling.
pub const CRATE_NAME: &str = "ferro-oci-server";