/ src / types / manifest.rs
manifest.rs
  1  use ::serde::Deserialize;
  2  use serde_json::value::RawValue;
  3  
  4  use crate::registry_error::{RegistryError, RegistryResult};
  5  
  6  pub const APPLICATION_CONTENT_TYPE_TOP: &str = "application";
  7  pub const DOCKER_IMAGE_MANIFEST_V2_CONTENT_TYPE_SUB: &str =
  8      "vnd.docker.distribution.manifest.v2+json";
  9  
 10  const FAT_MANIFEST_CONTENT_TYPE: &str = "application/vnd.docker.distribution.manifest.list.v2+json";
 11  const DOCKER_IMAGE_MANIFEST_V2: &str = "application/vnd.docker.distribution.manifest.v2+json";
 12  const CONTAINER_CONFIG_JSON: &str = "application/vnd.docker.container.image.v1+json";
 13  const LAYER_TAR_GZIP: &str = "application/vnd.docker.image.rootfs.diff.tar.gzip";
 14  
 15  #[derive(Deserialize, Debug, Clone)]
 16  #[serde(rename_all = "camelCase")]
 17  pub struct FatManifest<'a> {
 18      pub schema_version: i32,
 19      pub media_type: String,
 20      #[serde(borrow)]
 21      manifest: &'a RawValue, // TODO: Implement
 22  }
 23  
 24  impl<'a> FatManifest<'a> {
 25      pub fn validate(&self) -> RegistryResult<()> {
 26          if self.schema_version != 2 {
 27              return Err(RegistryError::InvalidManifestSchema(format!(
 28                  "Expected manifest version 2, got {}",
 29                  self.schema_version
 30              )));
 31          }
 32  
 33          if self.media_type.as_str() != FAT_MANIFEST_CONTENT_TYPE {
 34              return Err(RegistryError::InvalidManifestSchema(format!(
 35                  "Expected media_type {FAT_MANIFEST_CONTENT_TYPE}, got {}",
 36                  self.media_type
 37              )));
 38          }
 39  
 40          Ok(())
 41      }
 42  }
 43  
 44  #[derive(Deserialize, Debug, Clone)]
 45  #[serde(rename_all = "camelCase")]
 46  pub struct DockerImageManifestV2 {
 47      pub schema_version: i32,
 48      pub media_type: String,
 49      pub config: ManifestConfig,
 50      pub layers: Vec<LayerManifest>,
 51  }
 52  
 53  impl DockerImageManifestV2 {
 54      pub fn parse(content_type: String, data: Vec<u8>) -> RegistryResult<Self> {
 55          let slice = data.as_slice();
 56          match content_type.as_str() {
 57              DOCKER_IMAGE_MANIFEST_V2 => {
 58                  let image_manifest: Self = serde_json::from_slice(slice)?;
 59                  image_manifest.validate()?;
 60                  Ok(image_manifest)
 61              }
 62              _ => {
 63                  error!("Got unsupported manifest type {content_type}");
 64                  Err(RegistryError::UnsupportedManifestType)
 65              }
 66          }
 67      }
 68  
 69      pub fn validate(&self) -> RegistryResult<()> {
 70          if self.schema_version != 2 {
 71              return Err(RegistryError::InvalidManifestSchema(format!(
 72                  "Expected manifest version 2, got {}",
 73                  self.schema_version
 74              )));
 75          }
 76  
 77          if self.media_type.as_str() != DOCKER_IMAGE_MANIFEST_V2 {
 78              return Err(RegistryError::InvalidManifestSchema(format!(
 79                  "Expected media_type {DOCKER_IMAGE_MANIFEST_V2}, got {}",
 80                  self.media_type
 81              )));
 82          }
 83  
 84          Ok(())
 85      }
 86  }
 87  
 88  #[derive(Deserialize, Debug, Clone)]
 89  #[serde(rename_all = "camelCase")]
 90  pub struct ManifestConfig {
 91      pub media_type: String,
 92      pub size: i64,
 93      pub digest: String,
 94  }
 95  
 96  impl ManifestConfig {
 97      pub fn validate(&self) -> RegistryResult<()> {
 98          if self.media_type.as_str() != CONTAINER_CONFIG_JSON {
 99              error!(
100                  "Expected manifest config type to have media type {}",
101                  self.media_type
102              );
103              return Err(RegistryError::InvalidManifestSchema(format!(
104                  "Expected media_type {CONTAINER_CONFIG_JSON}, got {}",
105                  self.media_type
106              )));
107          }
108  
109          // TODO: Validate size whenever docker actually uses it...
110  
111          Ok(())
112      }
113  }
114  
115  #[derive(Deserialize, Debug, Clone)]
116  #[serde(rename_all = "camelCase")]
117  pub struct LayerManifest {
118      pub media_type: String,
119      pub size: i64,
120      pub digest: String,
121  }
122  
123  impl LayerManifest {
124      pub fn validate(&self) -> RegistryResult<()> {
125          if self.media_type != LAYER_TAR_GZIP {
126              error!(
127                  "Expected manifest config type to have media type {}",
128                  self.media_type
129              );
130              return Err(RegistryError::InvalidManifestSchema(format!(
131                  "Expected media_type {LAYER_TAR_GZIP}, got {}",
132                  self.media_type
133              )));
134          }
135  
136          // TODO: Validate size...
137  
138          Ok(())
139      }
140  }