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