@@ -28,6 +28,7 @@ pub struct ContainerRequest<I: Image> {
2828 pub ( crate ) overridden_cmd : Vec < String > ,
2929 pub ( crate ) image_name : Option < String > ,
3030 pub ( crate ) image_tag : Option < String > ,
31+ pub ( crate ) image_digest : Option < String > ,
3132 pub ( crate ) container_name : Option < String > ,
3233 pub ( crate ) platform : Option < String > ,
3334 pub ( crate ) network : Option < String > ,
@@ -181,13 +182,22 @@ impl<I: Image> ContainerRequest<I> {
181182 }
182183
183184 pub fn descriptor ( & self ) -> String {
184- let original_name = self . image . name ( ) ;
185- let original_tag = self . image . tag ( ) ;
186-
187- let name = self . image_name . as_deref ( ) . unwrap_or ( original_name) ;
188- let tag = self . image_tag . as_deref ( ) . unwrap_or ( original_tag) ;
189-
190- format ! ( "{name}:{tag}" )
185+ let name = self
186+ . image_name
187+ . as_deref ( )
188+ . unwrap_or_else ( || self . image . name ( ) ) ;
189+ let tag = self
190+ . image_tag
191+ . as_deref ( )
192+ . unwrap_or_else ( || self . image . tag ( ) ) ;
193+
194+ // An explicit `with_digest` override takes precedence over a digest baked into the image.
195+ // When a digest is present, the reference becomes `name:tag@digest`: Docker resolves the
196+ // image by digest, while the tag is kept for readability.
197+ match self . image_digest . as_deref ( ) . or_else ( || self . image . digest ( ) ) {
198+ Some ( digest) => format ! ( "{name}:{tag}@{digest}" ) ,
199+ None => format ! ( "{name}:{tag}" ) ,
200+ }
191201 }
192202
193203 pub fn ready_conditions ( & self ) -> Vec < WaitFor > {
@@ -265,6 +275,7 @@ impl<I: Image> From<I> for ContainerRequest<I> {
265275 overridden_cmd : Vec :: new ( ) ,
266276 image_name : None ,
267277 image_tag : None ,
278+ image_digest : None ,
268279 container_name : None ,
269280 platform : None ,
270281 network : None ,
@@ -327,6 +338,7 @@ impl<I: Image + Debug> Debug for ContainerRequest<I> {
327338 . field ( "overridden_cmd" , & self . overridden_cmd )
328339 . field ( "image_name" , & self . image_name )
329340 . field ( "image_tag" , & self . image_tag )
341+ . field ( "image_digest" , & self . image_digest )
330342 . field ( "container_name" , & self . container_name )
331343 . field ( "platform" , & self . platform )
332344 . field ( "network" , & self . network )
@@ -368,3 +380,67 @@ impl<I: Image + Debug> Debug for ContainerRequest<I> {
368380 repr. finish ( )
369381 }
370382}
383+
384+ #[ cfg( test) ]
385+ mod tests {
386+ use super :: * ;
387+ use crate :: { images:: generic:: GenericImage , ImageExt } ;
388+
389+ /// Minimal image that pins a digest via the [`Image`] trait itself.
390+ #[ derive( Debug , Default ) ]
391+ struct DigestPinnedImage ;
392+
393+ impl Image for DigestPinnedImage {
394+ fn name ( & self ) -> & str {
395+ "pinned"
396+ }
397+
398+ fn tag ( & self ) -> & str {
399+ "1.0"
400+ }
401+
402+ fn digest ( & self ) -> Option < & str > {
403+ Some ( "sha256:aaaa" )
404+ }
405+
406+ fn ready_conditions ( & self ) -> Vec < WaitFor > {
407+ Vec :: new ( )
408+ }
409+ }
410+
411+ #[ test]
412+ fn descriptor_without_digest_uses_name_and_tag ( ) {
413+ let request: ContainerRequest < _ > = GenericImage :: new ( "nginx" , "1.25" ) . into ( ) ;
414+ assert_eq ! ( request. descriptor( ) , "nginx:1.25" ) ;
415+ }
416+
417+ #[ test]
418+ fn descriptor_with_digest_override_keeps_tag ( ) {
419+ let request = GenericImage :: new ( "nginx" , "1.25" ) . with_digest ( "sha256:abc123" ) ;
420+ assert_eq ! ( request. descriptor( ) , "nginx:1.25@sha256:abc123" ) ;
421+ }
422+
423+ #[ test]
424+ fn descriptor_uses_digest_from_image_trait ( ) {
425+ let request: ContainerRequest < _ > = DigestPinnedImage . into ( ) ;
426+ assert_eq ! ( request. descriptor( ) , "pinned:1.0@sha256:aaaa" ) ;
427+ }
428+
429+ #[ test]
430+ fn with_digest_overrides_image_trait_digest ( ) {
431+ let request = DigestPinnedImage . with_digest ( "sha256:bbbb" ) ;
432+ assert_eq ! ( request. descriptor( ) , "pinned:1.0@sha256:bbbb" ) ;
433+ }
434+
435+ #[ test]
436+ fn descriptor_combines_name_tag_and_digest_overrides ( ) {
437+ let request = GenericImage :: new ( "nginx" , "1.25" )
438+ . with_name ( "ghcr.io/library/nginx" )
439+ . with_tag ( "mainline" )
440+ . with_digest ( "sha256:abc123" ) ;
441+ assert_eq ! (
442+ request. descriptor( ) ,
443+ "ghcr.io/library/nginx:mainline@sha256:abc123"
444+ ) ;
445+ }
446+ }
0 commit comments