oci_spec/image/manifest.rs
1use super::{Descriptor, MediaType};
2use crate::{
3 error::{OciSpecError, Result},
4 from_file, from_reader, to_file, to_string, to_writer,
5};
6use derive_builder::Builder;
7use getset::{CopyGetters, Getters, MutGetters, Setters};
8use serde::{Deserialize, Serialize};
9use std::{
10 collections::HashMap,
11 fmt::Display,
12 io::{Read, Write},
13 path::Path,
14};
15
16#[derive(
17 Builder,
18 Clone,
19 CopyGetters,
20 Debug,
21 Deserialize,
22 Eq,
23 Getters,
24 MutGetters,
25 Setters,
26 PartialEq,
27 Serialize,
28)]
29#[serde(rename_all = "camelCase")]
30#[builder(
31 pattern = "owned",
32 setter(into, strip_option),
33 build_fn(error = "OciSpecError")
34)]
35/// Unlike the image index, which contains information about a set of images
36/// that can span a variety of architectures and operating systems, an image
37/// manifest provides a configuration and set of layers for a single
38/// container image for a specific architecture and operating system.
39pub struct ImageManifest {
40 /// This REQUIRED property specifies the image manifest schema version.
41 /// For this version of the specification, this MUST be 2 to ensure
42 /// backward compatibility with older versions of Docker. The
43 /// value of this field will not change. This field MAY be
44 /// removed in a future version of the specification.
45 #[getset(get_copy = "pub", set = "pub")]
46 schema_version: u32,
47 /// This property is reserved for use, to maintain compatibility. When
48 /// used, this field contains the media type of this document,
49 /// which differs from the descriptor use of mediaType.
50 #[serde(skip_serializing_if = "Option::is_none")]
51 #[getset(get = "pub", set = "pub")]
52 #[builder(default)]
53 media_type: Option<MediaType>,
54 /// This OPTIONAL property contains the type of an artifact when the manifest is used for an
55 /// artifact. This MUST be set when config.mediaType is set to the empty value. If defined, the
56 /// value MUST comply with RFC 6838, including the naming requirements in its section 4.2, and
57 /// MAY be registered with IANA. Implementations storing or copying image manifests MUST NOT
58 /// error on encountering an artifactType that is unknown to the implementation.
59 #[serde(skip_serializing_if = "Option::is_none")]
60 #[getset(get = "pub", set = "pub")]
61 #[builder(default)]
62 artifact_type: Option<MediaType>,
63 /// This REQUIRED property references a configuration object for a
64 /// container, by digest. Beyond the descriptor requirements,
65 /// the value has the following additional restrictions:
66 /// The media type descriptor property has additional restrictions for
67 /// config. Implementations MUST support at least the following
68 /// media types:
69 /// - application/vnd.oci.image.config.v1+json
70 ///
71 /// Manifests concerned with portability SHOULD use one of the above
72 /// media types.
73 #[getset(get = "pub", set = "pub")]
74 config: Descriptor,
75 /// Each item in the array MUST be a descriptor. The array MUST have the
76 /// base layer at index 0. Subsequent layers MUST then follow in
77 /// stack order (i.e. from `layers[0]` to `layers[len(layers)-1]`).
78 /// The final filesystem layout MUST match the result of applying
79 /// the layers to an empty directory. The ownership, mode, and other
80 /// attributes of the initial empty directory are unspecified.
81 #[getset(get_mut = "pub", get = "pub", set = "pub")]
82 layers: Vec<Descriptor>,
83 /// This OPTIONAL property specifies a descriptor of another manifest. This value, used by the
84 /// referrers API, indicates a relationship to the specified manifest.
85 #[serde(skip_serializing_if = "Option::is_none")]
86 #[getset(get = "pub", set = "pub")]
87 #[builder(default)]
88 subject: Option<Descriptor>,
89 /// This OPTIONAL property contains arbitrary metadata for the image
90 /// manifest. This OPTIONAL property MUST use the annotation
91 /// rules.
92 #[serde(skip_serializing_if = "Option::is_none")]
93 #[getset(get_mut = "pub", get = "pub", set = "pub")]
94 #[builder(default)]
95 annotations: Option<HashMap<String, String>>,
96}
97
98impl ImageManifest {
99 /// Attempts to load an image manifest from a file.
100 /// # Errors
101 /// This function will return an [OciSpecError::Io](crate::OciSpecError::Io)
102 /// if the file does not exist or an
103 /// [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest
104 /// cannot be deserialized.
105 /// # Example
106 /// ``` no_run
107 /// use oci_spec::image::ImageManifest;
108 ///
109 /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
110 /// ```
111 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ImageManifest> {
112 from_file(path)
113 }
114
115 /// Attempts to load an image manifest from a stream.
116 /// # Errors
117 /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe)
118 /// if the manifest cannot be deserialized.
119 /// # Example
120 /// ``` no_run
121 /// use oci_spec::image::ImageManifest;
122 /// use std::fs::File;
123 ///
124 /// let reader = File::open("manifest.json").unwrap();
125 /// let image_manifest = ImageManifest::from_reader(reader).unwrap();
126 /// ```
127 pub fn from_reader<R: Read>(reader: R) -> Result<ImageManifest> {
128 from_reader(reader)
129 }
130
131 /// Attempts to write an image manifest to a file as JSON. If the file already exists, it
132 /// will be overwritten.
133 /// # Errors
134 /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
135 /// the image manifest cannot be serialized.
136 /// # Example
137 /// ``` no_run
138 /// use oci_spec::image::ImageManifest;
139 ///
140 /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
141 /// image_manifest.to_file("my-manifest.json").unwrap();
142 /// ```
143 pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
144 to_file(&self, path, false)
145 }
146
147 /// Attempts to write an image manifest to a file as pretty printed JSON. If the file already exists, it
148 /// will be overwritten.
149 /// # Errors
150 /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
151 /// the image manifest cannot be serialized.
152 /// # Example
153 /// ``` no_run
154 /// use oci_spec::image::ImageManifest;
155 ///
156 /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
157 /// image_manifest.to_file_pretty("my-manifest.json").unwrap();
158 /// ```
159 pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
160 to_file(&self, path, true)
161 }
162
163 /// Attempts to write an image manifest to a stream as JSON.
164 /// # Errors
165 /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
166 /// the image manifest cannot be serialized.
167 /// # Example
168 /// ``` no_run
169 /// use oci_spec::image::ImageManifest;
170 ///
171 /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
172 /// let mut writer = Vec::new();
173 /// image_manifest.to_writer(&mut writer);
174 /// ```
175 pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
176 to_writer(&self, writer, false)
177 }
178
179 /// Attempts to write an image manifest to a stream as pretty printed JSON.
180 /// # Errors
181 /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
182 /// the image manifest cannot be serialized.
183 /// # Example
184 /// ``` no_run
185 /// use oci_spec::image::ImageManifest;
186 ///
187 /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
188 /// let mut writer = Vec::new();
189 /// image_manifest.to_writer_pretty(&mut writer);
190 /// ```
191 pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
192 to_writer(&self, writer, true)
193 }
194
195 /// Attempts to write an image manifest to a string as JSON.
196 /// # Errors
197 /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
198 /// the image configuration cannot be serialized.
199 /// # Example
200 /// ``` no_run
201 /// use oci_spec::image::ImageManifest;
202 ///
203 /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
204 /// let json_str = image_manifest.to_string().unwrap();
205 /// ```
206 pub fn to_string(&self) -> Result<String> {
207 to_string(&self, false)
208 }
209
210 /// Attempts to write an image manifest to a string as pretty printed JSON.
211 /// # Errors
212 /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if
213 /// the image configuration cannot be serialized.
214 /// # Example
215 /// ``` no_run
216 /// use oci_spec::image::ImageManifest;
217 ///
218 /// let image_manifest = ImageManifest::from_file("manifest.json").unwrap();
219 /// let json_str = image_manifest.to_string_pretty().unwrap();
220 /// ```
221 pub fn to_string_pretty(&self) -> Result<String> {
222 to_string(&self, true)
223 }
224}
225
226/// This ToString trait is automatically implemented for any type which implements the Display trait.
227/// As such, ToString shouldn’t be implemented directly: Display should be implemented instead,
228/// and you get the ToString implementation for free.
229impl Display for ImageManifest {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 // Serde seralization never fails since this is
232 // a combination of String and enums.
233 write!(
234 f,
235 "{}",
236 self.to_string_pretty()
237 .expect("ImageManifest to JSON convertion failed")
238 )
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use std::{fs, path::PathBuf, str::FromStr};
245
246 use super::*;
247 use crate::image::{DescriptorBuilder, Sha256Digest};
248
249 fn create_manifest() -> ImageManifest {
250 use crate::image::SCHEMA_VERSION;
251
252 let config = DescriptorBuilder::default()
253 .media_type(MediaType::ImageConfig)
254 .size(7023u64)
255 .digest(
256 "b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
257 .parse::<Sha256Digest>()
258 .unwrap(),
259 )
260 .build()
261 .expect("build config descriptor");
262
263 let layers: Vec<Descriptor> = [
264 (
265 32654u64,
266 "9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0",
267 ),
268 (
269 16724,
270 "3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b",
271 ),
272 (
273 73109,
274 "ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736",
275 ),
276 ]
277 .iter()
278 .map(|l| {
279 DescriptorBuilder::default()
280 .media_type(MediaType::ImageLayerGzip)
281 .size(l.0)
282 .digest(Sha256Digest::from_str(l.1).unwrap())
283 .build()
284 .expect("build layer")
285 })
286 .collect();
287
288 ImageManifestBuilder::default()
289 .schema_version(SCHEMA_VERSION)
290 .config(config)
291 .layers(layers)
292 .build()
293 .expect("build image manifest")
294 }
295
296 fn get_manifest_path() -> PathBuf {
297 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test/data/manifest.json")
298 }
299
300 #[test]
301 fn load_manifest_from_file() {
302 // arrange
303 let manifest_path = get_manifest_path();
304 let expected = create_manifest();
305
306 // act
307 let actual = ImageManifest::from_file(manifest_path).expect("from file");
308
309 // assert
310 assert_eq!(actual, expected);
311 }
312
313 #[test]
314 fn getset() {
315 let mut manifest = create_manifest();
316 assert_eq!(manifest.layers().len(), 3);
317 let layer_copy = manifest.layers()[0].clone();
318 manifest.layers_mut().push(layer_copy);
319 assert_eq!(manifest.layers().len(), 4);
320 }
321
322 #[test]
323 fn load_manifest_from_reader() {
324 // arrange
325 let reader = fs::read(get_manifest_path()).expect("read manifest");
326
327 // act
328 let actual = ImageManifest::from_reader(&*reader).expect("from reader");
329
330 // assert
331 let expected = create_manifest();
332 assert_eq!(actual, expected);
333 }
334
335 #[test]
336 fn save_manifest_to_file() {
337 // arrange
338 let tmp = std::env::temp_dir().join("save_manifest_to_file");
339 fs::create_dir_all(&tmp).expect("create test directory");
340 let manifest = create_manifest();
341 let manifest_path = tmp.join("manifest.json");
342
343 // act
344 manifest
345 .to_file_pretty(&manifest_path)
346 .expect("write manifest to file");
347
348 // assert
349 let actual = fs::read_to_string(manifest_path).expect("read actual");
350 let expected = fs::read_to_string(get_manifest_path()).expect("read expected");
351 assert_eq!(actual, expected);
352 }
353
354 #[test]
355 fn save_manifest_to_writer() {
356 // arrange
357 let manifest = create_manifest();
358 let mut actual = Vec::new();
359
360 // act
361 manifest.to_writer_pretty(&mut actual).expect("to writer");
362
363 // assert
364 let expected = fs::read(get_manifest_path()).expect("read expected");
365 assert_eq!(actual, expected);
366 }
367
368 #[test]
369 fn save_manifest_to_string() {
370 // arrange
371 let manifest = create_manifest();
372
373 // act
374 let actual = manifest.to_string_pretty().expect("to string");
375
376 // assert
377 let expected = fs::read_to_string(get_manifest_path()).expect("read expected");
378 assert_eq!(actual, expected);
379 }
380}