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}