11use std:: borrow:: Cow ;
22use std:: collections:: VecDeque ;
33use std:: io:: IsTerminal ;
4+ use std:: io:: Read ;
45use std:: io:: Write ;
56
67use anyhow:: { Context , Result } ;
@@ -325,10 +326,37 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
325326 Ok ( ( ) )
326327}
327328
329+ #[ derive( Debug ) ]
330+ enum Slot {
331+ Staged ,
332+ Booted ,
333+ Rollback ,
334+ }
335+
336+ impl std:: fmt:: Display for Slot {
337+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
338+ let s = match self {
339+ Slot :: Staged => "staged" ,
340+ Slot :: Booted => "booted" ,
341+ Slot :: Rollback => "rollback" ,
342+ } ;
343+ f. write_str ( s)
344+ }
345+ }
346+
347+ /// Output a row title, prefixed by spaces
348+ fn write_row_name ( mut out : impl Write , s : & str , prefix_len : usize ) -> Result < ( ) > {
349+ let n = prefix_len. saturating_sub ( s. chars ( ) . count ( ) ) ;
350+ let mut spaces = std:: io:: repeat ( b' ' ) . take ( n as u64 ) ;
351+ std:: io:: copy ( & mut spaces, & mut out) ?;
352+ write ! ( out, "{s}: " ) ?;
353+ Ok ( ( ) )
354+ }
355+
328356/// Write the data for a container image based status.
329357fn human_render_imagestatus (
330358 mut out : impl Write ,
331- slot_name : & str ,
359+ slot : Slot ,
332360 image : & crate :: spec:: ImageStatus ,
333361) -> Result < ( ) > {
334362 let transport = & image. image . transport ;
@@ -340,46 +368,70 @@ fn human_render_imagestatus(
340368 // But for non-registry we include the transport
341369 Cow :: Owned ( format ! ( "{transport}:{imagename}" ) )
342370 } ;
343- writeln ! ( out, "Current {slot_name} image: {imageref}" ) ?;
344-
345- let version = image
346- . version
347- . as_deref ( )
348- . unwrap_or ( "No image version defined" ) ;
349- let timestamp = image
350- . timestamp
351- . as_ref ( )
352- . map ( |t| t. to_string ( ) )
353- . unwrap_or_else ( || "No timestamp present" . to_owned ( ) ) ;
371+ let prefix = match slot {
372+ Slot :: Staged => " Staged image" . into ( ) ,
373+ Slot :: Booted => format ! ( "{} Booted image" , crate :: glyph:: Glyph :: BlackCircle ) ,
374+ Slot :: Rollback => " Rollback image" . into ( ) ,
375+ } ;
376+ let prefix_len = prefix. chars ( ) . count ( ) ;
377+ writeln ! ( out, "{prefix}: {imageref}" ) ?;
378+
379+ write_row_name ( & mut out, "Digest" , prefix_len) ?;
354380 let digest = & image. image_digest ;
381+ writeln ! ( out, "{digest}" ) ?;
382+
383+ let timestamp = image. timestamp . as_ref ( ) ;
384+ // If we have a version, combine with timestamp
385+ if let Some ( version) = image. version . as_deref ( ) {
386+ write_row_name ( & mut out, "Version" , prefix_len) ?;
387+ if let Some ( timestamp) = timestamp {
388+ writeln ! ( out, "{version} ({timestamp})" ) ?;
389+ } else {
390+ writeln ! ( out, "{version}" ) ?;
391+ }
392+ } else if let Some ( timestamp) = timestamp. as_deref ( ) {
393+ // Otherwise just output timestamp
394+ write_row_name ( & mut out, "Timestamp" , prefix_len) ?;
395+ writeln ! ( out, "{timestamp}" ) ?;
396+ }
355397
356- writeln ! ( out, " Image version: {version} ({timestamp})" ) ?;
357- writeln ! ( out, " Image digest: {digest}" ) ?;
358398 Ok ( ( ) )
359399}
360400
361- fn human_render_ostree ( mut out : impl Write , slot_name : & str , _ostree_commit : & str ) -> Result < ( ) > {
401+ fn human_render_ostree ( mut out : impl Write , slot : Slot , ostree_commit : & str ) -> Result < ( ) > {
362402 // TODO consider rendering more ostree stuff here like rpm-ostree status does
363- writeln ! ( out, "Current {slot_name} state is native ostree" ) ?;
403+ let prefix = match slot {
404+ Slot :: Staged => " Staged ostree" . into ( ) ,
405+ Slot :: Booted => format ! ( "{} Booted ostree" , crate :: glyph:: Glyph :: BlackCircle ) ,
406+ Slot :: Rollback => " Rollback ostree" . into ( ) ,
407+ } ;
408+ let prefix_len = prefix. len ( ) ;
409+ writeln ! ( out, "{prefix}" ) ?;
410+ write_row_name ( & mut out, "Commit" , prefix_len) ?;
411+ writeln ! ( out, "{ostree_commit}" ) ?;
364412 Ok ( ( ) )
365413}
366414
367415fn human_readable_output_booted ( mut out : impl Write , host : & Host ) -> Result < ( ) > {
416+ let mut first = true ;
368417 for ( slot_name, status) in [
369- ( "staged" , & host. status . staged ) ,
370- ( "booted" , & host. status . booted ) ,
371- ( "rollback" , & host. status . rollback ) ,
418+ ( Slot :: Staged , & host. status . staged ) ,
419+ ( Slot :: Booted , & host. status . booted ) ,
420+ ( Slot :: Rollback , & host. status . rollback ) ,
372421 ] {
373422 if let Some ( host_status) = status {
423+ if first {
424+ first = false ;
425+ } else {
426+ writeln ! ( out) ?;
427+ }
374428 if let Some ( image) = & host_status. image {
375429 human_render_imagestatus ( & mut out, slot_name, image) ?;
376430 } else if let Some ( ostree) = host_status. ostree . as_ref ( ) {
377431 human_render_ostree ( & mut out, slot_name, & ostree. checksum ) ?;
378432 } else {
379433 writeln ! ( out, "Current {slot_name} state is unknown" ) ?;
380434 }
381- } else {
382- writeln ! ( out, "No {slot_name} image present" ) ?;
383435 }
384436 }
385437 Ok ( ( ) )
@@ -413,14 +465,14 @@ mod tests {
413465 let w = human_status_from_spec_fixture ( include_str ! ( "fixtures/spec-staged-booted.yaml" ) )
414466 . expect ( "No spec found" ) ;
415467 let expected = indoc:: indoc! { r"
416- Current staged image: quay.io/example/someimage:latest
417- Image version: nightly (2023-10-14 19:22:15 UTC)
418- Image digest: sha256:16dc2b6256b4ff0d2ec18d2dbfb06d117904010c8cf9732cdb022818cf7a7566
419- Current booted image: quay.io/example/someimage:latest
420- Image version: nightly (2023-09-30 19:22:16 UTC)
421- Image digest : sha256:736b359467c9437c1ac915acaae952aad854e07eb4a16a94999a48af08c83c34
422- No rollback image present
423- " } ;
468+ Staged image: quay.io/example/someimage:latest
469+ Digest: sha256:16dc2b6256b4ff0d2ec18d2dbfb06d117904010c8cf9732cdb022818cf7a7566
470+ Version: nightly (2023-10-14 19:22:15 UTC)
471+
472+ ● Booted image: quay.io/example/someimage:latest
473+ Digest : sha256:736b359467c9437c1ac915acaae952aad854e07eb4a16a94999a48af08c83c34
474+ Version: nightly (2023-09-30 19:22:16 UTC)
475+ " } ;
424476 similar_asserts:: assert_eq!( w, expected) ;
425477 }
426478
@@ -432,10 +484,12 @@ mod tests {
432484 ) )
433485 . expect ( "No spec found" ) ;
434486 let expected = indoc:: indoc! { r"
435- Current staged state is native ostree
436- Current booted state is native ostree
437- No rollback image present
438- " } ;
487+ Staged ostree
488+ Commit: 1c24260fdd1be20f72a4a97a75c582834ee3431fbb0fa8e4f482bb219d633a45
489+
490+ ● Booted ostree
491+ Commit: f9fa3a553ceaaaf30cf85bfe7eed46a822f7b8fd7e14c1e3389cbc3f6d27f791
492+ " } ;
439493 similar_asserts:: assert_eq!( w, expected) ;
440494 }
441495
@@ -445,12 +499,13 @@ mod tests {
445499 let w = human_status_from_spec_fixture ( include_str ! ( "fixtures/spec-ostree-to-bootc.yaml" ) )
446500 . expect ( "No spec found" ) ;
447501 let expected = indoc:: indoc! { r"
448- Current staged image: quay.io/centos-bootc/centos-bootc:stream9
449- Image version: stream9.20240807.0 (No timestamp present)
450- Image digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
451- Current booted state is native ostree
452- No rollback image present
453- " } ;
502+ Staged image: quay.io/centos-bootc/centos-bootc:stream9
503+ Digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
504+ Version: stream9.20240807.0
505+
506+ ● Booted ostree
507+ Commit: f9fa3a553ceaaaf30cf85bfe7eed46a822f7b8fd7e14c1e3389cbc3f6d27f791
508+ " } ;
454509 similar_asserts:: assert_eq!( w, expected) ;
455510 }
456511
@@ -460,12 +515,10 @@ mod tests {
460515 let w = human_status_from_spec_fixture ( include_str ! ( "fixtures/spec-only-booted.yaml" ) )
461516 . expect ( "No spec found" ) ;
462517 let expected = indoc:: indoc! { r"
463- No staged image present
464- Current booted image: quay.io/centos-bootc/centos-bootc:stream9
465- Image version: stream9.20240807.0 (No timestamp present)
466- Image digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
467- No rollback image present
468- " } ;
518+ ● Booted image: quay.io/centos-bootc/centos-bootc:stream9
519+ Digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
520+ Version: stream9.20240807.0
521+ " } ;
469522 similar_asserts:: assert_eq!( w, expected) ;
470523 }
471524
@@ -483,12 +536,10 @@ mod tests {
483536 let w = human_status_from_spec_fixture ( include_str ! ( "fixtures/spec-via-local-oci.yaml" ) )
484537 . unwrap ( ) ;
485538 let expected = indoc:: indoc! { r"
486- No staged image present
487- Current booted image: oci:/var/mnt/osupdate
488- Image version: stream9.20240807.0 (No timestamp present)
489- Image digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
490- No rollback image present
491- " } ;
539+ ● Booted image: oci:/var/mnt/osupdate
540+ Digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
541+ Version: stream9.20240807.0
542+ " } ;
492543 similar_asserts:: assert_eq!( w, expected) ;
493544 }
494545
0 commit comments