3
3
use std:: fmt:: Display ;
4
4
5
5
use anyhow:: Result ;
6
+ use ostree_ext:: container:: Transport ;
7
+ use ostree_ext:: oci_spec:: distribution:: Reference ;
6
8
use ostree_ext:: oci_spec:: image:: Digest ;
7
9
use ostree_ext:: { container:: OstreeImageReference , oci_spec} ;
8
10
use schemars:: JsonSchema ;
@@ -90,27 +92,37 @@ pub struct ImageReference {
90
92
pub signature : Option < ImageSignature > ,
91
93
}
92
94
95
+ /// If the reference is in :tag@digest form, strip the tag.
96
+ fn canonicalize_reference ( reference : Reference ) -> Option < Reference > {
97
+ // No tag? Just pass through.
98
+ if reference. tag ( ) . is_none ( ) {
99
+ return None ;
100
+ }
101
+
102
+ // No digest? Also pass through.
103
+ let Some ( digest) = reference. digest ( ) else {
104
+ return None ;
105
+ } ;
106
+
107
+ Some ( reference. clone_with_digest ( digest. to_owned ( ) ) )
108
+ }
109
+
93
110
impl ImageReference {
94
111
/// Returns a canonicalized version of this image reference, preferring the digest over the tag if both are present.
95
112
pub fn canonicalize ( self ) -> Result < Self > {
96
- match self . transport . as_str ( ) {
97
- "containers-storage" | "registry" | "oci" => {
113
+ // TODO maintain a proper transport enum in the spec here
114
+ let transport = Transport :: try_from ( self . transport . as_str ( ) ) ?;
115
+ match transport {
116
+ Transport :: ContainerStorage | Transport :: Registry => {
98
117
let reference: oci_spec:: distribution:: Reference = self . image . parse ( ) ?;
99
118
100
- // No tag? Just pass through.
101
- if reference. tag ( ) . is_none ( ) {
102
- return Ok ( self ) ;
103
- }
104
-
105
- // No digest? Also pass through.
106
- let Some ( digest) = reference. digest ( ) else {
119
+ // Check if the image reference needs canonicicalization
120
+ let Some ( reference) = canonicalize_reference ( reference) else {
107
121
return Ok ( self ) ;
108
122
} ;
109
123
110
- let registry = reference. registry ( ) ;
111
- let repository = reference. repository ( ) ;
112
124
let r = ImageReference {
113
- image : format ! ( "{registry}/{repository}@{digest}" ) ,
125
+ image : reference . to_string ( ) ,
114
126
transport : self . transport . clone ( ) ,
115
127
signature : self . signature . clone ( ) ,
116
128
} ;
@@ -288,6 +300,38 @@ mod tests {
288
300
289
301
use super :: * ;
290
302
303
+ #[ test]
304
+ fn test_canonicalize_reference ( ) {
305
+ // expand this
306
+ let passthrough = [
307
+ ( "quay.io/example/someimage:latest" ) ,
308
+ ( "quay.io/example/someimage" ) ,
309
+ ( "quay.io/example/someimage@sha256:5db6d8b5f34d3cbdaa1e82ed0152a5ac980076d19317d4269db149cbde057bb2" ) ,
310
+ ] ;
311
+ let mapped = [
312
+ (
313
+ "quay.io/example/someimage:latest@sha256:5db6d8b5f34d3cbdaa1e82ed0152a5ac980076d19317d4269db149cbde057bb2" ,
314
+ "quay.io/example/someimage@sha256:5db6d8b5f34d3cbdaa1e82ed0152a5ac980076d19317d4269db149cbde057bb2" ,
315
+ ) ,
316
+ (
317
+ "localhost/someimage:latest@sha256:5db6d8b5f34d3cbdaa1e82ed0152a5ac980076d19317d4269db149cbde057bb2" ,
318
+ "localhost/someimage@sha256:5db6d8b5f34d3cbdaa1e82ed0152a5ac980076d19317d4269db149cbde057bb2" ,
319
+ ) ,
320
+ ] ;
321
+ for & v in passthrough. iter ( ) {
322
+ let reference = Reference :: from_str ( v) . unwrap ( ) ;
323
+ assert ! ( reference. tag( ) . is_none( ) || reference. digest( ) . is_none( ) ) ;
324
+ assert ! ( canonicalize_reference( reference) . is_none( ) ) ;
325
+ }
326
+ for & ( initial, expected) in mapped. iter ( ) {
327
+ let reference = Reference :: from_str ( initial) . unwrap ( ) ;
328
+ assert ! ( reference. tag( ) . is_some( ) ) ;
329
+ assert ! ( reference. digest( ) . is_some( ) ) ;
330
+ let canonicalized = canonicalize_reference ( reference) . unwrap ( ) ;
331
+ assert_eq ! ( canonicalized. to_string( ) , expected) ;
332
+ }
333
+ }
334
+
291
335
#[ test]
292
336
fn test_image_reference_canonicalize ( ) {
293
337
let sample_digest =
@@ -305,11 +349,6 @@ mod tests {
305
349
format ! ( "quay.io/example/someimage@{}" , sample_digest) ,
306
350
"containers-storage" ,
307
351
) ,
308
- (
309
- format ! ( "quay.io/example/someimage:latest@{}" , sample_digest) ,
310
- format ! ( "quay.io/example/someimage@{}" , sample_digest) ,
311
- "oci" ,
312
- ) ,
313
352
// When only a digest is present, it should be used
314
353
(
315
354
format ! ( "quay.io/example/someimage@{}" , sample_digest) ,
@@ -340,7 +379,12 @@ mod tests {
340
379
format ! ( "localhost/someimage@{sample_digest}" ) ,
341
380
"registry" ,
342
381
) ,
343
- // OCI Archive / Dir should be preserved, and canonicalize should be a no-op
382
+ // Also for now, we do not canonicalize OCI references
383
+ (
384
+ format ! ( "/path/to/dir:latest" ) ,
385
+ format ! ( "/path/to/dir:latest" ) ,
386
+ "oci" ,
387
+ ) ,
344
388
(
345
389
"/tmp/repo" . to_string ( ) ,
346
390
"/tmp/repo" . to_string ( ) ,
@@ -374,6 +418,18 @@ mod tests {
374
418
}
375
419
}
376
420
421
+ #[ test]
422
+ fn test_unimplemented_oci_tagged_digested ( ) {
423
+ let imgref = ImageReference {
424
+ image : "path/to/image:sometag@sha256:5db6d8b5f34d3cbdaa1e82ed0152a5ac980076d19317d4269db149cbde057bb2" . to_string ( ) ,
425
+ transport : "oci" . to_string ( ) ,
426
+ signature : None
427
+ } ;
428
+ let canonicalized = imgref. clone ( ) . canonicalize ( ) . unwrap ( ) ;
429
+ // TODO For now this is known to incorrectly pass
430
+ assert_eq ! ( imgref, canonicalized) ;
431
+ }
432
+
377
433
#[ test]
378
434
fn test_parse_spec_v1_null ( ) {
379
435
const SPEC_FIXTURE : & str = include_str ! ( "fixtures/spec-v1-null.json" ) ;
0 commit comments