Skip to content

Commit bc20565

Browse files
committed
cli: Adds human readable output format to bootc status
Implements a human readable output format as the primary `bootc status` output. Also implements associated testing using specs from systems in various stages of bootc deployment. Co-authored-by: Huijing Hei <hhei@redhat.com> Co-authored-by: Yasmin de Souza <ydesouza@redhat.com> Co-authored-by: Steven Presti <spresti@redhat.com> Co-authored-by: Luke Yang <luyang@redhat.com> Co-authored-by: Joseph Marrero <jmarrero@redhat.com> Signed-off-by: djach7 <djachimo@redhat.com>
1 parent 1fd6c69 commit bc20565

File tree

7 files changed

+312
-0
lines changed

7 files changed

+312
-0
lines changed

lib/src/cli.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ pub(crate) struct EditOpts {
120120
#[derive(Debug, Clone, ValueEnum, PartialEq, Eq)]
121121
#[clap(rename_all = "lowercase")]
122122
pub(crate) enum OutputFormat {
123+
/// Output in Human Readable format.
124+
HumanReadable,
123125
/// Output in YAML format.
124126
Yaml,
125127
/// Output in JSON format.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
apiVersion: org.containers.bootc/v1alpha1
2+
kind: BootcHost
3+
metadata:
4+
name: host
5+
spec:
6+
image:
7+
image: quay.io/centos-bootc/centos-bootc:stream9
8+
transport: registry
9+
bootOrder: default
10+
status:
11+
staged: null
12+
booted:
13+
image:
14+
image:
15+
image: quay.io/centos-bootc/centos-bootc:stream9
16+
transport: registry
17+
version: stream9.20240807.0
18+
timestamp: null
19+
imageDigest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
20+
cachedUpdate: null
21+
incompatible: false
22+
pinned: false
23+
ostree:
24+
checksum: 439f6bd2e2361bee292c1f31840d798c5ac5ba76483b8021dc9f7b0164ac0f48
25+
deploySerial: 0
26+
rollback: null
27+
rollbackQueued: false
28+
type: bootcHost
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
apiVersion: org.containers.bootc/v1alpha1
2+
kind: BootcHost
3+
metadata:
4+
name: host
5+
spec:
6+
image:
7+
image: quay.io/centos-bootc/centos-bootc:stream9
8+
transport: registry
9+
bootOrder: default
10+
status:
11+
staged:
12+
image:
13+
image:
14+
image: quay.io/centos-bootc/centos-bootc:stream9
15+
transport: registry
16+
version: stream9.20240807.0
17+
timestamp: null
18+
imageDigest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
19+
cachedUpdate: null
20+
incompatible: false
21+
pinned: false
22+
store: ostreeContainer
23+
ostree:
24+
checksum: 05cbf6dcae32e7a1c5a0774a648a073a5834a305ca92204b53fb6c281fe49db1
25+
deploySerial: 0
26+
booted:
27+
image: null
28+
cachedUpdate: null
29+
incompatible: false
30+
pinned: false
31+
store: null
32+
ostree:
33+
checksum: f9fa3a553ceaaaf30cf85bfe7eed46a822f7b8fd7e14c1e3389cbc3f6d27f791
34+
deploySerial: 0
35+
rollback: null
36+
rollbackQueued: false
37+
type: null
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
apiVersion: org.containers.bootc/v1alpha1
2+
kind: BootcHost
3+
metadata:
4+
name: host
5+
spec:
6+
image: null
7+
bootOrder: default
8+
status:
9+
staged:
10+
image: null
11+
cachedUpdate: null
12+
incompatible: true
13+
pinned: false
14+
store: null
15+
ostree:
16+
checksum: 1c24260fdd1be20f72a4a97a75c582834ee3431fbb0fa8e4f482bb219d633a45
17+
deploySerial: 0
18+
booted:
19+
image: null
20+
cachedUpdate: null
21+
incompatible: false
22+
pinned: false
23+
store: null
24+
ostree:
25+
checksum: f9fa3a553ceaaaf30cf85bfe7eed46a822f7b8fd7e14c1e3389cbc3f6d27f791
26+
deploySerial: 0
27+
rollback: null
28+
rollbackQueued: false
29+
type: null
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
apiVersion: org.containers.bootc/v1alpha1
2+
kind: BootcHost
3+
metadata:
4+
name: host
5+
spec:
6+
image:
7+
image: quay.io/example/someimage:latest
8+
transport: registry
9+
signature: insecure
10+
status:
11+
staged:
12+
image:
13+
image:
14+
image: quay.io/example/someimage:latest
15+
transport: registry
16+
signature: insecure
17+
version: nightly
18+
timestamp: 2023-10-14T19:22:15Z
19+
imageDigest: sha256:16dc2b6256b4ff0d2ec18d2dbfb06d117904010c8cf9732cdb022818cf7a7566
20+
incompatible: false
21+
pinned: false
22+
ostree:
23+
checksum: 3c6dad657109522e0b2e49bf44b5420f16f0b438b5b9357e5132211cfbad135d
24+
deploySerial: 0
25+
booted:
26+
image:
27+
image:
28+
image: quay.io/example/someimage:latest
29+
transport: registry
30+
signature: insecure
31+
version: nightly
32+
timestamp: 2023-09-30T19:22:16Z
33+
imageDigest: sha256:736b359467c9437c1ac915acaae952aad854e07eb4a16a94999a48af08c83c34
34+
incompatible: false
35+
pinned: false
36+
ostree:
37+
checksum: 26836632adf6228d64ef07a26fd3efaf177104efd1f341a2cf7909a3e4e2c72c
38+
deploySerial: 0
39+
rollback: null
40+
isContainer: false
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
apiVersion: org.containers.bootc/v1alpha1
2+
kind: BootcHost
3+
metadata:
4+
name: host
5+
spec:
6+
image:
7+
image: quay.io/example/someimage:latest
8+
transport: registry
9+
signature: insecure
10+
status:
11+
staged:
12+
image:
13+
image:
14+
image: quay.io/example/someimage:latest
15+
transport: registry
16+
signature: insecure
17+
version: nightly
18+
timestamp: 2023-10-14T19:22:15Z
19+
imageDigest: sha256:16dc2b6256b4ff0d2ec18d2dbfb06d117904010c8cf9732cdb022818cf7a7566
20+
incompatible: false
21+
pinned: false
22+
ostree:
23+
checksum: 3c6dad657109522e0b2e49bf44b5420f16f0b438b5b9357e5132211cfbad135d
24+
deploySerial: 0
25+
booted: null
26+
rollback:
27+
image:
28+
image:
29+
image: quay.io/example/someimage:latest
30+
transport: registry
31+
signature: insecure
32+
version: nightly
33+
timestamp: 2023-09-30T19:22:16Z
34+
imageDigest: sha256:736b359467c9437c1ac915acaae952aad854e07eb4a16a94999a48af08c83c34
35+
incompatible: false
36+
pinned: false
37+
ostree:
38+
checksum: 26836632adf6228d64ef07a26fd3efaf177104efd1f341a2cf7909a3e4e2c72c
39+
deploySerial: 0
40+
isContainer: false

lib/src/status.rs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use std::collections::VecDeque;
2+
use std::io::IsTerminal;
3+
use std::io::Write;
24

35
use anyhow::{Context, Result};
46
use camino::Utf8Path;
@@ -305,19 +307,153 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
305307
let mut out = out.lock();
306308
let legacy_opt = if opts.json {
307309
OutputFormat::Json
310+
} else if std::io::stdout().is_terminal() {
311+
OutputFormat::HumanReadable
308312
} else {
309313
OutputFormat::Yaml
310314
};
311315
let format = opts.format.unwrap_or(legacy_opt);
312316
match format {
313317
OutputFormat::Json => serde_json::to_writer(&mut out, &host).map_err(anyhow::Error::new),
314318
OutputFormat::Yaml => serde_yaml::to_writer(&mut out, &host).map_err(anyhow::Error::new),
319+
OutputFormat::HumanReadable => human_readable_output(&mut out, &host),
315320
}
316321
.context("Writing to stdout")?;
317322

318323
Ok(())
319324
}
320325

326+
fn human_readable_output(mut out: impl Write, host: &Host) -> Result<()> {
327+
for (status_string, status) in [
328+
("staged", &host.status.staged),
329+
("booted", &host.status.booted),
330+
("rollback", &host.status.rollback),
331+
] {
332+
if let Some(host_status) = status {
333+
if let Some(image) = &host_status.image {
334+
writeln!(
335+
out,
336+
"Current {} image: {}",
337+
status_string, image.image.image
338+
)?;
339+
340+
let version = image
341+
.version
342+
.as_deref()
343+
.unwrap_or("No image version defined");
344+
let timestamp = image
345+
.timestamp
346+
.as_ref()
347+
.map(|t| t.to_string())
348+
.unwrap_or_else(|| "No timestamp present".to_owned());
349+
let transport = &image.image.transport;
350+
let digest = &image.image_digest;
351+
352+
writeln!(out, " Image version: {version} ({timestamp})")?;
353+
writeln!(out, " Image transport: {transport}")?;
354+
writeln!(out, " Image digest: {digest}")?;
355+
} else {
356+
writeln!(out, "Current {status_string} state is native ostree")?;
357+
}
358+
} else {
359+
writeln!(out, "No {status_string} image present")?;
360+
}
361+
}
362+
Ok(())
363+
}
364+
365+
fn human_status_from_spec_fixture(spec_fixture: &str) -> Result<String> {
366+
let host: Host = serde_yaml::from_str(spec_fixture).unwrap();
367+
let mut w = Vec::new();
368+
human_readable_output(&mut w, &host).unwrap();
369+
let w = String::from_utf8(w).unwrap();
370+
Ok(w)
371+
}
372+
373+
#[test]
374+
fn test_human_readable_base_spec() {
375+
// Tests Staged and Booted, null Rollback
376+
let w = human_status_from_spec_fixture(include_str!("fixtures/spec-staged-booted.yaml"))
377+
.expect("No spec found");
378+
let expected = indoc::indoc! { r"
379+
Current staged image: quay.io/example/someimage:latest
380+
Image version: nightly (2023-10-14 19:22:15 UTC)
381+
Image transport: registry
382+
Image digest: sha256:16dc2b6256b4ff0d2ec18d2dbfb06d117904010c8cf9732cdb022818cf7a7566
383+
Current booted image: quay.io/example/someimage:latest
384+
Image version: nightly (2023-09-30 19:22:16 UTC)
385+
Image transport: registry
386+
Image digest: sha256:736b359467c9437c1ac915acaae952aad854e07eb4a16a94999a48af08c83c34
387+
No rollback image present
388+
"};
389+
similar_asserts::assert_eq!(w, expected);
390+
}
391+
392+
#[test]
393+
fn test_human_readable_rfe_spec() {
394+
// Basic rhel for edge bootc install with nothing
395+
let w =
396+
human_status_from_spec_fixture(include_str!("fixtures/spec-rfe-ostree-deployment.yaml"))
397+
.expect("No spec found");
398+
let expected = indoc::indoc! { r"
399+
Current staged state is native ostree
400+
Current booted state is native ostree
401+
No rollback image present
402+
"};
403+
similar_asserts::assert_eq!(w, expected);
404+
}
405+
406+
#[test]
407+
fn test_human_readable_staged_spec() {
408+
// staged image, no boot/rollback
409+
let w = human_status_from_spec_fixture(include_str!("fixtures/spec-ostree-to-bootc.yaml"))
410+
.expect("No spec found");
411+
let expected = indoc::indoc! { r"
412+
Current staged image: quay.io/centos-bootc/centos-bootc:stream9
413+
Image version: stream9.20240807.0 (No timestamp present)
414+
Image transport: registry
415+
Image digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
416+
Current booted state is native ostree
417+
No rollback image present
418+
"};
419+
similar_asserts::assert_eq!(w, expected);
420+
}
421+
422+
#[test]
423+
fn test_human_readable_booted_spec() {
424+
// booted image, no staged/rollback
425+
let w = human_status_from_spec_fixture(include_str!("fixtures/spec-only-booted.yaml"))
426+
.expect("No spec found");
427+
let expected = indoc::indoc! { r"
428+
No staged image present
429+
Current booted image: quay.io/centos-bootc/centos-bootc:stream9
430+
Image version: stream9.20240807.0 (No timestamp present)
431+
Image transport: registry
432+
Image digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
433+
No rollback image present
434+
"};
435+
similar_asserts::assert_eq!(w, expected);
436+
}
437+
438+
#[test]
439+
fn test_human_readable_staged_rollback_spec() {
440+
// staged/rollback image, no booted
441+
let w = human_status_from_spec_fixture(include_str!("fixtures/spec-staged-rollback.yaml"))
442+
.expect("No spec found");
443+
let expected = indoc::indoc! { r"
444+
Current staged image: quay.io/example/someimage:latest
445+
Image version: nightly (2023-10-14 19:22:15 UTC)
446+
Image transport: registry
447+
Image digest: sha256:16dc2b6256b4ff0d2ec18d2dbfb06d117904010c8cf9732cdb022818cf7a7566
448+
No booted image present
449+
Current rollback image: quay.io/example/someimage:latest
450+
Image version: nightly (2023-09-30 19:22:16 UTC)
451+
Image transport: registry
452+
Image digest: sha256:736b359467c9437c1ac915acaae952aad854e07eb4a16a94999a48af08c83c34
453+
"};
454+
similar_asserts::assert_eq!(w, expected);
455+
}
456+
321457
#[test]
322458
fn test_convert_signatures() {
323459
use std::str::FromStr;

0 commit comments

Comments
 (0)