mirror of
https://github.com/getnora-io/nora.git
synced 2026-04-12 09:10:32 +00:00
fix: Docker dashboard for namespaced images, library/ auto-prepend for Hub official images (v0.2.32)
Docker dashboard: - build_docker_index now finds manifests segment by position, not fixed index - Correctly indexes library/alpine, grafana/grafana, and other namespaced images Docker proxy: - Auto-prepend library/ for single-segment names when upstream returns 404 - Applies to both manifests and blobs - nginx, alpine, node now work without explicit library/ prefix - Cached under original name for future local hits
This commit is contained in:
@@ -8,7 +8,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.31"
|
||||
version = "0.2.32"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = ["DevITWay <devitway@gmail.com>"]
|
||||
|
||||
@@ -214,6 +214,38 @@ async fn download_blob(
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-prepend library/ for single-segment names (Docker Hub official images)
|
||||
if !name.contains('/') {
|
||||
let library_name = format!("library/{}", name);
|
||||
for upstream in &state.config.docker.upstreams {
|
||||
if let Ok(data) = fetch_blob_from_upstream(
|
||||
&state.http_client,
|
||||
&upstream.url,
|
||||
&library_name,
|
||||
&digest,
|
||||
&state.docker_auth,
|
||||
state.config.docker.proxy_timeout,
|
||||
upstream.auth.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
let storage = state.storage.clone();
|
||||
let key_clone = key.clone();
|
||||
let data_clone = data.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = storage.put(&key_clone, &data_clone).await;
|
||||
});
|
||||
|
||||
return (
|
||||
StatusCode::OK,
|
||||
[(header::CONTENT_TYPE, "application/octet-stream")],
|
||||
Bytes::from(data),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusCode::NOT_FOUND.into_response()
|
||||
}
|
||||
|
||||
@@ -453,6 +485,57 @@ async fn get_manifest(
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-prepend library/ for single-segment names (Docker Hub official images)
|
||||
// e.g., "nginx" -> "library/nginx", "alpine" -> "library/alpine"
|
||||
if !name.contains('/') {
|
||||
let library_name = format!("library/{}", name);
|
||||
for upstream in &state.config.docker.upstreams {
|
||||
if let Ok((data, content_type)) = fetch_manifest_from_upstream(
|
||||
&state.http_client,
|
||||
&upstream.url,
|
||||
&library_name,
|
||||
&reference,
|
||||
&state.docker_auth,
|
||||
state.config.docker.proxy_timeout,
|
||||
upstream.auth.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
state.metrics.record_download("docker");
|
||||
state.metrics.record_cache_miss();
|
||||
state.activity.push(ActivityEntry::new(
|
||||
ActionType::ProxyFetch,
|
||||
format!("{}:{}", name, reference),
|
||||
"docker",
|
||||
"PROXY",
|
||||
));
|
||||
|
||||
use sha2::Digest;
|
||||
let digest = format!("sha256:{:x}", sha2::Sha256::digest(&data));
|
||||
|
||||
// Cache under original name for future local hits
|
||||
let storage = state.storage.clone();
|
||||
let key_clone = key.clone();
|
||||
let data_clone = data.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = storage.put(&key_clone, &data_clone).await;
|
||||
});
|
||||
|
||||
state.repo_index.invalidate("docker");
|
||||
|
||||
return (
|
||||
StatusCode::OK,
|
||||
[
|
||||
(header::CONTENT_TYPE, content_type),
|
||||
(HeaderName::from_static("docker-content-digest"), digest),
|
||||
],
|
||||
Bytes::from(data),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusCode::NOT_FOUND.into_response()
|
||||
}
|
||||
|
||||
|
||||
@@ -173,9 +173,14 @@ async fn build_docker_index(storage: &Storage) -> Vec<RepoInfo> {
|
||||
}
|
||||
|
||||
if let Some(rest) = key.strip_prefix("docker/") {
|
||||
// Support both single-segment and namespaced images:
|
||||
// docker/alpine/manifests/latest.json → name="alpine"
|
||||
// docker/library/alpine/manifests/latest.json → name="library/alpine"
|
||||
let parts: Vec<_> = rest.split('/').collect();
|
||||
if parts.len() >= 3 && parts[1] == "manifests" && key.ends_with(".json") {
|
||||
let name = parts[0].to_string();
|
||||
let manifest_pos = parts.iter().position(|&p| p == "manifests");
|
||||
if let Some(pos) = manifest_pos {
|
||||
if pos >= 1 && key.ends_with(".json") {
|
||||
let name = parts[..pos].join("/");
|
||||
let entry = repos.entry(name).or_insert((0, 0, 0));
|
||||
entry.0 += 1;
|
||||
|
||||
@@ -207,6 +212,7 @@ async fn build_docker_index(storage: &Storage) -> Vec<RepoInfo> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
to_sorted_vec(repos)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user