Skip to content

Commit

Permalink
cli: Adds human readable output format to bootc status
Browse files Browse the repository at this point in the history
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.

Signed-off-by: djach7 <[email protected]>
  • Loading branch information
djach7 committed Sep 9, 2024
1 parent b165c19 commit e0de226
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 12 deletions.
28 changes: 28 additions & 0 deletions lib/src/fixtures/spec-only-booted.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
apiVersion: org.containers.bootc/v1alpha1
kind: BootcHost
metadata:
name: host
spec:
image:
image: quay.io/centos-bootc/centos-bootc:stream9
transport: registry
bootOrder: default
status:
staged: null
booted:
image:
image:
image: quay.io/centos-bootc/centos-bootc:stream9
transport: registry
version: stream9.20240807.0
timestamp: null
imageDigest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
cachedUpdate: null
incompatible: false
pinned: false
ostree:
checksum: 439f6bd2e2361bee292c1f31840d798c5ac5ba76483b8021dc9f7b0164ac0f48
deploySerial: 0
rollback: null
rollbackQueued: false
type: bootcHost
37 changes: 37 additions & 0 deletions lib/src/fixtures/spec-ostree-to-bootc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
apiVersion: org.containers.bootc/v1alpha1
kind: BootcHost
metadata:
name: host
spec:
image:
image: quay.io/centos-bootc/centos-bootc:stream9
transport: registry
bootOrder: default
status:
staged:
image:
image:
image: quay.io/centos-bootc/centos-bootc:stream9
transport: registry
version: stream9.20240807.0
timestamp: null
imageDigest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
cachedUpdate: null
incompatible: false
pinned: false
store: ostreeContainer
ostree:
checksum: 05cbf6dcae32e7a1c5a0774a648a073a5834a305ca92204b53fb6c281fe49db1
deploySerial: 0
booted:
image: null
cachedUpdate: null
incompatible: false
pinned: false
store: null
ostree:
checksum: f9fa3a553ceaaaf30cf85bfe7eed46a822f7b8fd7e14c1e3389cbc3f6d27f791
deploySerial: 0
rollback: null
rollbackQueued: false
type: null
29 changes: 29 additions & 0 deletions lib/src/fixtures/spec-rfe-ostree-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apiVersion: org.containers.bootc/v1alpha1
kind: BootcHost
metadata:
name: host
spec:
image: null
bootOrder: default
status:
staged:
image: null
cachedUpdate: null
incompatible: true
pinned: false
store: null
ostree:
checksum: 1c24260fdd1be20f72a4a97a75c582834ee3431fbb0fa8e4f482bb219d633a45
deploySerial: 0
booted:
image: null
cachedUpdate: null
incompatible: false
pinned: false
store: null
ostree:
checksum: f9fa3a553ceaaaf30cf85bfe7eed46a822f7b8fd7e14c1e3389cbc3f6d27f791
deploySerial: 0
rollback: null
rollbackQueued: false
type: null
135 changes: 123 additions & 12 deletions lib/src/status.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::VecDeque;
use std::io::IsTerminal;
use std::io::Write;

