use std::io::Read;
use anyhow::Context;
use serde::de::Visitor;
use serde::{Deserialize, Deserializer};
use super::Sha256Digest;
use crate::parser::constants::{SHA256_DIGEST_LENGTH, SHA256_DIGEST_PREFIX};
use crate::parser::util::sha256_digest_from_hex;
pub(super) type ImageLayerConfigs = Vec<LayerConfig>;
pub(super) type ImageHistory = Vec<HistoryEntry>;
#[derive(Deserialize)]
pub(super) struct DockerManifest {
#[serde(rename = "RepoTags")]
repo_tags: Option<Vec<String>>,
}
impl DockerManifest {
pub fn from_reader(src: impl Read) -> anyhow::Result<Option<String>> {
let mut manifest =
serde_json::from_reader::<_, Vec<DockerManifest>>(src).context("failed to parse the Docker's manifest")?;
Ok(manifest.get_mut(0).and_then(|manifest| {
manifest.repo_tags.as_mut().and_then(|tags| tags.pop())
}))
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub(super) enum JsonBlob {
Index { manifests: Vec<ImageIndexManifest> },
Manifest { layers: ImageLayerConfigs },
Config {
architecture: String,
os: String,
history: ImageHistory,
},
}
#[derive(Debug, Deserialize)]
pub(super) struct HistoryEntry {
pub created_by: String,
pub comment: Option<String>,
#[serde(default)]
pub empty_layer: bool,
}
#[derive(Debug, Deserialize)]
pub(super) struct LayerConfig {
#[serde(deserialize_with = "deserialize_sha256_digest")]
pub digest: Sha256Digest,
}
fn deserialize_sha256_digest<'de, D>(de: D) -> Result<Sha256Digest, D::Error>
where
D: Deserializer<'de>,
{
struct Sha256HashVisitor;
impl Visitor<'_> for Sha256HashVisitor {
type Value = Sha256Digest;
fn expecting(
&self,
formatter: &mut std::fmt::Formatter,
) -> std::fmt::Result {
formatter
.write_str("a sha256 digest string prefixed with `sha256:`")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let raw = &v[SHA256_DIGEST_PREFIX.len()..];
if raw.len() != SHA256_DIGEST_LENGTH * 2 {
return Err(serde::de::Error::custom(
"Invalid sha256 digest format",
));
}
sha256_digest_from_hex(raw).map_err(|_| {
serde::de::Error::custom("Failed to parse the sha256 digest")
})
}
}
de.deserialize_str(Sha256HashVisitor)
}
#[derive(Debug, Deserialize)]
pub(super) struct ImageIndexManifest {
pub annotations: Option<ImageIndexManifestAnnotations>,
}
#[derive(Debug, Deserialize)]
pub(super) struct ImageIndexManifestAnnotations {
#[serde(rename = "io.containerd.image.name")]
pub fully_qualified_image_name: Option<String>,
}