Skip to content

Commit 4cfafa7

Browse files
committed
lib/src/status: add verbose human readable output
Assisted-by: Claude Code
1 parent bb5171b commit 4cfafa7

File tree

2 files changed

+112
-10
lines changed

2 files changed

+112
-10
lines changed

lib/src/cli.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ pub(crate) struct StatusOpts {
188188
/// Only display status for the booted deployment.
189189
#[clap(long)]
190190
pub(crate) booted: bool,
191+
192+
/// Show verbose output for human readable format.
193+
#[clap(long, short = 'v')]
194+
pub(crate) verbose: bool,
191195
}
192196

193197
#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
@@ -1343,7 +1347,8 @@ mod tests {
13431347
json: false,
13441348
format: None,
13451349
format_version: None,
1346-
booted: false
1350+
booted: false,
1351+
verbose: false
13471352
})
13481353
));
13491354
assert!(matches!(
@@ -1353,6 +1358,24 @@ mod tests {
13531358
..
13541359
})
13551360
));
1361+
1362+
// Test verbose long form
1363+
assert!(matches!(
1364+
Opt::parse_including_static(["bootc", "status", "--verbose"]),
1365+
Opt::Status(StatusOpts {
1366+
verbose: true,
1367+
..
1368+
})
1369+
));
1370+
1371+
// Test verbose short form
1372+
assert!(matches!(
1373+
Opt::parse_including_static(["bootc", "status", "-v"]),
1374+
Opt::Status(StatusOpts {
1375+
verbose: true,
1376+
..
1377+
})
1378+
));
13561379
}
13571380

13581381
#[test]

lib/src/status.rs

Lines changed: 88 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
325325
.to_canon_json_writer(&mut out)
326326
.map_err(anyhow::Error::new),
327327
OutputFormat::Yaml => serde_yaml::to_writer(&mut out, &host).map_err(anyhow::Error::new),
328-
OutputFormat::HumanReadable => human_readable_output(&mut out, &host),
328+
OutputFormat::HumanReadable => human_readable_output(&mut out, &host, opts.verbose),
329329
}
330330
.context("Writing to stdout")?;
331331

