|
2 | 2 |
|
3 | 3 | use std::fmt::Display;
|
4 | 4 |
|
5 |
| -use ostree_ext::container::OstreeImageReference; |
6 | 5 | use ostree_ext::oci_spec::image::Digest;
|
| 6 | +use ostree_ext::{container::OstreeImageReference, oci_spec}; |
7 | 7 | use schemars::JsonSchema;
|
8 | 8 | use serde::{Deserialize, Serialize};
|
9 | 9 |
|
@@ -89,6 +89,25 @@ pub struct ImageReference {
|
89 | 89 | pub signature: Option<ImageSignature>,
|
90 | 90 | }
|
91 | 91 |
|
| 92 | +impl ImageReference { |
| 93 | + /// Returns a canonicalized version of this image reference, preferring the digest over the tag if both are present. |
| 94 | + pub fn canonicalize(self) -> Result<Self, anyhow::Error> { |
| 95 | + let reference: oci_spec::distribution::Reference = self.image.parse()?; |
| 96 | + |
| 97 | + if reference.digest().is_some() && reference.tag().is_some() { |
| 98 | + let registry = reference.registry(); |
| 99 | + let repository = reference.repository(); |
| 100 | + let digest = reference.digest().expect("digest is present"); |
| 101 | + return Ok(ImageReference { |
| 102 | + image: format!("{registry}/{repository}@{digest}"), |
| 103 | + ..self |
| 104 | + }); |
| 105 | + } |
| 106 | + |
| 107 | + Ok(self) |
| 108 | + } |
| 109 | +} |
| 110 | + |
92 | 111 | /// The status of the booted image
|
93 | 112 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
94 | 113 | #[serde(rename_all = "camelCase")]
|
@@ -253,6 +272,61 @@ mod tests {
|
253 | 272 |
|
254 | 273 | use super::*;
|
255 | 274 |
|
| 275 | + #[test] |
| 276 | + fn test_image_reference_canonicalize() { |
| 277 | + let sample_digest = |
| 278 | + "sha256:5db6d8b5f34d3cbdaa1e82ed0152a5ac980076d19317d4269db149cbde057bb2"; |
| 279 | + let test_cases = [ |
| 280 | + // When both a tag and digest are present, the digest should be used |
| 281 | + ( |
| 282 | + format!("quay.io/example/someimage:latest@{}", sample_digest), |
| 283 | + format!("quay.io/example/someimage@{}", sample_digest), |
| 284 | + ), |
| 285 | + // When only a digest is present, it should be used |
| 286 | + ( |
| 287 | + format!("quay.io/example/someimage@{}", sample_digest), |
| 288 | + format!("quay.io/example/someimage@{}", sample_digest), |
| 289 | + ), |
| 290 | + // When only a tag is present, it should be preserved |
| 291 | + ( |
| 292 | + "quay.io/example/someimage:latest".to_string(), |
| 293 | + "quay.io/example/someimage:latest".to_string(), |
| 294 | + ), |
| 295 | + // When no tag or digest is present, preserve the original image name |
| 296 | + ( |
| 297 | + "quay.io/example/someimage".to_string(), |
| 298 | + "quay.io/example/someimage".to_string(), |
| 299 | + ), |
| 300 | + // When used with a local image (i.e. from containers-storage), the functionality should |
| 301 | + // be the same as previous cases |
| 302 | + ( |
| 303 | + "localhost/someimage:latest".to_string(), |
| 304 | + "localhost/someimage:latest".to_string(), |
| 305 | + ), |
| 306 | + ( |
| 307 | + format!("localhost/someimage:latest@{sample_digest}"), |
| 308 | + format!("localhost/someimage@{sample_digest}"), |
| 309 | + ), |
| 310 | + ]; |
| 311 | + |
| 312 | + for (initial, expected) in test_cases { |
| 313 | + let imgref = ImageReference { |
| 314 | + image: initial.to_string(), |
| 315 | + transport: "registry".to_string(), |
| 316 | + signature: None, |
| 317 | + }; |
| 318 | + |
| 319 | + let canonicalized = imgref.canonicalize(); |
| 320 | + if let Err(e) = canonicalized { |
| 321 | + panic!("Failed to canonicalize {initial}: {e}"); |
| 322 | + } |
| 323 | + let canonicalized = canonicalized.unwrap(); |
| 324 | + assert_eq!(canonicalized.image, expected); |
| 325 | + assert_eq!(canonicalized.transport, "registry"); |
| 326 | + assert_eq!(canonicalized.signature, None); |
| 327 | + } |
| 328 | + } |
| 329 | + |
256 | 330 | #[test]
|
257 | 331 | fn test_parse_spec_v1_null() {
|
258 | 332 | const SPEC_FIXTURE: &str = include_str!("fixtures/spec-v1-null.json");
|
|
0 commit comments