Skip to content

Commit cef9678

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

2 files changed

Lines changed: 109 additions & 10 deletions

File tree

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+
/// Include additional fields in 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: 85 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

@@ -359,12 +359,30 @@ fn write_row_name(mut out: impl Write, s: &str, prefix_len: usize) -> Result<()>
359359
Ok(())
360360
}
361361

362+
/// Helper function to render verbose ostree information
363+
fn render_verbose_ostree_info(mut out: impl Write, ostree: &crate::spec::BootEntryOstree, slot: Option<Slot>, prefix_len: usize) -> Result<()> {
364+
write_row_name(&mut out, "StateRoot", prefix_len)?;
365+
writeln!(out, "{}", ostree.stateroot)?;
366+
367+
// Show deployment serial (similar to Index in rpm-ostree)
368+
write_row_name(&mut out, "Deploy serial", prefix_len)?;
369+
writeln!(out, "{}", ostree.deploy_serial)?;
370+
371+
// Show if this is staged
372+
let is_staged = matches!(slot, Some(Slot::Staged));
373+
write_row_name(&mut out, "Staged", prefix_len)?;
374+
writeln!(out, "{}", if is_staged { "yes" } else { "no" })?;
375+
376+
Ok(())
377+
}
378+
362379
/// Write the data for a container image based status.
363380
fn human_render_slot(
364381
mut out: impl Write,
365382
slot: Option<Slot>,
366383
entry: &crate::spec::BootEntry,
367384
image: &crate::spec::ImageStatus,
385+
verbose: bool,
368386
) -> Result<()> {
369387
let transport = &image.image.transport;
370388
let imagename = &image.image.image;
@@ -415,6 +433,33 @@ fn human_render_slot(
415433
writeln!(out, "yes")?;
416434
}
417435

436+
if verbose {
437+
// Show additional information in verbose mode similar to rpm-ostree
438+
if let Some(ostree) = &entry.ostree {
439+
render_verbose_ostree_info(&mut out, ostree, slot, prefix_len)?;
440+
441+
// Show the commit (equivalent to Base Commit in rpm-ostree)
442+
write_row_name(&mut out, "Commit", prefix_len)?;
443+
writeln!(out, "{}", ostree.checksum)?;
444+
}
445+
446+
// Show signature information if available
447+
if let Some(signature) = &image.image.signature {
448+
write_row_name(&mut out, "Signature", prefix_len)?;
449+
match signature {
450+
crate::spec::ImageSignature::OstreeRemote(remote) => {
451+
writeln!(out, "ostree-remote:{}", remote)?;
452+
}
453+
crate::spec::ImageSignature::ContainerPolicy => {
454+
writeln!(out, "container-policy")?;
455+
}
456+
crate::spec::ImageSignature::Insecure => {
457+
writeln!(out, "insecure")?;
458+
}
459+
}
460+
}
461+
}
462+
418463
tracing::debug!("pinned={}", entry.pinned);
419464

420465
Ok(())
@@ -426,6 +471,7 @@ fn human_render_slot_ostree(
426471
slot: Option<Slot>,
427472
entry: &crate::spec::BootEntry,
428473
ostree_commit: &str,
474+
verbose: bool,
429475
) -> Result<()> {
430476
// TODO consider rendering more ostree stuff here like rpm-ostree status does
431477
let prefix = match slot {
@@ -444,11 +490,18 @@ fn human_render_slot_ostree(
444490
writeln!(out, "yes")?;
445491
}
446492

493+
if verbose {
494+
// Show additional information in verbose mode similar to rpm-ostree
495+
if let Some(ostree) = &entry.ostree {
496+
render_verbose_ostree_info(&mut out, ostree, slot, prefix_len)?;
497+
}
498+
}
499+
447500
tracing::debug!("pinned={}", entry.pinned);
448501
Ok(())
449502
}
450503

451-
fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()> {
504+
fn human_readable_output_booted(mut out: impl Write, host: &Host, verbose: bool) -> Result<()> {
452505
let mut first = true;
453506
for (slot_name, status) in [
454507
(Slot::Staged, &host.status.staged),
@@ -462,9 +515,9 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()>
462515
writeln!(out)?;
463516
}
464517
if let Some(image) = &host_status.image {
465-
human_render_slot(&mut out, Some(slot_name), host_status, image)?;
518+
human_render_slot(&mut out, Some(slot_name), host_status, image, verbose)?;
466519
} else if let Some(ostree) = host_status.ostree.as_ref() {
467-
human_render_slot_ostree(&mut out, Some(slot_name), host_status, &ostree.checksum)?;
520+
human_render_slot_ostree(&mut out, Some(slot_name), host_status, &ostree.checksum, verbose)?;
468521
} else {
469522
writeln!(out, "Current {slot_name} state is unknown")?;
470523
}
@@ -476,9 +529,9 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()>
476529
writeln!(out)?;
477530

478531
if let Some(image) = &entry.image {
479-
human_render_slot(&mut out, None, entry, image)?;
532+
human_render_slot(&mut out, None, entry, image, verbose)?;
480533
} else if let Some(ostree) = entry.ostree.as_ref() {
481-
human_render_slot_ostree(&mut out, None, entry, &ostree.checksum)?;
534+
human_render_slot_ostree(&mut out, None, entry, &ostree.checksum, verbose)?;
482535
}
483536
}
484537
}
@@ -487,9 +540,9 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()>
487540
}
488541

489542
/// Implementation of rendering our host structure in a "human readable" way.
490-
fn human_readable_output(mut out: impl Write, host: &Host) -> Result<()> {
543+
fn human_readable_output(mut out: impl Write, host: &Host, verbose: bool) -> Result<()> {
491544
if host.status.booted.is_some() {
492-
human_readable_output_booted(out, host)?;
545+
human_readable_output_booted(out, host, verbose)?;
493546
} else {
494547
writeln!(out, "System is not deployed via bootc.")?;
495548
}
@@ -503,7 +556,17 @@ mod tests {
503556
fn human_status_from_spec_fixture(spec_fixture: &str) -> Result<String> {
504557
let host: Host = serde_yaml::from_str(spec_fixture).unwrap();
505558
let mut w = Vec::new();
506-
human_readable_output(&mut w, &host).unwrap();
559+
human_readable_output(&mut w, &host, false).unwrap();
560+
let w = String::from_utf8(w).unwrap();
561+
Ok(w)
562+
}
563+
564+
/// Helper function to generate human-readable status output with verbose mode enabled
565+
/// from a YAML fixture string. Used for testing verbose output formatting.
566+
fn human_status_from_spec_fixture_verbose(spec_fixture: &str) -> Result<String> {
567+
let host: Host = serde_yaml::from_str(spec_fixture).unwrap();
568+
let mut w = Vec::new();
569+
human_readable_output(&mut w, &host, true).unwrap();
507570
let w = String::from_utf8(w).unwrap();
508571
Ok(w)
509572
}
@@ -634,4 +697,17 @@ mod tests {
634697
"};
635698
similar_asserts::assert_eq!(w, expected);
636699
}
700+
701+
#[test]
702+
fn test_human_readable_verbose_spec() {
703+
// Test verbose output includes additional fields
704+
let w = human_status_from_spec_fixture_verbose(include_str!("fixtures/spec-only-booted.yaml"))
705+
.expect("No spec found");
706+
707+
// Verbose output should include StateRoot, Deploy serial, Staged, and Commit
708+
assert!(w.contains("StateRoot:"));
709+
assert!(w.contains("Deploy serial:"));
710+
assert!(w.contains("Staged:"));
711+
assert!(w.contains("Commit:"));
712+
}
637713
}

0 commit comments

Comments
 (0)