@@ -101,6 +101,27 @@ enum OciCommand {
101101 /// optional reference name for the manifest, use as 'ref/<name>' elsewhere
102102 name : Option < String > ,
103103 } ,
104+ /// List all tagged OCI images in the repository
105+ #[ clap( name = "images" ) ]
106+ ListImages ,
107+ /// Show information about an OCI image
108+ #[ clap( name = "inspect" ) ]
109+ Inspect {
110+ /// Image reference (tag name or manifest digest)
111+ image : String ,
112+ } ,
113+ /// Tag an image with a new name
114+ Tag {
115+ /// Manifest digest (sha256:...)
116+ manifest_digest : String ,
117+ /// Tag name to assign
118+ name : String ,
119+ } ,
120+ /// Remove a tag from an image
121+ Untag {
122+ /// Tag name to remove
123+ name : String ,
124+ } ,
104125 /// Compute the composefs image object id of the rootfs of a stored OCI image
105126 ComputeId {
106127 #[ clap( flatten) ]
@@ -359,11 +380,110 @@ where
359380 println ! ( "{}" , image_id. to_id( ) ) ;
360381 }
361382 OciCommand :: Pull { ref image, name } => {
362- let ( digest, verity) =
363- composefs_oci:: pull ( & Arc :: new ( repo) , image, name. as_deref ( ) , None ) . await ?;
383+ // If no explicit name provided, use the image reference as the tag
384+ let tag_name = name. as_deref ( ) . unwrap_or ( image) ;
385+ let result =
386+ composefs_oci:: pull_image ( & Arc :: new ( repo) , image, Some ( tag_name) , None ) . await ?;
364387
365- println ! ( "config {digest}" ) ;
366- println ! ( "verity {}" , verity. to_hex( ) ) ;
388+ println ! ( "manifest {}" , result. manifest_digest) ;
389+ println ! ( "config {}" , result. config_digest) ;
390+ println ! ( "verity {}" , result. manifest_verity. to_hex( ) ) ;
391+ println ! ( "tagged {tag_name}" ) ;
392+ }
393+ OciCommand :: ListImages => {
394+ let images = composefs_oci:: oci_image:: list_images ( & repo) ?;
395+
396+ if images. is_empty ( ) {
397+ println ! ( "No images found" ) ;
398+ } else {
399+ println ! (
400+ "{:<30} {:<12} {:<10} {:<8} {:<6}" ,
401+ "NAME" , "DIGEST" , "ARCH" , "SEALED" , "LAYERS"
402+ ) ;
403+ for img in images {
404+ let digest_short = img
405+ . manifest_digest
406+ . strip_prefix ( "sha256:" )
407+ . unwrap_or ( & img. manifest_digest ) ;
408+ let digest_display = if digest_short. len ( ) > 12 {
409+ & digest_short[ ..12 ]
410+ } else {
411+ digest_short
412+ } ;
413+ println ! (
414+ "{:<30} {:<12} {:<10} {:<8} {:<6}" ,
415+ img. name,
416+ digest_display,
417+ if img. architecture. is_empty( ) {
418+ "artifact"
419+ } else {
420+ & img. architecture
421+ } ,
422+ if img. sealed { "yes" } else { "no" } ,
423+ img. layer_count
424+ ) ;
425+ }
426+ }
427+ }
428+ OciCommand :: Inspect { ref image } => {
429+ let img = if image. starts_with ( "sha256:" ) {
430+ composefs_oci:: oci_image:: OciImage :: open ( & repo, image, None ) ?
431+ } else {
432+ composefs_oci:: oci_image:: OciImage :: open_ref ( & repo, image) ?
433+ } ;
434+
435+ println ! ( "Manifest: {}" , img. manifest_digest( ) ) ;
436+ println ! ( "Config: {}" , img. config_digest( ) ) ;
437+ println ! (
438+ "Type: {}" ,
439+ if img. is_container_image( ) {
440+ "container"
441+ } else {
442+ "artifact"
443+ }
444+ ) ;
445+
446+ if img. is_container_image ( ) {
447+ println ! ( "Architecture: {}" , img. architecture( ) ) ;
448+ println ! ( "OS: {}" , img. os( ) ) ;
449+ }
450+
451+ if let Some ( created) = img. created ( ) {
452+ println ! ( "Created: {created}" ) ;
453+ }
454+
455+ println ! (
456+ "Sealed: {}" ,
457+ if img. is_sealed( ) { "yes" } else { "no" }
458+ ) ;
459+ if let Some ( seal) = img. seal_digest ( ) {
460+ println ! ( "Seal digest: {seal}" ) ;
461+ }
462+
463+ println ! ( "Layers: {}" , img. layer_descriptors( ) . len( ) ) ;
464+ for ( i, layer) in img. layer_descriptors ( ) . iter ( ) . enumerate ( ) {
465+ println ! ( " [{i}] {} ({} bytes)" , layer. digest( ) , layer. size( ) ) ;
466+ }
467+
468+ if let Some ( labels) = img. labels ( ) {
469+ if !labels. is_empty ( ) {
470+ println ! ( "Labels:" ) ;
471+ for ( k, v) in labels {
472+ println ! ( " {k}: {v}" ) ;
473+ }
474+ }
475+ }
476+ }
477+ OciCommand :: Tag {
478+ ref manifest_digest,
479+ ref name,
480+ } => {
481+ composefs_oci:: oci_image:: tag_image ( & repo, manifest_digest, name) ?;
482+ println ! ( "Tagged {manifest_digest} as {name}" ) ;
483+ }
484+ OciCommand :: Untag { ref name } => {
485+ composefs_oci:: oci_image:: untag_image ( & repo, name) ?;
486+ println ! ( "Removed tag {name}" ) ;
367487 }
368488 OciCommand :: Seal {
369489 config_opts :
0 commit comments