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 }