mirror of
https://github.com/CloudNebulaProject/refraction-forger.git
synced 2026-04-10 21:30:40 +00:00
127 lines
4.4 KiB
Rust
127 lines
4.4 KiB
Rust
|
|
use std::path::PathBuf;
|
||
|
|
|
||
|
|
use miette::{Context, IntoDiagnostic};
|
||
|
|
use tracing::info;
|
||
|
|
|
||
|
|
/// Push an OCI Image Layout to a registry.
|
||
|
|
pub async fn run(
|
||
|
|
image_dir: &PathBuf,
|
||
|
|
reference: &str,
|
||
|
|
auth_file: Option<&PathBuf>,
|
||
|
|
) -> miette::Result<()> {
|
||
|
|
// Read the OCI Image Layout index.json
|
||
|
|
let index_path = image_dir.join("index.json");
|
||
|
|
let index_content = std::fs::read_to_string(&index_path)
|
||
|
|
.into_diagnostic()
|
||
|
|
.wrap_err_with(|| format!("Failed to read OCI index: {}", index_path.display()))?;
|
||
|
|
|
||
|
|
let index: serde_json::Value =
|
||
|
|
serde_json::from_str(&index_content).into_diagnostic()?;
|
||
|
|
|
||
|
|
let manifests = index["manifests"]
|
||
|
|
.as_array()
|
||
|
|
.ok_or_else(|| miette::miette!("Invalid OCI index: missing manifests array"))?;
|
||
|
|
|
||
|
|
if manifests.is_empty() {
|
||
|
|
return Err(miette::miette!("OCI index contains no manifests"));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Read the manifest
|
||
|
|
let manifest_digest = manifests[0]["digest"]
|
||
|
|
.as_str()
|
||
|
|
.ok_or_else(|| miette::miette!("Invalid manifest entry: missing digest"))?;
|
||
|
|
|
||
|
|
let digest_hex = manifest_digest
|
||
|
|
.strip_prefix("sha256:")
|
||
|
|
.ok_or_else(|| miette::miette!("Unsupported digest algorithm: {manifest_digest}"))?;
|
||
|
|
|
||
|
|
let manifest_path = image_dir.join("blobs/sha256").join(digest_hex);
|
||
|
|
let manifest_json: serde_json::Value = serde_json::from_str(
|
||
|
|
&std::fs::read_to_string(&manifest_path)
|
||
|
|
.into_diagnostic()
|
||
|
|
.wrap_err("Failed to read manifest blob")?,
|
||
|
|
)
|
||
|
|
.into_diagnostic()?;
|
||
|
|
|
||
|
|
// Read config blob
|
||
|
|
let config_digest = manifest_json["config"]["digest"]
|
||
|
|
.as_str()
|
||
|
|
.ok_or_else(|| miette::miette!("Missing config digest in manifest"))?;
|
||
|
|
let config_hex = config_digest.strip_prefix("sha256:").unwrap_or(config_digest);
|
||
|
|
let config_json = std::fs::read(image_dir.join("blobs/sha256").join(config_hex))
|
||
|
|
.into_diagnostic()
|
||
|
|
.wrap_err("Failed to read config blob")?;
|
||
|
|
|
||
|
|
// Read layer blobs
|
||
|
|
let layers_json = manifest_json["layers"]
|
||
|
|
.as_array()
|
||
|
|
.ok_or_else(|| miette::miette!("Missing layers in manifest"))?;
|
||
|
|
|
||
|
|
let mut layers = Vec::new();
|
||
|
|
for layer_desc in layers_json {
|
||
|
|
let layer_digest = layer_desc["digest"]
|
||
|
|
.as_str()
|
||
|
|
.ok_or_else(|| miette::miette!("Missing layer digest"))?;
|
||
|
|
let layer_hex = layer_digest
|
||
|
|
.strip_prefix("sha256:")
|
||
|
|
.unwrap_or(layer_digest);
|
||
|
|
|
||
|
|
let layer_data = std::fs::read(image_dir.join("blobs/sha256").join(layer_hex))
|
||
|
|
.into_diagnostic()
|
||
|
|
.wrap_err_with(|| format!("Failed to read layer blob: {layer_digest}"))?;
|
||
|
|
|
||
|
|
layers.push(forge_oci::tar_layer::LayerBlob {
|
||
|
|
data: layer_data,
|
||
|
|
digest: layer_digest.to_string(),
|
||
|
|
uncompressed_size: 0, // Not tracked in layout
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Determine auth
|
||
|
|
let auth = if let Some(auth_path) = auth_file {
|
||
|
|
let auth_content = std::fs::read_to_string(auth_path)
|
||
|
|
.into_diagnostic()
|
||
|
|
.wrap_err_with(|| format!("Failed to read auth file: {}", auth_path.display()))?;
|
||
|
|
|
||
|
|
let auth_json: serde_json::Value =
|
||
|
|
serde_json::from_str(&auth_content).into_diagnostic()?;
|
||
|
|
|
||
|
|
if let Some(token) = auth_json["token"].as_str() {
|
||
|
|
forge_oci::registry::AuthConfig::Bearer {
|
||
|
|
token: token.to_string(),
|
||
|
|
}
|
||
|
|
} else if let (Some(user), Some(pass)) = (
|
||
|
|
auth_json["username"].as_str(),
|
||
|
|
auth_json["password"].as_str(),
|
||
|
|
) {
|
||
|
|
forge_oci::registry::AuthConfig::Basic {
|
||
|
|
username: user.to_string(),
|
||
|
|
password: pass.to_string(),
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
forge_oci::registry::AuthConfig::Anonymous
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
forge_oci::registry::AuthConfig::Anonymous
|
||
|
|
};
|
||
|
|
|
||
|
|
// Determine if we need insecure registries (localhost)
|
||
|
|
let insecure = if reference.starts_with("localhost") || reference.starts_with("127.0.0.1") {
|
||
|
|
let host_port = reference.split('/').next().unwrap_or("");
|
||
|
|
vec![host_port.to_string()]
|
||
|
|
} else {
|
||
|
|
vec![]
|
||
|
|
};
|
||
|
|
|
||
|
|
info!(reference, "Pushing OCI image to registry");
|
||
|
|
|
||
|
|
let manifest_url =
|
||
|
|
forge_oci::registry::push_image(reference, layers, config_json, &auth, &insecure)
|
||
|
|
.await
|
||
|
|
.map_err(miette::Report::new)
|
||
|
|
.wrap_err("Push failed")?;
|
||
|
|
|
||
|
|
println!("Pushed: {manifest_url}");
|
||
|
|
Ok(())
|
||
|
|
}
|