oci_spec/image/descriptor.rs
1use super::{Arch, Digest, MediaType, Os};
2use crate::error::OciSpecError;
3use derive_builder::Builder;
4use getset::{CopyGetters, Getters, Setters};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(
9 Builder, Clone, CopyGetters, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,
10)]
11#[serde(rename_all = "camelCase")]
12#[builder(
13 pattern = "owned",
14 setter(into, strip_option),
15 build_fn(error = "OciSpecError")
16)]
17/// A Content Descriptor (or simply Descriptor) describes the disposition of
18/// the targeted content. It includes the type of the content, a content
19/// identifier (digest), and the byte-size of the raw content.
20/// Descriptors SHOULD be embedded in other formats to securely reference
21/// external content.
22pub struct Descriptor {
23 /// This REQUIRED property contains the media type of the referenced
24 /// content. Values MUST comply with RFC 6838, including the naming
25 /// requirements in its section 4.2.
26 #[getset(get = "pub", set = "pub")]
27 media_type: MediaType,
28 /// This REQUIRED property is the digest of the targeted content,
29 /// conforming to the requirements outlined in Digests. Retrieved
30 /// content SHOULD be verified against this digest when consumed via
31 /// untrusted sources.
32 #[getset(get = "pub", set = "pub")]
33 digest: Digest,
34 /// This REQUIRED property specifies the size, in bytes, of the raw
35 /// content. This property exists so that a client will have an
36 /// expected size for the content before processing. If the
37 /// length of the retrieved content does not match the specified
38 /// length, the content SHOULD NOT be trusted.
39 #[getset(get_copy = "pub", set = "pub")]
40 size: u64,
41 /// This OPTIONAL property specifies a list of URIs from which this
42 /// object MAY be downloaded. Each entry MUST conform to [RFC 3986](https://tools.ietf.org/html/rfc3986).
43 /// Entries SHOULD use the http and https schemes, as defined
44 /// in [RFC 7230](https://tools.ietf.org/html/rfc7230#section-2.7).
45 #[serde(skip_serializing_if = "Option::is_none")]
46 #[getset(get = "pub", set = "pub")]
47 #[builder(default)]
48 urls: Option<Vec<String>>,
49 /// This OPTIONAL property contains arbitrary metadata for this
50 /// descriptor. This OPTIONAL property MUST use the annotation
51 /// rules.
52 #[serde(skip_serializing_if = "Option::is_none")]
53 #[getset(get = "pub", set = "pub")]
54 #[builder(default)]
55 annotations: Option<HashMap<String, String>>,
56 /// This OPTIONAL property describes the minimum runtime requirements of
57 /// the image. This property SHOULD be present if its target is
58 /// platform-specific.
59 #[serde(skip_serializing_if = "Option::is_none")]
60 #[getset(get = "pub", set = "pub")]
61 #[builder(default)]
62 platform: Option<Platform>,
63 /// This OPTIONAL property contains the type of an artifact when the descriptor points to an
64 /// artifact. This is the value of the config descriptor mediaType when the descriptor
65 /// references an image manifest. If defined, the value MUST comply with RFC 6838, including
66 /// the naming requirements in its section 4.2, and MAY be registered with IANA.
67 #[serde(skip_serializing_if = "Option::is_none")]
68 #[getset(get = "pub", set = "pub")]
69 #[builder(default)]
70 artifact_type: Option<MediaType>,
71 /// This OPTIONAL property contains an embedded representation of the referenced content.
72 /// Values MUST conform to the Base 64 encoding, as defined in RFC 4648. The decoded data MUST
73 /// be identical to the referenced content and SHOULD be verified against the digest and size
74 /// fields by content consumers. See Embedded Content for when this is appropriate.
75 #[serde(skip_serializing_if = "Option::is_none")]
76 #[getset(get = "pub", set = "pub")]
77 #[builder(default)]
78 data: Option<String>,
79}
80
81#[derive(
82 Builder, Clone, Debug, Default, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,
83)]
84#[builder(
85 pattern = "owned",
86 setter(into, strip_option),
87 build_fn(error = "OciSpecError")
88)]
89#[getset(get = "pub", set = "pub")]
90/// Describes the minimum runtime requirements of the image.
91pub struct Platform {
92 /// This REQUIRED property specifies the CPU architecture.
93 /// Image indexes SHOULD use, and implementations SHOULD understand,
94 /// values listed in the Go Language document for GOARCH.
95 architecture: Arch,
96 /// This REQUIRED property specifies the operating system.
97 /// Image indexes SHOULD use, and implementations SHOULD understand,
98 /// values listed in the Go Language document for GOOS.
99 os: Os,
100 /// This OPTIONAL property specifies the version of the operating system
101 /// targeted by the referenced blob. Implementations MAY refuse to use
102 /// manifests where os.version is not known to work with the host OS
103 /// version. Valid values are implementation-defined. e.g.
104 /// 10.0.14393.1066 on windows.
105 #[serde(skip_serializing_if = "Option::is_none")]
106 #[builder(default)]
107 os_version: Option<String>,
108 /// This OPTIONAL property specifies an array of strings, each
109 /// specifying a mandatory OS feature. When os is windows, image
110 /// indexes SHOULD use, and implementations SHOULD understand
111 /// the following values:
112 /// - win32k: image requires win32k.sys on the host (Note: win32k.sys is
113 /// missing on Nano Server)
114 ///
115 /// When os is not windows, values are implementation-defined and SHOULD
116 /// be submitted to this specification for standardization.
117 #[serde(skip_serializing_if = "Option::is_none")]
118 #[builder(default)]
119 os_features: Option<Vec<String>>,
120 /// This OPTIONAL property specifies the variant of the CPU.
121 /// Image indexes SHOULD use, and implementations SHOULD understand,
122 /// variant values listed in the [Platform Variants]
123 /// (<https://github.com/opencontainers/image-spec/blob/main/image-index.md#platform-variants>)
124 /// table.
125 #[serde(skip_serializing_if = "Option::is_none")]
126 #[builder(default)]
127 variant: Option<String>,
128 /// This property is RESERVED for future versions of the specification.
129 #[serde(skip_serializing_if = "Option::is_none")]
130 #[builder(default)]
131 features: Option<Vec<String>>,
132}
133
134impl Descriptor {
135 /// Construct a new descriptor with the required fields.
136 pub fn new(media_type: MediaType, size: u64, digest: impl Into<Digest>) -> Self {
137 Self {
138 media_type,
139 size,
140 digest: digest.into(),
141 urls: Default::default(),
142 annotations: Default::default(),
143 platform: Default::default(),
144 artifact_type: Default::default(),
145 data: Default::default(),
146 }
147 }
148
149 /// Return a view of [`Self::digest()`] that has been parsed as a valid SHA-256.
150 pub fn as_digest_sha256(&self) -> Option<&str> {
151 match self.digest.algorithm() {
152 super::DigestAlgorithm::Sha256 => Some(self.digest.digest()),
153 _ => None,
154 }
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use std::str::FromStr;
161
162 use super::*;
163
164 #[test]
165 fn test_deserialize() {
166 let descriptor_str = r#"{
167 "mediaType": "application/vnd.oci.image.manifest.v1+json",
168 "digest":"sha256:c2b8beca588702777e5f35dafdbeae9ec16c2bab802331f81cacd2a92f1d5356",
169 "size":769,
170 "annotations":{"org.opencontainers.image.created": "2023-10-11T22:37:26Z"},
171 "artifactType":"application/spdx+json"}"#;
172 let descriptor: Descriptor = serde_json::from_str(descriptor_str).unwrap();
173 assert_eq!(descriptor.media_type, MediaType::ImageManifest);
174 assert_eq!(
175 descriptor.digest,
176 Digest::from_str(
177 "sha256:c2b8beca588702777e5f35dafdbeae9ec16c2bab802331f81cacd2a92f1d5356"
178 )
179 .unwrap()
180 );
181 assert_eq!(descriptor.size, 769);
182 assert_eq!(
183 descriptor
184 .annotations
185 .unwrap()
186 .get("org.opencontainers.image.created"),
187 Some(&"2023-10-11T22:37:26Z".to_string())
188 );
189 assert_eq!(
190 descriptor.artifact_type.unwrap(),
191 MediaType::Other("application/spdx+json".to_string())
192 );
193 }
194
195 #[test]
196 fn test_malformed_digest() {
197 let descriptor_str = r#"{
198 "mediaType": "application/vnd.oci.image.manifest.v1+json",
199 "digest":"../blah:this-is-an-attack",
200 "size":769,
201 "annotations":{"org.opencontainers.image.created": "2023-10-11T22:37:26Z"},
202 "artifactType":"application/spdx+json"}"#;
203 assert!(serde_json::from_str::<Descriptor>(descriptor_str).is_err());
204 }
205}