use base64::{Engine, engine::general_purpose::STANDARD};
use cap_std::fs::Dir;
use oci_spec::image::{ImageConfiguration, ImageManifest};
use std::io::Read;
use crate::error::{Result, StorageError};
use crate::storage::Storage;
const MANIFEST_FILENAME: &str = "manifest";
#[derive(Debug)]
pub struct Image {
id: String,
image_dir: Dir,
}
impl Image {
pub fn open(storage: &Storage, id: &str) -> Result<Self> {
let id = id.strip_prefix("sha256:").unwrap_or(id);
let images_dir = storage.root_dir().open_dir("overlay-images")?;
let image_dir = images_dir
.open_dir(id)
.map_err(|_| StorageError::ImageNotFound(id.to_string()))?;
Ok(Self {
id: id.to_string(),
image_dir,
})
}
pub fn id(&self) -> &str {
&self.id
}
pub fn read_manifest_raw(&self) -> Result<Vec<u8>> {
let mut file = self.image_dir.open(MANIFEST_FILENAME)?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
Ok(data)
}
pub fn manifest(&self) -> Result<ImageManifest> {
let file = self.image_dir.open(MANIFEST_FILENAME)?;
serde_json::from_reader(file)
.map_err(|e| StorageError::InvalidStorage(format!("Invalid manifest JSON: {}", e)))
}
pub fn config(&self) -> Result<ImageConfiguration> {
let key = format!("sha256:{}", self.id);
let encoded_key = STANDARD.encode(key.as_bytes());
let config_data = self.read_metadata(&encoded_key).map_err(|e| {
StorageError::Io(std::io::Error::other(format!(
"reading config metadata ={} for image {}: {}",
encoded_key, self.id, e
)))
})?;
serde_json::from_slice(&config_data)
.map_err(|e| StorageError::InvalidStorage(format!("Invalid config JSON: {}", e)))
}
pub fn layers(&self) -> Result<Vec<String>> {
let config = self.config()?;
let diff_ids: Vec<String> = config
.rootfs()
.diff_ids()
.iter()
.map(|digest| {
let diff_id = digest.to_string();
diff_id
.strip_prefix("sha256:")
.unwrap_or(&diff_id)
.to_string()
})
.collect();
Ok(diff_ids)
}
pub fn storage_layer_ids(&self, stores: &[Storage]) -> Result<Vec<String>> {
let diff_ids = self.layers()?;
let mut resolved: Vec<Option<String>> = vec![None; diff_ids.len()];
for store in stores {
if resolved.iter().all(|r| r.is_some()) {
break;
}
if let Ok(found) = store.resolve_diff_ids(&diff_ids) {
for (i, id) in found.into_iter().enumerate() {
if resolved[i].is_none() {
resolved[i] = id;
}
}
}
}
resolved
.into_iter()
.enumerate()
.map(|(i, opt)| opt.ok_or_else(|| StorageError::LayerNotFound(diff_ids[i].clone())))
.collect()
}
pub fn read_metadata(&self, key: &str) -> Result<Vec<u8>> {
let filename = format!("={}", key);
let mut file = self.image_dir.open(&filename)?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
Ok(data)
}
pub fn image_dir(&self) -> &Dir {
&self.image_dir
}
pub fn names(&self, storage: &Storage) -> Result<Vec<String>> {
let images_dir = storage.root_dir().open_dir("overlay-images")?;
let mut file = images_dir.open("images.json")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let entries: Vec<ImageJsonEntry> = serde_json::from_str(&contents)
.map_err(|e| StorageError::InvalidStorage(format!("Invalid images.json: {}", e)))?;
for entry in entries {
if entry.id == self.id {
return Ok(entry.names.unwrap_or_default());
}
}
Ok(Vec::new())
}
}
#[derive(Debug, serde::Deserialize)]
pub(crate) struct ImageJsonEntry {
pub(crate) id: String,
pub(crate) names: Option<Vec<String>>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_manifest_parsing() {
let manifest_json = r#"{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"size": 1234
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"size": 5678
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"size": 9012
}
]
}"#;
let manifest: ImageManifest = serde_json::from_str(manifest_json).unwrap();
assert_eq!(manifest.schema_version(), 2);
assert_eq!(manifest.layers().len(), 2);
}
}