oci_spec/image/
artifact.rs

1use super::{Descriptor, MediaType};
2use crate::error::{OciSpecError, Result};
3use derive_builder::Builder;
4use getset::{Getters, MutGetters, Setters};
5use serde::{Deserialize, Serialize};
6use std::{
7    collections::HashMap,
8    io::{Read, Write},
9    path::Path,
10};
11
12#[derive(
13    Builder, Clone, Debug, Deserialize, Eq, Getters, MutGetters, Setters, PartialEq, Serialize,
14)]
15#[serde(rename_all = "camelCase")]
16#[builder(
17    pattern = "owned",
18    setter(into, strip_option),
19    build_fn(error = "OciSpecError")
20)]
21/// The OCI Artifact manifest describes content addressable artifacts
22/// in order to store them along side container images in a registry.
23pub struct ArtifactManifest {
24    /// This property MUST be used and contain the media type
25    /// `application/vnd.oci.artifact.manifest.v1+json`.
26    #[getset(get = "pub")]
27    #[builder(default = "MediaType::ArtifactManifest")]
28    #[builder(setter(skip))]
29    media_type: MediaType,
30
31    /// This property SHOULD be used and contain
32    /// the mediaType of the referenced artifact.
33    /// If defined, the value MUST comply with RFC 6838,
34    /// including the naming requirements in its section 4.2,
35    /// and MAY be registered with IANA.
36    #[getset(get = "pub", set = "pub")]
37    artifact_type: MediaType,
38
39    /// This OPTIONAL property is an array of objects and each item
40    /// in the array MUST be a descriptor. Each descriptor represents
41    /// an artifact of any IANA mediaType. The list MAY be ordered
42    /// for certain artifact types like scan results.
43    #[getset(get_mut = "pub", get = "pub", set = "pub")]
44    #[builder(default)]
45    blobs: Vec<Descriptor>,
46
47    /// This OPTIONAL property specifies a descriptor of another manifest.
48    /// This value, used by the referrers API, indicates a relationship
49    /// to the specified manifest.
50    #[serde(skip_serializing_if = "Option::is_none")]
51    #[getset(get = "pub", set = "pub")]
52    #[builder(default)]
53    subject: Option<Descriptor>,
54
55    /// This OPTIONAL property contains additional metadata for the artifact
56    /// manifest. This OPTIONAL property MUST use the annotation rules.
57    /// See Pre-Defined Annotation Keys. Annotations MAY be used to filter
58    /// the response from the referrers API.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    #[getset(get_mut = "pub", get = "pub", set = "pub")]
61    #[builder(default)]
62    annotations: Option<HashMap<String, String>>,
63}
64
65impl ArtifactManifest {
66    /// Attempts to load an image manifest from a file.
67    ///
68    /// # Errors
69    ///
70    /// - [OciSpecError::Io] if the file does not exist
71    /// - [OciSpecError::SerDe] if the image manifest cannot be deserialized.
72    ///
73    /// # Example
74    ///
75    /// ``` no_run
76    /// use oci_spec::image::ArtifactManifest;
77    ///
78    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
79    /// ```
80    pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
81        crate::from_file(path)
82    }
83
84    /// Attempts to load an image manifest from a stream.
85    ///
86    /// # Errors
87    ///
88    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the manifest cannot be deserialized.
89    ///
90    /// # Example
91    ///
92    /// ``` no_run
93    /// use oci_spec::image::ArtifactManifest;
94    /// use std::fs::File;
95    ///
96    /// let reader = File::open("manifest.json").unwrap();
97    /// let artifact_manifest = ArtifactManifest::from_reader(reader).unwrap();
98    /// ```
99    pub fn from_reader<R: Read>(reader: R) -> Result<Self> {
100        crate::from_reader(reader)
101    }
102
103    /// Attempts to write an image manifest to a file as JSON. If the file already exists, it
104    /// will be overwritten.
105    ///
106    /// # Errors
107    ///
108    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest cannot be serialized.
109    ///
110    /// # Example
111    ///
112    /// ``` no_run
113    /// use oci_spec::image::ArtifactManifest;
114    ///
115    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
116    /// artifact_manifest.to_file("my-manifest.json").unwrap();
117    /// ```
118    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
119        crate::to_file(&self, path, false)
120    }
121
122    /// Attempts to write an image manifest to a file as pretty printed JSON. If the file already exists, it
123    /// will be overwritten.
124    ///
125    /// # Errors
126    ///
127    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest cannot be serialized.
128    ///
129    /// # Example
130    ///
131    /// ``` no_run
132    /// use oci_spec::image::ArtifactManifest;
133    ///
134    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
135    /// artifact_manifest.to_file_pretty("my-manifest.json").unwrap();
136    /// ```
137    pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
138        crate::to_file(&self, path, true)
139    }
140
141    /// Attempts to write an image manifest to a stream as JSON.
142    ///
143    /// # Errors
144    ///
145    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest cannot be serialized.
146    ///
147    /// # Example
148    ///
149    /// ``` no_run
150    /// use oci_spec::image::ArtifactManifest;
151    ///
152    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
153    /// let mut writer = Vec::new();
154    /// artifact_manifest.to_writer(&mut writer);
155    /// ```
156    pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
157        crate::to_writer(&self, writer, false)
158    }
159
160    /// Attempts to write an image manifest to a stream as pretty printed JSON.
161    ///
162    /// # Errors
163    ///
164    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest cannot be serialized.
165    ///
166    /// # Example
167    ///
168    /// ``` no_run
169    /// use oci_spec::image::ArtifactManifest;
170    ///
171    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
172    /// let mut writer = Vec::new();
173    /// artifact_manifest.to_writer_pretty(&mut writer);
174    /// ```
175    pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
176        crate::to_writer(&self, writer, true)
177    }
178
179    /// Attempts to write an image manifest to a string as JSON.
180    ///
181    /// # Errors
182    ///
183    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image configuration cannot be serialized.
184    ///
185    /// # Example
186    ///
187    /// ``` no_run
188    /// use oci_spec::image::ArtifactManifest;
189    ///
190    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
191    /// let json_str = artifact_manifest.to_string().unwrap();
192    /// ```
193    pub fn to_string(&self) -> Result<String> {
194        crate::to_string(&self, false)
195    }
196
197    /// Attempts to write an image manifest to a string as pretty printed JSON.
198    ///
199    /// # Errors
200    ///
201    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image configuration cannot be serialized.
202    ///
203    /// # Example
204    ///
205    /// ``` no_run
206    /// use oci_spec::image::ArtifactManifest;
207    ///
208    /// let artifact_manifest = ArtifactManifest::from_file("manifest.json").unwrap();
209    /// let json_str = artifact_manifest.to_string_pretty().unwrap();
210    /// ```
211    pub fn to_string_pretty(&self) -> Result<String> {
212        crate::to_string(&self, true)
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use crate::image::{DescriptorBuilder, Sha256Digest};
220    use std::{path::PathBuf, str::FromStr};
221
222    fn get_manifest_path() -> PathBuf {
223        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test/data/artifact_manifest.json")
224    }
225
226    fn create_manifest() -> ArtifactManifest {
227        let blob = DescriptorBuilder::default()
228            .media_type(MediaType::Other("application/gzip".to_string()))
229            .size(123u64)
230            .digest(
231                Sha256Digest::from_str(
232                    "87923725d74f4bfb94c9e86d64170f7521aad8221a5de834851470ca142da630",
233                )
234                .unwrap(),
235            )
236            .build()
237            .unwrap();
238        let subject = DescriptorBuilder::default()
239            .media_type(MediaType::ImageManifest)
240            .size(1234u64)
241            .digest(
242                Sha256Digest::from_str(
243                    "cc06a2839488b8bd2a2b99dcdc03d5cfd818eed72ad08ef3cc197aac64c0d0a0",
244                )
245                .unwrap(),
246            )
247            .build()
248            .unwrap();
249        let annotations = HashMap::from([
250            (
251                "org.opencontainers.artifact.created".to_string(),
252                "2022-01-01T14:42:55Z".to_string(),
253            ),
254            ("org.example.sbom.format".to_string(), "json".to_string()),
255        ]);
256        ArtifactManifestBuilder::default()
257            .artifact_type(MediaType::Other(
258                "application/vnd.example.sbom.v1".to_string(),
259            ))
260            .blobs(vec![blob])
261            .subject(subject)
262            .annotations(annotations)
263            .build()
264            .unwrap()
265    }
266
267    #[test]
268    fn load_manifest_from_file() {
269        // arrange
270        let manifest_path = get_manifest_path();
271        let expected = create_manifest();
272
273        // act
274        let actual = ArtifactManifest::from_file(manifest_path).expect("from file");
275
276        // assert
277        assert_eq!(actual, expected);
278    }
279}