2024-04-16 01:53:44 -07:00
|
|
|
use crate::{
|
2024-04-16 09:29:54 -07:00
|
|
|
name::ImageName,
|
2024-04-16 01:53:44 -07:00
|
|
|
packer::{OciPackedFormat, OciPackedImage},
|
|
|
|
schema::OciSchema,
|
|
|
|
};
|
2024-04-14 17:19:38 -07:00
|
|
|
|
2024-03-05 11:35:25 +00:00
|
|
|
use anyhow::Result;
|
2024-04-16 09:29:54 -07:00
|
|
|
use log::{debug, error};
|
|
|
|
use oci_spec::image::{
|
|
|
|
Descriptor, ImageConfiguration, ImageIndex, ImageIndexBuilder, ImageManifest, MediaType,
|
|
|
|
ANNOTATION_REF_NAME,
|
|
|
|
};
|
|
|
|
use std::{
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
sync::Arc,
|
|
|
|
};
|
|
|
|
use tokio::{fs, sync::RwLock};
|
2024-01-18 00:02:21 -08:00
|
|
|
|
2024-04-02 00:56:18 +00:00
|
|
|
#[derive(Clone)]
|
2024-04-15 10:24:14 -07:00
|
|
|
pub struct OciPackerCache {
|
2024-01-18 00:02:21 -08:00
|
|
|
cache_dir: PathBuf,
|
2024-04-16 09:29:54 -07:00
|
|
|
index: Arc<RwLock<ImageIndex>>,
|
2024-01-18 00:02:21 -08:00
|
|
|
}
|
|
|
|
|
2024-04-16 09:29:54 -07:00
|
|
|
const ANNOTATION_IMAGE_NAME: &str = "io.containerd.image.name";
|
|
|
|
const ANNOTATION_OCI_PACKER_FORMAT: &str = "dev.krata.oci.packer.format";
|
|
|
|
|
2024-04-15 10:24:14 -07:00
|
|
|
impl OciPackerCache {
|
2024-04-16 09:29:54 -07:00
|
|
|
pub async fn new(cache_dir: &Path) -> Result<OciPackerCache> {
|
|
|
|
let index = ImageIndexBuilder::default()
|
|
|
|
.schema_version(2u32)
|
|
|
|
.media_type(MediaType::ImageIndex)
|
|
|
|
.manifests(Vec::new())
|
|
|
|
.build()?;
|
|
|
|
let cache = OciPackerCache {
|
2024-01-18 00:02:21 -08:00
|
|
|
cache_dir: cache_dir.to_path_buf(),
|
2024-04-16 09:29:54 -07:00
|
|
|
index: Arc::new(RwLock::new(index)),
|
|
|
|
};
|
|
|
|
|
|
|
|
{
|
|
|
|
let mut mutex = cache.index.write().await;
|
|
|
|
*mutex = cache.load_index().await?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(cache)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn list(&self) -> Result<Vec<Descriptor>> {
|
|
|
|
let index = self.index.read().await;
|
|
|
|
Ok(index.manifests().clone())
|
2024-01-18 00:02:21 -08:00
|
|
|
}
|
|
|
|
|
2024-04-15 10:24:14 -07:00
|
|
|
pub async fn recall(
|
|
|
|
&self,
|
2024-04-16 09:29:54 -07:00
|
|
|
name: ImageName,
|
2024-04-15 10:24:14 -07:00
|
|
|
digest: &str,
|
|
|
|
format: OciPackedFormat,
|
2024-04-16 01:53:44 -07:00
|
|
|
) -> Result<Option<OciPackedImage>> {
|
2024-04-16 09:29:54 -07:00
|
|
|
let index = self.index.read().await;
|
|
|
|
|
|
|
|
let mut descriptor: Option<Descriptor> = None;
|
|
|
|
for manifest in index.manifests() {
|
|
|
|
if manifest.digest() == digest
|
|
|
|
&& manifest
|
|
|
|
.annotations()
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|x| x.get(ANNOTATION_OCI_PACKER_FORMAT))
|
|
|
|
.map(|x| x.as_str())
|
|
|
|
== Some(format.extension())
|
|
|
|
{
|
|
|
|
descriptor = Some(manifest.clone());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let Some(descriptor) = descriptor else {
|
|
|
|
return Ok(None);
|
|
|
|
};
|
|
|
|
|
2024-04-14 17:19:38 -07:00
|
|
|
let mut fs_path = self.cache_dir.clone();
|
2024-01-18 00:15:36 -08:00
|
|
|
let mut config_path = self.cache_dir.clone();
|
2024-01-18 00:02:21 -08:00
|
|
|
let mut manifest_path = self.cache_dir.clone();
|
2024-04-14 17:19:38 -07:00
|
|
|
fs_path.push(format!("{}.{}", digest, format.extension()));
|
2024-01-18 00:15:36 -08:00
|
|
|
manifest_path.push(format!("{}.manifest.json", digest));
|
|
|
|
config_path.push(format!("{}.config.json", digest));
|
2024-04-16 09:29:54 -07:00
|
|
|
|
|
|
|
if fs_path.exists() && manifest_path.exists() && config_path.exists() {
|
|
|
|
let image_metadata = fs::metadata(&fs_path).await?;
|
|
|
|
let manifest_metadata = fs::metadata(&manifest_path).await?;
|
|
|
|
let config_metadata = fs::metadata(&config_path).await?;
|
|
|
|
if image_metadata.is_file() && manifest_metadata.is_file() && config_metadata.is_file()
|
|
|
|
{
|
|
|
|
let manifest_bytes = fs::read(&manifest_path).await?;
|
|
|
|
let manifest: ImageManifest = serde_json::from_slice(&manifest_bytes)?;
|
|
|
|
let config_bytes = fs::read(&config_path).await?;
|
|
|
|
let config: ImageConfiguration = serde_json::from_slice(&config_bytes)?;
|
|
|
|
debug!("cache hit digest={}", digest);
|
|
|
|
Ok(Some(OciPackedImage::new(
|
|
|
|
name,
|
|
|
|
digest.to_string(),
|
|
|
|
fs_path.clone(),
|
|
|
|
format,
|
|
|
|
descriptor,
|
|
|
|
OciSchema::new(config_bytes, config),
|
|
|
|
OciSchema::new(manifest_bytes, manifest),
|
|
|
|
)))
|
2024-01-18 00:02:21 -08:00
|
|
|
} else {
|
2024-04-16 09:29:54 -07:00
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
debug!("cache miss digest={}", digest);
|
|
|
|
Ok(None)
|
|
|
|
}
|
2024-01-18 00:02:21 -08:00
|
|
|
}
|
|
|
|
|
2024-04-16 01:53:44 -07:00
|
|
|
pub async fn store(&self, packed: OciPackedImage) -> Result<OciPackedImage> {
|
2024-04-16 09:29:54 -07:00
|
|
|
let mut index = self.index.write().await;
|
|
|
|
let mut manifests = index.manifests().clone();
|
2024-04-15 10:24:14 -07:00
|
|
|
debug!("cache store digest={}", packed.digest);
|
2024-04-14 17:19:38 -07:00
|
|
|
let mut fs_path = self.cache_dir.clone();
|
2024-01-18 00:02:21 -08:00
|
|
|
let mut manifest_path = self.cache_dir.clone();
|
2024-01-18 00:15:36 -08:00
|
|
|
let mut config_path = self.cache_dir.clone();
|
2024-04-15 10:24:14 -07:00
|
|
|
fs_path.push(format!("{}.{}", packed.digest, packed.format.extension()));
|
|
|
|
manifest_path.push(format!("{}.manifest.json", packed.digest));
|
|
|
|
config_path.push(format!("{}.config.json", packed.digest));
|
2024-04-16 10:05:24 -07:00
|
|
|
if fs::rename(&packed.path, &fs_path).await.is_err() {
|
|
|
|
fs::copy(&packed.path, &fs_path).await?;
|
|
|
|
fs::remove_file(&packed.path).await?;
|
|
|
|
}
|
2024-04-16 01:53:44 -07:00
|
|
|
fs::write(&config_path, packed.config.raw()).await?;
|
|
|
|
fs::write(&manifest_path, packed.manifest.raw()).await?;
|
2024-04-16 09:29:54 -07:00
|
|
|
manifests.retain(|item| {
|
|
|
|
if item.digest() != &packed.digest {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
let Some(format) = item
|
|
|
|
.annotations()
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|x| x.get(ANNOTATION_OCI_PACKER_FORMAT))
|
|
|
|
.map(|x| x.as_str())
|
|
|
|
else {
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
if format != packed.format.extension() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
});
|
|
|
|
|
|
|
|
let mut descriptor = packed.descriptor.clone();
|
|
|
|
let mut annotations = descriptor.annotations().clone().unwrap_or_default();
|
|
|
|
annotations.insert(
|
|
|
|
ANNOTATION_OCI_PACKER_FORMAT.to_string(),
|
|
|
|
packed.format.extension().to_string(),
|
|
|
|
);
|
|
|
|
let image_name = packed.name.to_string();
|
|
|
|
annotations.insert(ANNOTATION_IMAGE_NAME.to_string(), image_name);
|
|
|
|
let image_ref = packed.name.reference.clone();
|
2024-04-22 12:48:45 -07:00
|
|
|
if let Some(image_ref) = image_ref {
|
|
|
|
annotations.insert(ANNOTATION_REF_NAME.to_string(), image_ref);
|
|
|
|
}
|
2024-04-16 09:29:54 -07:00
|
|
|
descriptor.set_annotations(Some(annotations));
|
|
|
|
manifests.push(descriptor.clone());
|
|
|
|
index.set_manifests(manifests);
|
|
|
|
self.save_index(&index).await?;
|
|
|
|
|
|
|
|
let packed = OciPackedImage::new(
|
|
|
|
packed.name,
|
2024-04-15 10:24:14 -07:00
|
|
|
packed.digest,
|
|
|
|
fs_path.clone(),
|
|
|
|
packed.format,
|
2024-04-16 09:29:54 -07:00
|
|
|
descriptor,
|
2024-04-15 10:24:14 -07:00
|
|
|
packed.config,
|
|
|
|
packed.manifest,
|
2024-04-16 09:29:54 -07:00
|
|
|
);
|
|
|
|
Ok(packed)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn save_empty_index(&self) -> Result<ImageIndex> {
|
|
|
|
let index = ImageIndexBuilder::default()
|
|
|
|
.schema_version(2u32)
|
|
|
|
.media_type(MediaType::ImageIndex)
|
|
|
|
.manifests(Vec::new())
|
|
|
|
.build()?;
|
|
|
|
self.save_index(&index).await?;
|
|
|
|
Ok(index)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn load_index(&self) -> Result<ImageIndex> {
|
|
|
|
let mut index_path = self.cache_dir.clone();
|
|
|
|
index_path.push("index.json");
|
|
|
|
|
|
|
|
if !index_path.exists() {
|
|
|
|
self.save_empty_index().await?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let content = fs::read_to_string(&index_path).await?;
|
|
|
|
let index = match serde_json::from_str::<ImageIndex>(&content) {
|
|
|
|
Ok(index) => index,
|
|
|
|
Err(error) => {
|
|
|
|
error!("image index was corrupted, creating a new one: {}", error);
|
|
|
|
self.save_empty_index().await?
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(index)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn save_index(&self, index: &ImageIndex) -> Result<()> {
|
|
|
|
let mut encoded = serde_json::to_string_pretty(index)?;
|
|
|
|
encoded.push('\n');
|
|
|
|
let mut index_path = self.cache_dir.clone();
|
|
|
|
index_path.push("index.json");
|
|
|
|
fs::write(&index_path, encoded).await?;
|
|
|
|
Ok(())
|
2024-01-18 00:02:21 -08:00
|
|
|
}
|
|
|
|
}
|