@@ -365,6 +365,7 @@ fn human_render_slot(
365365
slot: Option<Slot>,
366366
entry: &crate::spec::BootEntry,
367367
image: &crate::spec::ImageStatus,
368+
verbose: bool,
368369
) -> Result<()> {
369370
let transport = &image.image.transport;
370371
let imagename = &image.image.image;
@@ -415,6 +416,43 @@ fn human_render_slot(
415416
writeln!(out, "yes")?;
416417
}
417418

419+
if verbose {
420+
// Show additional information in verbose mode similar to rpm-ostree
421+
if let Some(ostree) = &entry.ostree {
422+
write_row_name(&mut out, "StateRoot", prefix_len)?;
423+
writeln!(out, "{}", ostree.stateroot)?;
424+
425+
// Show deployment serial (similar to Index in rpm-ostree)
426+
write_row_name(&mut out, "Deploy serial", prefix_len)?;
427+
writeln!(out, "{}", ostree.deploy_serial)?;
428+
429+
// Show if this is staged
430+
let is_staged = matches!(slot, Some(Slot::Staged));
431+
write_row_name(&mut out, "Staged", prefix_len)?;
432+
writeln!(out, "{}", if is_staged { "yes" } else { "no" })?;
433+
434+
// Show the commit (equivalent to Base Commit in rpm-ostree)
435+
write_row_name(&mut out, "Commit", prefix_len)?;
436+
writeln!(out, "{}", ostree.checksum)?;
437+
}
438+
439+
// Show signature information if available
440+
if let Some(signature) = &image.image.signature {
441+
write_row_name(&mut out, "Signature", prefix_len)?;
442+
match signature {
443+
crate::spec::ImageSignature::OstreeRemote(remote) => {
444+
writeln!(out, "ostree-remote:{}", remote)?;
445+
}
446+
crate::spec::ImageSignature::ContainerPolicy => {
447+
writeln!(out, "container-policy")?;
448+
}
449+
crate::spec::ImageSignature::Insecure => {
450+
writeln!(out, "insecure")?;
451+
}
452+
}
453+
}
454+
}
455+
418456
tracing::debug!("pinned={}", entry.pinned);
419457

420458
Ok(())
@@ -426,6 +464,7 @@ fn human_render_slot_ostree(
426464
slot: Option<Slot>,
427465
entry: &crate::spec::BootEntry,
428466
ostree_commit: &str,
467+
verbose: bool,
429468
) -> Result<()> {
430469
// TODO consider rendering more ostree stuff here like rpm-ostree status does
431470
let prefix = match slot {
@@ -444,11 +483,28 @@ fn human_render_slot_ostree(
444483
writeln!(out, "yes")?;
445484
}
446485

486+
if verbose {
487+
// Show additional information in verbose mode similar to rpm-ostree
488+
if let Some(ostree) = &entry.ostree {
489+
write_row_name(&mut out, "StateRoot", prefix_len)?;
490+
writeln!(out, "{}", ostree.stateroot)?;
491+
492+
// Show deployment serial (similar to Index in rpm-ostree)
493+
write_row_name(&mut out, "Deploy serial", prefix_len)?;
494+
writeln!(out, "{}", ostree.deploy_serial)?;
495+
496+
// Show if this is staged
497+
let is_staged = matches!(slot, Some(Slot::Staged));
498+
write_row_name(&mut out, "Staged", prefix_len)?;
499+
writeln!(out, "{}", if is_staged { "yes" } else { "no" })?;
500+
}
501+
}
502+
447503
tracing::debug!("pinned={}", entry.pinned);
448504
Ok(())
449505
}
450506

451-
fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()> {
507+
fn human_readable_output_booted(mut out: impl Write, host: &Host, verbose: bool) -> Result<()> {
452508
let mut first = true;
453509
for (slot_name, status) in [
454510
(Slot::Staged, &host.status.staged),
@@ -462,9 +518,9 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()>
462518
writeln!(out)?;
463519
}
464520
if let Some(image) = &host_status.image {
465-
human_render_slot(&mut out, Some(slot_name), host_status, image)?;
521+
human_render_slot(&mut out, Some(slot_name), host_status, image, verbose)?;
466522
} else if let Some(ostree) = host_status.ostree.as_ref() {
467-
human_render_slot_ostree(&mut out, Some(slot_name), host_status, &ostree.checksum)?;
523+
human_render_slot_ostree(&mut out, Some(slot_name), host_status, &ostree.checksum, verbose)?;
468524
} else {
469525
writeln!(out, "Current {slot_name} state is unknown")?;
470526
}
@@ -476,9 +532,9 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()>
476532
writeln!(out)?;
477533

478534
if let Some(image) = &entry.image {
479-
human_render_slot(&mut out, None, entry, image)?;
535+
human_render_slot(&mut out, None, entry, image, verbose)?;
480536
} else if let Some(ostree) = entry.ostree.as_ref() {
481-
human_render_slot_ostree(&mut out, None, entry, &ostree.checksum)?;
537+
human_render_slot_ostree(&mut out, None, entry, &ostree.checksum, verbose)?;
482538
}
483539
}
484540
}
@@ -487,9 +543,9 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()>
487543
}
488544

489545
/// Implementation of rendering our host structure in a "human readable" way.
490-
fn human_readable_output(mut out: impl Write, host: &Host) -> Result<()> {
546+
fn human_readable_output(mut out: impl Write, host: &Host, verbose: bool) -> Result<()> {
491547
if host.status.booted.is_some() {
492-
human_readable_output_booted(out, host)?;
548+
human_readable_output_booted(out, host, verbose)?;
493549
} else {
494550
writeln!(out, "System is not deployed via bootc.")?;
495551
}
@@ -503,7 +559,17 @@ mod tests {
503559
fn human_status_from_spec_fixture(spec_fixture: &str) -> Result<String> {
504560
let host: Host = serde_yaml::from_str(spec_fixture).unwrap();
505561
let mut w = Vec::new();
506-
human_readable_output(&mut w, &host).unwrap();
562+
human_readable_output(&mut w, &host, false).unwrap();
563+
let w = String::from_utf8(w).unwrap();
564+
Ok(w)
565+
}
566+
567+
/// Helper function to generate human-readable status output with verbose mode enabled
568+
/// from a YAML fixture string. Used for testing verbose output formatting.
569+
fn human_status_from_spec_fixture_verbose(spec_fixture: &str) -> Result<String> {
570+
let host: Host = serde_yaml::from_str(spec_fixture).unwrap();
571+
let mut w = Vec::new();
572+
human_readable_output(&mut w, &host, true).unwrap();
507573
let w = String::from_utf8(w).unwrap();
508574
Ok(w)
509575
}
@@ -634,4 +700,17 @@ mod tests {
634700
"};
635701
similar_asserts::assert_eq!(w, expected);
636702
}
703+
704+
#[test]
705+
fn test_human_readable_verbose_spec() {
706+
// Test verbose output includes additional fields
707+
let w = human_status_from_spec_fixture_verbose(include_str!("fixtures/spec-only-booted.yaml"))
708+
.expect("No spec found");
709+
710+
// Verbose output should include StateRoot, Deploy serial, Staged, and Commit
711+
assert!(w.contains("StateRoot:"));
712+
assert!(w.contains("Deploy serial:"));
713+
assert!(w.contains("Staged:"));
714+
assert!(w.contains("Commit:"));
715+
}
637716
}

0 commit comments

Comments
 (0)