Skip to content

Commit 5ed53c3

Browse files
authored
Merge pull request #1399 from jmarrero/verbose-status
lib/src/status: add verbose human readable output
2 parents e7d15d4 + 29066f5 commit 5ed53c3

File tree

2 files changed

+115
-10
lines changed

2 files changed

+115
-10
lines changed

lib/src/cli.rs

Lines changed: 18 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,18 @@ 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 { verbose: true, .. })
1366+
));
1367+
1368+
// Test verbose short form
1369+
assert!(matches!(
1370+
Opt::parse_including_static(["bootc", "status", "-v"]),
1371+
Opt::Status(StatusOpts { verbose: true, .. })
1372+
));
13561373
}
13571374

13581375
#[test]

lib/src/status.rs

Lines changed: 97 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,35 @@ 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(
364+
mut out: impl Write,
365+
ostree: &crate::spec::BootEntryOstree,
366+
slot: Option<Slot>,
367+
prefix_len: usize,
368+
) -> Result<()> {
369+
write_row_name(&mut out, "StateRoot", prefix_len)?;
370+
writeln!(out, "{}", ostree.stateroot)?;
371+
372+
// Show deployment serial (similar to Index in rpm-ostree)
373+
write_row_name(&mut out, "Deploy serial", prefix_len)?;
374+
writeln!(out, "{}", ostree.deploy_serial)?;
375+
376+
// Show if this is staged
377+
let is_staged = matches!(slot, Some(Slot::Staged));
378+
write_row_name(&mut out, "Staged", prefix_len)?;
379+
writeln!(out, "{}", if is_staged { "yes" } else { "no" })?;
380+
381+
Ok(())
382+
}
383+
362384
/// Write the data for a container image based status.
363385
fn human_render_slot(
364386
mut out: impl Write,
365387
slot: Option<Slot>,
366388
entry: &crate::spec::BootEntry,
367389
image: &crate::spec::ImageStatus,
390+
verbose: bool,
368391
) -> Result<()> {
369392
let transport = &image.image.transport;
370393
let imagename = &image.image.image;
@@ -415,6 +438,33 @@ fn human_render_slot(
415438
writeln!(out, "yes")?;
416439
}
417440

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

420470
Ok(())
@@ -426,6 +476,7 @@ fn human_render_slot_ostree(
426476
slot: Option<Slot>,
427477
entry: &crate::spec::BootEntry,
428478
ostree_commit: &str,
479+
verbose: bool,
429480
) -> Result<()> {
430481
// TODO consider rendering more ostree stuff here like rpm-ostree status does
431482
let prefix = match slot {
@@ -444,11 +495,18 @@ fn human_render_slot_ostree(
444495
writeln!(out, "yes")?;
445496
}
446497

498+
if verbose {
499+
// Show additional information in verbose mode similar to rpm-ostree
500+
if let Some(ostree) = &entry.ostree {
501+
render_verbose_ostree_info(&mut out, ostree, slot, prefix_len)?;
502+
}
503+
}
504+
447505
tracing::debug!("pinned={}", entry.pinned);
448506
Ok(())
449507
}
450508

451-
fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()> {
509+
fn human_readable_output_booted(mut out: impl Write, host: &Host, verbose: bool) -> Result<()> {
452510
let mut first = true;
453511
for (slot_name, status) in [
454512
(Slot::Staged, &host.status.staged),
@@ -462,9 +520,15 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()>
462520
writeln!(out)?;
463521
}
464522
if let Some(image) = &host_status.image {
465-
human_render_slot(&mut out, Some(slot_name), host_status, image)?;
523+
human_render_slot(&mut out, Some(slot_name), host_status, image, verbose)?;
466524
} else if let Some(ostree) = host_status.ostree.as_ref() {
467-
human_render_slot_ostree(&mut out, Some(slot_name), host_status, &ostree.checksum)?;
525+
human_render_slot_ostree(
526+
&mut out,
527+
Some(slot_name),
528+
host_status,
529+
&ostree.checksum,
530+
verbose,
531+
)?;
468532
} else {
469533
writeln!(out, "Current {slot_name} state is unknown")?;
470534
}
@@ -476,9 +540,9 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()>
476540
writeln!(out)?;
477541

478542
if let Some(image) = &entry.image {
479-
human_render_slot(&mut out, None, entry, image)?;
543+
human_render_slot(&mut out, None, entry, image, verbose)?;
480544
} else if let Some(ostree) = entry.ostree.as_ref() {
481-
human_render_slot_ostree(&mut out, None, entry, &ostree.checksum)?;
545+
human_render_slot_ostree(&mut out, None, entry, &ostree.checksum, verbose)?;
482546
}
483547
}
484548
}
@@ -487,9 +551,9 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()>
487551
}
488552

489553
/// Implementation of rendering our host structure in a "human readable" way.
490-
fn human_readable_output(mut out: impl Write, host: &Host) -> Result<()> {
554+
fn human_readable_output(mut out: impl Write, host: &Host, verbose: bool) -> Result<()> {
491555
if host.status.booted.is_some() {
492-
human_readable_output_booted(out, host)?;
556+
human_readable_output_booted(out, host, verbose)?;
493557
} else {
494558
writeln!(out, "System is not deployed via bootc.")?;
495559
}
@@ -503,7 +567,17 @@ mod tests {
503567
fn human_status_from_spec_fixture(spec_fixture: &str) -> Result<String> {
504568
let host: Host = serde_yaml::from_str(spec_fixture).unwrap();
505569
let mut w = Vec::new();
506-
human_readable_output(&mut w, &host).unwrap();
570+
human_readable_output(&mut w, &host, false).unwrap();
571+
let w = String::from_utf8(w).unwrap();
572+
Ok(w)
573+
}
574+
575+
/// Helper function to generate human-readable status output with verbose mode enabled
576+
/// from a YAML fixture string. Used for testing verbose output formatting.
577+
fn human_status_from_spec_fixture_verbose(spec_fixture: &str) -> Result<String> {
578+
let host: Host = serde_yaml::from_str(spec_fixture).unwrap();
579+
let mut w = Vec::new();
580+
human_readable_output(&mut w, &host, true).unwrap();
507581
let w = String::from_utf8(w).unwrap();
508582
Ok(w)
509583
}
@@ -634,4 +708,18 @@ mod tests {
634708
"};
635709
similar_asserts::assert_eq!(w, expected);
636710
}
711+
712+
#[test]
713+
fn test_human_readable_verbose_spec() {
714+
// Test verbose output includes additional fields
715+
let w =
716+
human_status_from_spec_fixture_verbose(include_str!("fixtures/spec-only-booted.yaml"))
717+
.expect("No spec found");
718+
719+
// Verbose output should include StateRoot, Deploy serial, Staged, and Commit
720+
assert!(w.contains("StateRoot:"));
721+
assert!(w.contains("Deploy serial:"));
722+
assert!(w.contains("Staged:"));
723+
assert!(w.contains("Commit:"));
724+
}
637725
}

0 commit comments

Comments
 (0)