docker_image_pusher/image/
manifest.rs1use crate::error::{RegistryError, Result};
2use serde_json::{self, Value};
3
4#[derive(Debug, Clone, PartialEq)]
6pub enum ManifestType {
7 DockerV2,
8 DockerList,
9 OciManifest,
10 OciIndex,
11}
12
13#[derive(Debug, Clone)]
15pub struct ParsedManifest {
16 pub manifest_type: ManifestType,
17 pub config_digest: Option<String>,
18 pub layer_digests: Vec<String>,
19 pub raw_data: Vec<u8>,
20 pub platform_manifests: Option<Vec<PlatformManifest>>, }
22
23#[derive(Debug, Clone)]
25pub struct PlatformManifest {
26 pub digest: String,
27 pub media_type: String,
28 pub platform: Option<Platform>,
29}
30
31#[derive(Debug, Clone)]
33pub struct Platform {
34 pub architecture: String,
35 pub os: String,
36 pub variant: Option<String>,
37}
38
39impl ManifestType {
40 pub fn from_media_type(media_type: &str) -> ManifestType {
41 match media_type {
42 "application/vnd.docker.distribution.manifest.v2+json" => ManifestType::DockerV2,
43 "application/vnd.docker.distribution.manifest.list.v2+json" => ManifestType::DockerList,
44 "application/vnd.oci.image.manifest.v1+json" => ManifestType::OciManifest,
45 "application/vnd.oci.image.index.v1+json" => ManifestType::OciIndex,
46 _ => ManifestType::DockerV2, }
48 }
49
50 pub fn is_index_type(&self) -> bool {
51 matches!(self, ManifestType::DockerList | ManifestType::OciIndex)
52 }
53
54 pub fn to_content_type(&self) -> &'static str {
55 match self {
56 ManifestType::DockerV2 => "application/vnd.docker.distribution.manifest.v2+json",
57 ManifestType::DockerList => "application/vnd.docker.distribution.manifest.list.v2+json",
58 ManifestType::OciManifest => "application/vnd.oci.image.manifest.v1+json",
59 ManifestType::OciIndex => "application/vnd.oci.image.index.v1+json",
60 }
61 }
62}
63
64pub fn parse_manifest(manifest_bytes: &[u8]) -> Result<Value> {
65 serde_json::from_slice(manifest_bytes).map_err(|e| RegistryError::Parse(e.to_string()))
66}
67
68pub fn parse_manifest_with_type(manifest_bytes: &[u8]) -> Result<ParsedManifest> {
70 let manifest: Value = parse_manifest(manifest_bytes)?;
71
72 let media_type = manifest
74 .get("mediaType")
75 .and_then(|m| m.as_str())
76 .unwrap_or("application/vnd.docker.distribution.manifest.v2+json");
77
78 let manifest_type = ManifestType::from_media_type(media_type);
79
80 match manifest_type {
81 ManifestType::OciIndex | ManifestType::DockerList => {
82 let platform_manifests = parse_index_manifests(&manifest)?;
84 Ok(ParsedManifest {
85 manifest_type,
86 config_digest: None, layer_digests: Vec::new(), raw_data: manifest_bytes.to_vec(),
89 platform_manifests: Some(platform_manifests),
90 })
91 }
92 ManifestType::DockerV2 | ManifestType::OciManifest => {
93 let config_digest = extract_config_digest(&manifest)?;
95 let layer_digests = extract_layer_digests(&manifest)?;
96 Ok(ParsedManifest {
97 manifest_type,
98 config_digest: Some(config_digest),
99 layer_digests,
100 raw_data: manifest_bytes.to_vec(),
101 platform_manifests: None,
102 })
103 }
104 }
105}
106
107pub fn extract_config_digest(manifest: &Value) -> Result<String> {
109 manifest
110 .get("config")
111 .and_then(|c| c.get("digest"))
112 .and_then(|d| d.as_str())
113 .map(|s| s.to_string())
114 .ok_or_else(|| RegistryError::Parse("Missing config digest in manifest".to_string()))
115}
116
117pub fn extract_layer_digests(manifest: &Value) -> Result<Vec<String>> {
119 let layers = manifest
120 .get("layers")
121 .and_then(|l| l.as_array())
122 .ok_or_else(|| RegistryError::Parse("Missing layers in manifest".to_string()))?;
123
124 let mut digests = Vec::new();
125 for layer in layers {
126 if let Some(digest) = layer.get("digest").and_then(|d| d.as_str()) {
127 digests.push(digest.to_string());
128 }
129 }
130
131 if digests.is_empty() {
132 return Err(RegistryError::Parse(
133 "No layer digests found in manifest".to_string(),
134 ));
135 }
136
137 Ok(digests)
138}
139
140fn parse_index_manifests(manifest: &Value) -> Result<Vec<PlatformManifest>> {
142 let manifests = manifest
143 .get("manifests")
144 .and_then(|m| m.as_array())
145 .ok_or_else(|| RegistryError::Parse("Missing manifests array in index".to_string()))?;
146
147 let mut platform_manifests = Vec::new();
148 for m in manifests {
149 let digest = m
150 .get("digest")
151 .and_then(|d| d.as_str())
152 .ok_or_else(|| RegistryError::Parse("Missing digest in manifest entry".to_string()))?;
153
154 let media_type = m
155 .get("mediaType")
156 .and_then(|mt| mt.as_str())
157 .unwrap_or("application/vnd.docker.distribution.manifest.v2+json");
158
159 let platform = if let Some(platform_obj) = m.get("platform") {
160 Some(Platform {
161 architecture: platform_obj
162 .get("architecture")
163 .and_then(|a| a.as_str())
164 .unwrap_or("amd64")
165 .to_string(),
166 os: platform_obj
167 .get("os")
168 .and_then(|o| o.as_str())
169 .unwrap_or("linux")
170 .to_string(),
171 variant: platform_obj
172 .get("variant")
173 .and_then(|v| v.as_str())
174 .map(|s| s.to_string()),
175 })
176 } else {
177 None
178 };
179
180 platform_manifests.push(PlatformManifest {
181 digest: digest.to_string(),
182 media_type: media_type.to_string(),
183 platform,
184 });
185 }
186
187 Ok(platform_manifests)
188}
189
190pub fn get_layers(manifest: &Value) -> Result<Vec<String>> {
192 extract_layer_digests(manifest)
193}
194
195pub fn is_gzipped(blob: &[u8]) -> bool {
197 blob.len() >= 2 && blob[0] == 0x1f && blob[1] == 0x8b
198}