Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ after its format id, linked into the server like `crates/formats/nuget`
under `crates/` (no `crates/format-<id>`) or as a module of the server: it
implements `FormatPlugin` with the layout
[crates/formats/README.md](crates/formats/README.md) describes. Docker / OCI
(`crates/format-oci`) is not there yet and is to move to
`crates/formats/docker`, in a pull request of its own; until then it is not
the model for a new format.
is in `crates/formats/docker` but still native: the server's registry serves
it under `/v2`, not through `FormatPlugin`. Until it implements
`FormatPlugin`, it is not the model for a new format.

## Changelog: a fragment per change, never CHANGELOG.md

Expand Down
66 changes: 33 additions & 33 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ members = [
"crates/crypto",
"crates/plugin-api",
"crates/plugin",
"crates/format-oci",
"crates/server",
"crates/e2e",
"crates/formats/docker",
"crates/formats/nuget",
"crates/formats/npm",
"crates/formats/raw",
Expand Down
8 changes: 4 additions & 4 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,10 @@ Every package format is a format plugin, a crate in
[`crates/formats/`](./crates/formats) linked into the server (see
[docs/format-plugins.md](./docs/format-plugins.md)): NuGet, npm and raw. A
new format goes in `crates/formats/<id>` too, never in a `crates/format-<id>`
crate or the server. Docker / OCI (`crates/format-oci`) is not there yet and
is to move to `crates/formats/docker`. A repository of a plugin format is
created with `"format": "<id>"` (hosted, proxy or group; a plugin format has
proxies when it implements the upstream side) and served under
crate or the server. Docker / OCI is in `crates/formats/docker` but still
native, served by the server's registry under `/v2`. A repository of a plugin
format is created with `"format": "<id>"` (hosted, proxy or group; a plugin
format has proxies when it implements the upstream side) and served under
`/repository/<name>/` unless the plugin names another mount (NuGet's is
`/v3`, npm's `/npm`). Helm charts are pushed to docker repositories, see
[docs/docker.md](docs/docker.md#helm-charts).
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,19 @@ A Cargo workspace of small crates plus the web SPA:
| `storage` | `forge-storage` | CAS over S3, blob outbox / GC reconciler. |
| `cache` | `forge-cache` | In-process L1 + optional Redis L2 with cross-instance invalidation (no Postgres advisory locks). |
| `core` | `forge-core` | Domain logic: repo dispatch, auth chain, authz/RBAC, sessions, OIDC, retention. |
| `format-oci` | `forge-oci` | Docker / OCI registry: the distribution-spec `/v2` API, manifests, registry tokens, hosted/proxy/group resolution. |
| `plugin-api` | `forge-plugin-api` | The format-plugin contract: the `FormatPlugin` trait and its types. |
| `plugin` | `forge-plugin` | The host side of format plugins: an installed plugin and its adapter to the repository machinery. |
| `formats/<id>` | `forge-format-*` | Format plugins, built into the server: `nuget`, `npm`, `raw`. |
| `formats/docker` | `forge-format-docker` | Docker / OCI registry, still native: the distribution-spec `/v2` API, manifests, registry tokens, hosted/proxy/group resolution. |
| `server` | `forge-server` | Axum HTTP server: the format plugins' feeds (NuGet at `/v3`, npm at `/npm`, raw files at `/raw`), the file store of files formats, the Docker registry, `/admin` + `/auth` REST, background workers, static SPA serving. |
| `e2e` | `forge-e2e` | End-to-end suite (testcontainers + mock upstream/IdP). |
| `xtask` | — | The task runner (`cargo xtask …`); replaces Make/Just. |
| `web/` | — | TanStack Router SPA (pnpm + Vite), built into `web/dist/`. |

**Every format is a plugin in `crates/formats/`.** A package format is a
crate in `crates/formats/<id>/` implementing `FormatPlugin`, linked into the
server. Docker / OCI (`crates/format-oci`) is not there yet and is to move to
`crates/formats/docker`.
server. Docker / OCI is in `crates/formats/docker` but still native: the
server's registry serves it under `/v2`, not through `FormatPlugin`.

**Single-origin serving.** `forge-server` mounts each format plugin's feed
(NuGet at `/v3`, npm at `/npm`, others at `/repository`), the Docker / OCI registry (`/v2`),
Expand Down
4 changes: 4 additions & 0 deletions crates/formats/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ the design, the contract and how to write one.
| [`npm`](npm) | `npm` | npm registries at `/npm` for npm, pnpm, Yarn and Bun: publish, install, dist-tags, deprecations, unpublishing, search, audits and logins; hosted, proxy and group repositories ([docs/npm.md](../../docs/npm.md)) |
| [`raw`](raw) | `raw` | Raw files at `/raw`: files under paths, uploads with `PUT` or a form, ranges, checksum sidecars, listings; hosted, proxy and group repositories ([docs/raw.md](../../docs/raw.md)) |

[`docker`](docker) holds Docker / OCI (`forge-format-docker`), which is still
native: the server's registry serves it under `/v2`, not through
`FormatPlugin` ([docs/docker.md](../../docs/docker.md)).

A plugin directory holds:

- `Cargo.toml`: a member of the workspace; its `version` is the plugin's;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
[package]
name = "forge-oci"
name = "forge-format-docker"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish.workspace = true

[dependencies]
forge-core = { path = "../core" }
forge-db = { path = "../db" }
forge-storage = { path = "../storage" }
forge-cache = { path = "../cache" }
forge-crypto = { path = "../crypto" }
forge-core = { path = "../../core" }
forge-db = { path = "../../db" }
forge-storage = { path = "../../storage" }
forge-cache = { path = "../../cache" }
forge-crypto = { path = "../../crypto" }

axum = { workspace = true }
tokio = { workspace = true, features = ["rt", "sync"] }
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 2 additions & 1 deletion crates/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ forge-cache = { path = "../cache" }
forge-core = { path = "../core" }
forge-crypto = { path = "../crypto" }
forge-plugin = { path = "../plugin" }
forge-oci = { path = "../format-oci" }
# Docker / OCI, still native: the registry under /v2 (crate::registry).
forge-format-docker = { path = "../formats/docker" }
# The built-in format plugins (crates/formats/nuget, crates/formats/npm,
# crates/formats/raw), linked in.
forge-format-nuget = { path = "../formats/nuget" }
Expand Down
4 changes: 2 additions & 2 deletions crates/server/src/admin/proxy_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ pub async fn invalidate(
}
}
Some(p) if docker => {
if !forge_oci::reference::valid_name(&p) {
if !forge_format_docker::reference::valid_name(&p) {
return bad_request("package is not a valid image name");
}
Some(forge_oci::proxied_image(&repo, &p))
Some(forge_format_docker::proxied_image(&repo, &p))
}
Some(p) => {
let format = match state.formats.for_repository(&repo) {
Expand Down
2 changes: 1 addition & 1 deletion crates/server/src/admin/repositories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,7 @@ async fn docker_options(
return writable_member_option(state, writable, kind, options.writable_member, config)
.await;
}
if !forge_oci::reference::valid_name_component(name) {
if !forge_format_docker::reference::valid_name_component(name) {
return Err(bad_request(format!(
"invalid docker repository name {name:?}: image names start with it, so use \
lowercase letters and digits, separated by '.', '_', '__' or '-'"
Expand Down
12 changes: 6 additions & 6 deletions crates/server/src/browse_images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ use chrono::{DateTime, Utc};
use forge_core::auth::principal::Principal;
use forge_db::model::{RepoKind, Repository};
use forge_db::{oci_dao, repository_dao};
use forge_oci::chart;
use forge_oci::manifest::{self, ManifestKind, MAX_MANIFEST_BYTES};
use forge_format_docker::chart;
use forge_format_docker::manifest::{self, ManifestKind, MAX_MANIFEST_BYTES};
use forge_storage::BlobRef;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -119,7 +119,7 @@ pub struct ImageDetail {

/// The registry host of the server (`host[:port]` of `public_base_url`).
fn registry_host(state: &AppState) -> String {
forge_oci::registry_origin(&state.config.public_base_url()).1
forge_format_docker::registry_origin(&state.config.public_base_url()).1
}

/// Where `repo`'s images are pulled from: its own host, or
Expand Down Expand Up @@ -266,7 +266,7 @@ pub async fn image_detail(
Ok(repo) => repo,
Err(resp) => return resp,
};
if !forge_oci::reference::valid_name(&image) {
if !forge_format_docker::reference::valid_name(&image) {
return (StatusCode::NOT_FOUND, "image not found").into_response();
}
let sources = match sources(&state, &principal, &repo).await {
Expand Down Expand Up @@ -521,8 +521,8 @@ pub async fn chart_detail(
};
let not_found = || (StatusCode::NOT_FOUND, "chart not found").into_response();
let image = query.image;
if !forge_oci::reference::valid_name(&image)
|| forge_oci::digest::Digest::parse(&query.digest).is_err()
if !forge_format_docker::reference::valid_name(&image)
|| forge_format_docker::digest::Digest::parse(&query.digest).is_err()
{
return not_found();
}
Expand Down
8 changes: 4 additions & 4 deletions crates/server/src/nexus_import/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
//! (`action = "import"`). A version Forge holds with the same SHA-256
//! is skipped without a download;
//! * a Docker tag is copied through the Nexus repository's registry API
//! ([`forge_oci::Registry::import_tag`]): digests are kept,
//! ([`forge_format_docker::Registry::import_tag`]): digests are kept,
//! multi-platform indexes arrive with every child manifest, and a
//! referrer is copied once its subject is in Forge;
//! * what Nexus recorded is kept: the publish time (`blobCreated`, for a
Expand Down Expand Up @@ -63,7 +63,7 @@ use forge_crypto::PlaintextSecret;
use forge_db::import_dao::{self, ImportItem, NewItem, NexusImport};
use forge_db::model::{Repository, DOCKER_FORMAT};
use forge_db::{repository_dao, secret_dao, user_dao};
use forge_oci::import::{ImageImportOptions, ImageOutcome, ImportSource};
use forge_format_docker::import::{ImageImportOptions, ImageOutcome, ImportSource};
use forge_storage::StagedBlob;
use futures_util::{FutureExt, StreamExt, TryStreamExt};
use serde_json::Value as JsonValue;
Expand Down Expand Up @@ -210,7 +210,7 @@ enum Target {
/// Docker, through the registry, from the Nexus repository's registry
/// API.
Images {
registry: forge_oci::Registry,
registry: forge_format_docker::Registry,
source: ImportSource,
},
}
Expand Down Expand Up @@ -705,7 +705,7 @@ impl Run<'_> {
async fn copy_image(
&self,
ctx: &Ctx,
registry: &forge_oci::Registry,
registry: &forge_format_docker::Registry,
from: &ImportSource,
kind: &str,
source: &SourceItem,
Expand Down
8 changes: 4 additions & 4 deletions crates/server/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use forge_crypto::service::EnvelopeService;
use forge_crypto::{ExposeSecret, PlaintextSecret};
use forge_db::model::Repository;
use forge_db::{authz_dao, secret_dao, workload_api_key_dao};
use forge_oci::token::TokenKeys;
use forge_format_docker::token::TokenKeys;
use sha2::Digest;
use sqlx::PgPool;

Expand All @@ -36,7 +36,7 @@ pub async fn registry(State(state): State<AppState>, req: Request<Body>) -> Resp
/// Workload pushes go to the trusted-publishing ledger.
pub(crate) struct LedgerRecorder;

impl forge_oci::PublishRecorder for LedgerRecorder {
impl forge_format_docker::PublishRecorder for LedgerRecorder {
fn for_repository(&self, repository: &Repository) -> Arc<dyn PublishPolicy> {
crate::dispatch::workload_ledger_policy(repository)
}
Expand All @@ -50,7 +50,7 @@ pub(crate) struct WorkloadLiveness {
}

#[async_trait::async_trait]
impl forge_oci::WorkloadCheck for WorkloadLiveness {
impl forge_format_docker::WorkloadCheck for WorkloadLiveness {
async fn still_valid(&self, principal: &Principal) -> Result<bool, String> {
let Principal::Workload {
realm_id,
Expand Down Expand Up @@ -91,7 +91,7 @@ pub(crate) fn amend_registry_rejection(public_base_url: &str, response: Response
{
return response;
}
let (scheme, service) = forge_oci::registry_origin(public_base_url);
let (scheme, service) = forge_format_docker::registry_origin(public_base_url);
let challenge = format!("Bearer realm=\"{scheme}://{service}/v2/token\",service=\"{service}\"");
let body = serde_json::json!({
"errors": [{ "code": "UNAUTHORIZED", "message": "invalid credentials" }]
Expand Down
12 changes: 6 additions & 6 deletions crates/server/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub struct AppState {
/// install's shared one ([`crate::registry::load_token_keys`] via
/// [`AppState::with_registry_keys`]) so every instance accepts every
/// instance's tokens.
pub registry_keys: forge_oci::token::TokenKeys,
pub registry_keys: forge_format_docker::token::TokenKeys,
/// This process among the instances of an install, as per-instance
/// reports (the local blob cache's statistics) name it.
pub instance: Arc<Instance>,
Expand Down Expand Up @@ -371,7 +371,7 @@ impl AppState {
oidc_login_starts,
envelope: None,
upstream,
registry_keys: forge_oci::token::TokenKeys::new(vec![{
registry_keys: forge_format_docker::token::TokenKeys::new(vec![{
use rand::Rng;
let mut key = vec![0u8; 32];
rand::rng().fill_bytes(&mut key);
Expand All @@ -389,22 +389,22 @@ impl AppState {
}

/// Use `keys` to sign and verify registry tokens.
pub fn with_registry_keys(mut self, keys: forge_oci::token::TokenKeys) -> Self {
pub fn with_registry_keys(mut self, keys: forge_format_docker::token::TokenKeys) -> Self {
self.registry_keys = keys;
self
}

/// The Docker / OCI registry service, wired to this state.
pub fn registry(&self) -> forge_oci::Registry {
forge_oci::Registry::new(forge_oci::RegistryParts {
pub fn registry(&self) -> forge_format_docker::Registry {
forge_format_docker::Registry::new(forge_format_docker::RegistryParts {
pool: self.pool.clone(),
cas: self.cas.clone(),
store: self.blob_store.clone(),
cache: self.cache.clone(),
authz: self.authz.clone(),
http: self.upstream.clone(),
envelope: self.envelope.clone(),
config: forge_oci::RegistryConfig {
config: forge_format_docker::RegistryConfig {
public_base_url: self.config.public_base_url(),
max_blob_bytes: self.config.docker_max_blob_bytes(),
default_max_upload_bytes: self.config.default_upload_limit(DOCKER_FORMAT),
Expand Down
2 changes: 1 addition & 1 deletion crates/server/src/workers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ fn spawn_registry_sweeper(
ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
ticker.tick().await;
match forge_oci::gc::sweep_once(
match forge_format_docker::gc::sweep_once(
&pool,
chrono::Duration::seconds(docker.upload_ttl_seconds as i64),
chrono::Duration::seconds(docker.unreferenced_blob_ttl_seconds as i64),
Expand Down
Loading
Loading