use async_trait::async_trait;
use tokio::io::AsyncReadExt;
use crate::image::{
docker::{MEDIA_TYPE_DOCKER_V2_LIST, MEDIA_TYPE_DOCKER_V2_SCHEMA2_MANIFEST},
oci::spec_v1::Image as OCIv1Image,
platform::get_os_platform,
types::{
errors::{ImageError, ImageResult},
Image, ImageInspect, ImageManifest, ImageReference, ImageSource,
},
};
use super::manifest::schema2::{Schema2, Schema2Config, Schema2Image, Schema2List};
#[derive(Debug)]
pub struct DockerImage {
pub source: Box<dyn ImageSource + Send + Sync>,
pub manifest: Vec<u8>,
pub cfgblob: Option<Vec<u8>>,
}
impl DockerImage {
async fn manifest_for_our_os_arch(
&mut self,
original: &ImageManifest,
) -> ImageResult<ImageManifest> {
let mime_type = original.mime_type.as_str();
log::debug!("Getting the Manifest for Current OS/Architecture");
match mime_type {
MEDIA_TYPE_DOCKER_V2_SCHEMA2_MANIFEST => {
log::trace!("Current Manifest is not a List, So using it as it is!");
Ok(original.clone())
}
MEDIA_TYPE_DOCKER_V2_LIST => {
log::trace!(
"Found Manifest List, Getting the actual manifest matching, OS/Platform"
);
let list: Schema2List = serde_json::from_slice(&original.manifest)?;
for m in list.manifests.iter() {
let (architecture, os) =
(m.platform.architecture.as_ref(), m.platform.os.clone());
let platform = get_os_platform();
if &platform.architecture == architecture.unwrap() && platform.os == os {
log::trace!("Getting Manifest for Digest: {}", m.digest);
return Ok(self.source.get_manifest(Some(&m.digest)).await?);
}
}
log::error!("No Manifest found Matching Current OS/Platform!");
Err(ImageError::new())
}
_ => {
log::error!(
"Media Type: {} found. Can't Download Manifest for this Media Type.",
mime_type
);
Err(ImageError::new())
}
}
}
async fn resolve_manifest(&mut self, original: &ImageManifest) -> ImageResult<ImageManifest> {
Ok(self.manifest_for_our_os_arch(original).await?)
}
}
#[async_trait]
impl Image for DockerImage {
fn reference(&self) -> Box<dyn ImageReference> {
self.source.reference()
}
fn source_ref(&self) -> &(dyn ImageSource + Send + Sync) {
self.source.as_ref()
}
async fn manifest(&mut self) -> ImageResult<ImageManifest> {
Ok(self.source.get_manifest(None).await?)
}
async fn resolved_manifest(&mut self) -> ImageResult<ImageManifest> {
let original = self.source.get_manifest(None).await?;
Ok(self.resolve_manifest(&original).await?)
}
async fn config_blob(&mut self) -> ImageResult<Vec<u8>> {
if self.cfgblob.is_none() {
log::debug!("Config blob is not cached. Downloading Config blob.");
let manifest = self.resolved_manifest().await?;
let schema: Schema2 = serde_json::from_slice(&manifest.manifest)?;
let cfgblob = self.source.get_blob(&schema.config.digest).await?;
futures_util::pin_mut!(cfgblob);
let mut blobvec = Vec::new();
cfgblob.read_to_end(&mut blobvec).await?;
self.cfgblob = Some(blobvec);
log::trace!(
"Config Blob: {}",
std::str::from_utf8(&self.cfgblob.as_ref().unwrap().to_vec()).unwrap()
);
}
Ok(self.cfgblob.as_ref().unwrap().clone())
}
async fn oci_config(&mut self) -> ImageResult<OCIv1Image> {
Ok(serde_json::from_slice(&self.config_blob().await?)?)
}
async fn inspect(&mut self) -> ImageResult<ImageInspect> {
let manifest: Schema2 = serde_json::from_slice(&self.resolved_manifest().await?.manifest)?;
let layers: Vec<String> = manifest
.layers
.iter()
.map(|l| l.digest.to_string())
.collect();
log::debug!(
"{}",
std::str::from_utf8(&self.config_blob().await?).unwrap()
);
let docker_image: Schema2Image = serde_json::from_slice(&self.config_blob().await?)?;
let default_docker_config = Schema2Config::default();
let docker_config = docker_image
.config
.as_ref()
.unwrap_or(&default_docker_config);
Ok(ImageInspect {
created: docker_image.created.to_string(),
architecture: docker_image.architecture.unwrap_or_default(),
docker_version: docker_image.docker_version.unwrap_or_default(),
os: docker_image.os.unwrap_or_default(),
layers,
labels: docker_config.labels.clone(),
env: docker_config.env.clone(),
})
}
}