use std::path::PathBuf;
use super::blob_source::BlobSource;
use super::manager::ImageManifest;
use boxlite_shared::errors::{BoxliteError, BoxliteResult};
#[derive(Clone)]
pub struct ImageObject {
reference: String,
manifest: ImageManifest,
blob_source: BlobSource,
}
impl ImageObject {
pub(super) fn new(reference: String, manifest: ImageManifest, blob_source: BlobSource) -> Self {
Self {
reference,
manifest,
blob_source,
}
}
#[allow(dead_code)]
pub fn reference(&self) -> &str {
&self.reference
}
#[allow(dead_code)]
pub fn layer_digests(&self) -> Vec<&str> {
self.manifest
.layers
.iter()
.map(|l| l.digest.as_str())
.collect()
}
#[allow(dead_code)]
pub fn config_digest(&self) -> &str {
&self.manifest.config_digest
}
#[allow(dead_code)]
pub fn layer_count(&self) -> usize {
self.manifest.layers.len()
}
pub async fn load_config(&self) -> BoxliteResult<oci_spec::image::ImageConfiguration> {
let config_path = self.blob_source.config_path(&self.manifest.config_digest);
let config_json = std::fs::read_to_string(&config_path).map_err(|e| {
BoxliteError::Storage(format!(
"Failed to read config from {}: {}",
config_path.display(),
e
))
})?;
serde_json::from_str(&config_json)
.map_err(|e| BoxliteError::Storage(format!("Failed to parse image config: {}", e)))
}
#[allow(dead_code)]
pub fn layer_tarball(&self, layer_index: usize) -> BoxliteResult<PathBuf> {
let layer = self.manifest.layers.get(layer_index).ok_or_else(|| {
BoxliteError::Storage(format!(
"Layer index {} out of bounds (total layers: {})",
layer_index,
self.manifest.layers.len()
))
})?;
Ok(self.blob_source.layer_tarball_path(&layer.digest))
}
pub fn layer_tarballs(&self) -> Vec<PathBuf> {
self.manifest
.layers
.iter()
.map(|layer| self.blob_source.layer_tarball_path(&layer.digest))
.collect()
}
pub async fn layer_extracted(&self) -> BoxliteResult<Vec<PathBuf>> {
let digests: Vec<String> = self
.manifest
.layers
.iter()
.map(|l| l.digest.clone())
.collect();
let extracted = self.blob_source.extract_layers(&digests).await?;
self.verify_diff_ids()?;
Ok(extracted)
}
fn verify_diff_ids(&self) -> BoxliteResult<()> {
use crate::images::archive::LayerVerifier;
let diff_ids = &self.manifest.diff_ids;
if diff_ids.is_empty() {
return Ok(());
}
let layers = &self.manifest.layers;
if diff_ids.len() != layers.len() {
tracing::warn!(
"DiffID count ({}) doesn't match layer count ({}), skipping verification",
diff_ids.len(),
layers.len()
);
return Ok(());
}
for (i, (layer, diff_id)) in layers.iter().zip(diff_ids.iter()).enumerate() {
let tarball_path = self.blob_source.layer_tarball_path(&layer.digest);
let verifier = match LayerVerifier::new(diff_id) {
Ok(v) => v,
Err(e) => {
tracing::warn!("DiffID parse error for layer {}: {}", i, e);
continue;
}
};
match verifier.verify_tarball(&tarball_path) {
Ok(true) => {
tracing::debug!("DiffID verified for layer {}: {}", i, layer.digest);
}
Ok(false) => {
return Err(BoxliteError::Image(format!(
"DiffID verification failed for layer {} ({}): \
uncompressed content does not match expected diff_id {}",
i, layer.digest, diff_id
)));
}
Err(e) => {
tracing::warn!("DiffID verification error for layer {}: {}", i, e);
}
}
}
Ok(())
}
pub(crate) fn compute_image_digest(&self) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
for layer in &self.manifest.layers {
hasher.update(layer.digest.as_bytes());
}
format!("sha256:{:x}", hasher.finalize())
}
#[allow(dead_code)]
pub fn inspect(&self) -> String {
let mut output = String::new();
output.push_str(&format!("{}\n", self.reference));
output.push_str(&format!("Config: {}\n", self.config_digest()));
output.push_str(&format!("Layers ({}):\n", self.layer_count()));
for (i, layer) in self.manifest.layers.iter().enumerate() {
output.push_str(&format!(" {}. {}\n", i + 1, layer.digest));
}
output
}
}
impl std::fmt::Debug for ImageObject {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ImageObject")
.field("reference", &self.reference)
.field("layers", &self.manifest.layers.len())
.field("config_digest", &self.manifest.config_digest)
.finish()
}
}
impl std::fmt::Display for ImageObject {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} ({} layers)",
self.reference,
self.manifest.layers.len()
)
}
}