use anyhow::{Context, Result};
use camino::Utf8Path;
Expand Down Expand Up @@ -305,31 +306,141 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
let mut out = out.lock();
let legacy_opt = if opts.json {
OutputFormat::Json
} else if std::io::stdout().is_terminal() {
OutputFormat::HumanReadable
} else {
if std::io::stdout().is_terminal() {
OutputFormat::HumanReadable
} else {
OutputFormat::Yaml
}
OutputFormat::Yaml
};
let format = opts.format.unwrap_or(legacy_opt);
match format {
OutputFormat::Json => serde_json::to_writer(&mut out, &host).map_err(anyhow::Error::new),
OutputFormat::Yaml => serde_yaml::to_writer(&mut out, &host).map_err(anyhow::Error::new),
OutputFormat::HumanReadable => {
if let Some(booted) = host.status.booted {
println!("Current deployment image: {:?}", booted.image.unwrap().image.image);
OutputFormat::HumanReadable => human_readable_output(&mut out, &host),
}
.context("Writing to stdout")?;

Ok(())
}

fn human_readable_output(mut out: impl Write, host: &Host) -> Result<()> {
for (status_string, status) in [
("staged", &host.status.staged),
("booted", &host.status.booted),
("rollback", &host.status.rollback),
] {
if let Some(host_status) = status {
if let Some(image) = &host_status.image {
writeln!(
out,
"Current {} image: {}",
status_string, image.image.image
)?;

let version = image
.version
.as_deref()
.unwrap_or("No image version defined");
let timestamp = image
.timestamp
.as_ref()
.map(|t| t.to_string())
.unwrap_or_else(|| "No timestamp present".to_owned());
let transport = &image.image.transport;
let digest = &image.image_digest;

writeln!(out, " Image version: {version} ({timestamp})")?;
writeln!(out, " Image transport: {transport}")?;
writeln!(out, " Image digest: {digest}")?;
} else {
println!("Not on a bootc host");
writeln!(out, "No {status_string} image present, checksum defined")?;
}
Ok(())
} else {
writeln!(out, "No {status_string} image present")?;
}
}
.context("Writing to stdout")?;

Ok(())
}

#[test]
fn test_human_readable_base_spec() {
// Tests Staged and Booted, null Rollback
let spec_fixture: &str = include_str!("fixtures/spec.yaml");
let host: Host = serde_yaml::from_str(spec_fixture).unwrap();
let mut w = Vec::new();
human_readable_output(&mut w, &host).unwrap();
let w = String::from_utf8(w).unwrap();
dbg!(&w);
let expected = indoc::indoc! { r"
Current staged image: quay.io/example/someimage:latest
Image version: nightly (2023-10-14 19:22:15 UTC)
Image transport: registry
Image digest: sha256:16dc2b6256b4ff0d2ec18d2dbfb06d117904010c8cf9732cdb022818cf7a7566
Current booted image: quay.io/example/someimage:latest
Image version: nightly (2023-09-30 19:22:16 UTC)
Image transport: registry
Image digest: sha256:736b359467c9437c1ac915acaae952aad854e07eb4a16a94999a48af08c83c34
No rollback image present
"};
similar_asserts::assert_eq!(w, expected);
}

#[test]
fn test_human_readable_rfe_spec() {
// Basic rhel for edge bootc install with nothing
let spec_fixture = include_str!("fixtures/spec-rfe-ostree-deployment.yaml");
let host = serde_yaml::from_str(spec_fixture).unwrap();
let mut w = Vec::new();
human_readable_output(&mut w, &host).unwrap();
let w = String::from_utf8(w).unwrap();
dbg!(&w);
let expected = indoc::indoc! { r"
No staged image present, checksum defined
No booted image present, checksum defined
No rollback image present
"};
similar_asserts::assert_eq!(w, expected);
}

#[test]
fn test_human_readable_staged_spec() {
// staged image, no boot/rollback
let spec_fixture: &str = include_str!("fixtures/spec-ostree-to-bootc.yaml");
let host: Host = serde_yaml::from_str(spec_fixture).unwrap();
let mut w = Vec::new();
human_readable_output(&mut w, &host).unwrap();
let w = String::from_utf8(w).unwrap();
dbg!(&w);
let expected = indoc::indoc! { r"
Current staged image: quay.io/centos-bootc/centos-bootc:stream9
Image version: stream9.20240807.0 (No timestamp present)
Image transport: registry
Image digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
No booted image present, checksum defined
No rollback image present
"};
similar_asserts::assert_eq!(w, expected);
}

#[test]
fn test_human_readable_booted_spec() {
// booted image, no staged/rollback
let spec_fixture: &str = include_str!("fixtures/spec-only-booted.yaml");
let host: Host = serde_yaml::from_str(spec_fixture).unwrap();
let mut w = Vec::new();
human_readable_output(&mut w, &host).unwrap();
let w = String::from_utf8(w).unwrap();
dbg!(&w);
let expected = indoc::indoc! { r"
No staged image present
Current booted image: quay.io/centos-bootc/centos-bootc:stream9
Image version: stream9.20240807.0 (No timestamp present)
Image transport: registry
Image digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
No rollback image present
"};
similar_asserts::assert_eq!(w, expected);
}

#[test]
fn test_convert_signatures() {
use std::str::FromStr;
Expand Down

0 comments on commit e0de226

Please sign in to comment.