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}