src/federation/nexus.rs
//! **Sonatype Nexus** harbour.
//!
//! Serves hosted/proxy content at `{base}/repository/{repo}/{layout-path}`, where
//! the layout path is the per-ecosystem mapping in [`upstream_path`]. Basic auth
//! (the Nexus norm) via the shared [`UpstreamAuth`].
#[cfg(feature = "federation")]
use anyhow::Result;
use super::{ArtifactCoord, RegistryFormat, UpstreamAuth, upstream_path};
#[cfg(feature = "federation")]
use super::{ArtifactMeta, UpstreamRegistry, verify_sha256};
#[cfg(feature = "federation")]
use super::{get_bytes, head_meta};
/// A read-only harbour proxying one Nexus hosted/proxy repository.
#[cfg_attr(not(feature = "federation"), allow(dead_code))]
pub struct NexusRegistry {
name: String,
/// Nexus base URL, e.g. `https://nexus.corp`.
base_url: String,
/// The hosted/proxy repository name, e.g. `maven-central` or `crates-proxy`.
repo: String,
auth: UpstreamAuth,
format: RegistryFormat,
}
impl NexusRegistry {
pub fn new(
name: impl Into<String>,
base_url: impl Into<String>,
repo: impl Into<String>,
auth: UpstreamAuth,
format: RegistryFormat,
) -> Self {
Self {
name: name.into(),
base_url: base_url.into().trim_end_matches('/').to_string(),
repo: repo.into(),
auth,
format,
}
}
/// Content URL for one artifact: `{base}/repository/{repo}/{layout-path}`.
pub fn content_url(&self, id: &ArtifactCoord) -> String {
format!(
"{}/repository/{}/{}",
self.base_url,
self.repo,
upstream_path(self.format, id)
)
}
}
#[cfg(feature = "federation")]
impl UpstreamRegistry for NexusRegistry {
fn name(&self) -> &str {
&self.name
}
fn base_url(&self) -> &str {
&self.base_url
}
fn auth(&self) -> &UpstreamAuth {
&self.auth
}
fn format(&self) -> RegistryFormat {
self.format
}
fn fetch(&self, id: &ArtifactCoord) -> Result<Option<Vec<u8>>> {
let (bytes, sha256) = match get_bytes(&self.base_url, &self.content_url(id), &self.auth)? {
Some(b) => b,
None => return Ok(None),
};
if let Some(expected) = sha256 {
verify_sha256(&bytes, &expected)?;
}
Ok(Some(bytes))
}
fn metadata(&self, id: &ArtifactCoord) -> Result<Option<ArtifactMeta>> {
head_meta(&self.base_url, &self.content_url(id), &self.auth)
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "federation")]
use super::super::testutil::MockServer;
use super::*;
fn id(name: &str, version: &str) -> ArtifactCoord {
ArtifactCoord::new(name, version)
}
#[test]
fn content_url_uses_nexus_repository_layout() {
let up = NexusRegistry::new(
"nx",
"https://nexus.corp/",
"crates-proxy",
UpstreamAuth::None,
RegistryFormat::Rust,
);
assert_eq!(
up.content_url(&id("serde", "1.0.0")),
"https://nexus.corp/repository/crates-proxy/serde/serde-1.0.0.crate"
);
}
#[test]
#[cfg(feature = "federation")]
fn miss_returns_none_and_hit_returns_bytes_at_nexus_layout() {
let server = MockServer::start(|method, path| {
assert_eq!(method, "GET");
if path == "/repository/crates-proxy/rand/rand-0.8.5.crate" {
(200, b"rand-crate-bytes".to_vec())
} else {
(404, b"nope".to_vec())
}
});
let up = NexusRegistry::new(
"nx",
&server.base_url,
"crates-proxy",
UpstreamAuth::None,
RegistryFormat::Rust,
);
assert_eq!(
up.fetch(&id("rand", "0.8.5")).unwrap().as_deref(),
Some(b"rand-crate-bytes".as_ref())
);
assert!(up.fetch(&id("ghost", "9.9.9")).unwrap().is_none());
assert_eq!(
server.requests()[0].path,
"/repository/crates-proxy/rand/rand-0.8.5.crate"
);
}
#[test]
#[cfg(feature = "federation")]
fn basic_auth_header_is_sent() {
let server = MockServer::start(|_m, _p| (200, b"bytes".to_vec()));
let up = NexusRegistry::new(
"nx",
&server.base_url,
"repo",
UpstreamAuth::Basic {
username: "admin".into(),
password: "s3cret".into(),
},
RegistryFormat::Rust,
);
up.fetch(&id("serde", "1.0.0")).unwrap();
// base64("admin:s3cret") == YWRtaW46czNjcmV0
assert_eq!(
server.requests()[0].header("authorization"),
Some("Basic YWRtaW46czNjcmV0")
);
crate::functional_status(
"skidbladnir/federation-nexus",
"basic_auth_attached",
server.requests()[0].header("authorization") == Some("Basic YWRtaW46czNjcmV0"),
"Nexus Basic auth attached",
);
}
/// The ship's primary voyage: mirror a coordinate set, sink each fetched
/// artifact, skip the 404s, and report the mirrored count.
#[test]
#[cfg(feature = "federation")]
fn mirror_fetches_each_coord_and_skips_missing() {
let server = MockServer::start(|_m, path| {
if path.ends_with("rand-0.8.5.crate") {
(200, b"rand".to_vec())
} else if path.ends_with("serde-1.0.0.crate") {
(200, b"serde".to_vec())
} else {
(404, b"no".to_vec())
}
});
let up = NexusRegistry::new(
"nx",
&server.base_url,
"crates-proxy",
UpstreamAuth::None,
RegistryFormat::Rust,
);
let coords = vec![
id("rand", "0.8.5"),
id("serde", "1.0.0"),
id("ghost", "0.0.0"),
];
let mut captured: Vec<(String, Vec<u8>)> = Vec::new();
let n = up
.mirror(&coords, &mut |c, bytes| {
captured.push((c.name.clone(), bytes));
Ok(())
})
.unwrap();
assert_eq!(n, 2, "two present, one missing");
assert_eq!(captured.len(), 2);
assert_eq!(captured[0], ("rand".to_string(), b"rand".to_vec()));
crate::functional_status(
"skidbladnir/federation-nexus",
"mirror_voyage",
n == 2 && captured.len() == 2,
"mirror fetched 2 of 3 coords (skipped the 404)",
);
}
}