refraction-forger/crates/forger/src/commands/push.rs

127 lines
4.4 KiB
Rust
Raw Normal View History

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(())
}