Skip to content

Commit 9b96f03

Browse files
authored
Merge pull request #901 from cgwalters/more-status
status: Rework human readable output
2 parents c1ac763 + 5543053 commit 9b96f03

File tree

3 files changed

+139
-51
lines changed

3 files changed

+139
-51
lines changed

lib/src/glyph.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//! Special Unicode characters used for display with ASCII fallbacks
2+
//! in case we're not in a UTF-8 locale.
3+
4+
use std::fmt::Display;
5+
6+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
7+
pub(crate) enum Glyph {
8+
BlackCircle,
9+
}
10+
11+
impl Glyph {
12+
// TODO: Add support for non-Unicode output
13+
#[allow(dead_code)]
14+
pub(crate) fn as_ascii(&self) -> &'static str {
15+
match self {
16+
Glyph::BlackCircle => "*",
17+
}
18+
}
19+
20+
pub(crate) fn as_utf8(&self) -> &'static str {
21+
match self {
22+
Glyph::BlackCircle => "●",
23+
}
24+
}
25+
}
26+
27+
impl Display for Glyph {
28+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29+
f.write_str(self.as_utf8())
30+
}
31+
}
32+
33+
#[test]
34+
fn test_glyph() {
35+
assert_eq!(Glyph::BlackCircle.as_utf8(), "●");
36+
}

lib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,5 @@ pub mod spec;
3939

4040
#[cfg(feature = "docgen")]
4141
mod docgen;
42+
mod glyph;
4243
mod imgstorage;

lib/src/status.rs

Lines changed: 102 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::borrow::Cow;
22
use std::collections::VecDeque;
33
use std::io::IsTerminal;
4+
use std::io::Read;
45
use std::io::Write;
56

67
use 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.
329357
fn 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

367415
fn 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

Comments
 (0)