Skip to content

feat(status): display pinned deployments #1285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions lib/src/fixtures/spec-booted-pinned.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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
architecture: arm64
version: stream9.20240807.0
timestamp: null
imageDigest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38
cachedUpdate: null
incompatible: false
pinned: true
ostree:
checksum: 439f6bd2e2361bee292c1f31840d798c5ac5ba76483b8021dc9f7b0164ac0f48
deploySerial: 0
stateroot: default
otherDeployments:
- image:
image:
image: quay.io/centos-bootc/centos-bootc:stream9
transport: registry
version: stream9.20240807.0
timestamp: null
imageDigest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b37
architecture: arm64
cachedUpdate: null
incompatible: false
pinned: true
ostree:
checksum: 99b2cc3b6edce9ebaef6a6076effa5ee3e1dcff3523016ffc94a1b27c6c67e12
deploySerial: 0
stateroot: default
rollback: null
rollbackQueued: false
type: bootcHost
4 changes: 4 additions & 0 deletions lib/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ pub struct HostStatus {
pub booted: Option<BootEntry>,
/// The previously booted image
pub rollback: Option<BootEntry>,
/// Other deployments (i.e. pinned)
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub other_deployments: Vec<BootEntry>,
/// Set to true if the rollback entry is queued for the next boot.
#[serde(default)]
pub rollback_queued: bool,
Expand Down
78 changes: 65 additions & 13 deletions lib/src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,12 @@ pub(crate) fn get_status(
.map(|d| boot_entry_from_deployment(sysroot, d))
.transpose()
.context("Rollback deployment")?;
let other_deployments = deployments
.other
.iter()
.map(|d| boot_entry_from_deployment(sysroot, d))
.collect::<Result<Vec<_>>>()
.context("Other deployments")?;
let spec = staged
.as_ref()
.or(booted.as_ref())
Expand All @@ -280,6 +286,7 @@ pub(crate) fn get_status(
staged,
booted,
rollback,
other_deployments,
rollback_queued,
ty,
};
Expand Down Expand Up @@ -362,7 +369,7 @@ fn write_row_name(mut out: impl Write, s: &str, prefix_len: usize) -> Result<()>
/// Write the data for a container image based status.
fn human_render_slot(
mut out: impl Write,
slot: Slot,
slot: Option<Slot>,
entry: &crate::spec::BootEntry,
image: &crate::spec::ImageStatus,
) -> Result<()> {
Expand All @@ -376,9 +383,10 @@ fn human_render_slot(
Cow::Owned(format!("{transport}:{imagename}"))
};
let prefix = match slot {
Slot::Staged => " Staged image".into(),
Slot::Booted => format!("{} Booted image", crate::glyph::Glyph::BlackCircle),
Slot::Rollback => " Rollback image".into(),
Some(Slot::Staged) => " Staged image".into(),
Some(Slot::Booted) => format!("{} Booted image", crate::glyph::Glyph::BlackCircle),
Some(Slot::Rollback) => " Rollback image".into(),
_ => " Other image".into(),
};
let prefix_len = prefix.chars().count();
writeln!(out, "{prefix}: {imageref}")?;
Expand Down Expand Up @@ -409,6 +417,11 @@ fn human_render_slot(
writeln!(out, "{timestamp}")?;
}

if entry.pinned {
write_row_name(&mut out, "Pinned", prefix_len)?;
writeln!(out, "yes")?;
}

tracing::debug!("pinned={}", entry.pinned);

Ok(())
Expand All @@ -417,20 +430,27 @@ fn human_render_slot(
/// Output a rendering of a non-container boot entry.
fn human_render_slot_ostree(
mut out: impl Write,
slot: Slot,
slot: Option<Slot>,
entry: &crate::spec::BootEntry,
ostree_commit: &str,
) -> Result<()> {
// TODO consider rendering more ostree stuff here like rpm-ostree status does
let prefix = match slot {
Slot::Staged => " Staged ostree".into(),
Slot::Booted => format!("{} Booted ostree", crate::glyph::Glyph::BlackCircle),
Slot::Rollback => " Rollback ostree".into(),
Some(Slot::Staged) => " Staged ostree".into(),
Some(Slot::Booted) => format!("{} Booted ostree", crate::glyph::Glyph::BlackCircle),
Some(Slot::Rollback) => " Rollback ostree".into(),
_ => " Other ostree".into(),
};
let prefix_len = prefix.len();
writeln!(out, "{prefix}")?;
write_row_name(&mut out, "Commit", prefix_len)?;
writeln!(out, "{ostree_commit}")?;

if entry.pinned {
write_row_name(&mut out, "Pinned", prefix_len)?;
writeln!(out, "yes")?;
}

tracing::debug!("pinned={}", entry.pinned);
Ok(())
}
Expand All @@ -449,14 +469,27 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host) -> Result<()>
writeln!(out)?;
}
if let Some(image) = &host_status.image {
human_render_slot(&mut out, slot_name, host_status, image)?;
human_render_slot(&mut out, Some(slot_name), host_status, image)?;
} else if let Some(ostree) = host_status.ostree.as_ref() {
human_render_slot_ostree(&mut out, slot_name, host_status, &ostree.checksum)?;
human_render_slot_ostree(&mut out, Some(slot_name), host_status, &ostree.checksum)?;
} else {
writeln!(out, "Current {slot_name} state is unknown")?;
}
}
}

if !host.status.other_deployments.is_empty() {
for entry in &host.status.other_deployments {
writeln!(out)?;

if let Some(image) = &entry.image {
human_render_slot(&mut out, None, entry, image)?;
} else if let Some(ostree) = entry.ostree.as_ref() {
human_render_slot_ostree(&mut out, None, entry, &ostree.checksum)?;
}
}
}

Ok(())
}

Expand Down Expand Up @@ -491,7 +524,7 @@ mod tests {
Staged image: quay.io/example/someimage:latest
Digest: sha256:16dc2b6256b4ff0d2ec18d2dbfb06d117904010c8cf9732cdb022818cf7a7566 (arm64)
Version: nightly (2023-10-14T19:22:15Z)

● Booted image: quay.io/example/someimage:latest
Digest: sha256:736b359467c9437c1ac915acaae952aad854e07eb4a16a94999a48af08c83c34 (arm64)
Version: nightly (2023-09-30T19:22:16Z)
Expand All @@ -509,7 +542,7 @@ mod tests {
let expected = indoc::indoc! { r"
Staged ostree
Commit: 1c24260fdd1be20f72a4a97a75c582834ee3431fbb0fa8e4f482bb219d633a45

● Booted ostree
Commit: f9fa3a553ceaaaf30cf85bfe7eed46a822f7b8fd7e14c1e3389cbc3f6d27f791
"};
Expand All @@ -525,7 +558,7 @@ mod tests {
Staged image: quay.io/centos-bootc/centos-bootc:stream9
Digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38 (s390x)
Version: stream9.20240807.0

● Booted ostree
Commit: f9fa3a553ceaaaf30cf85bfe7eed46a822f7b8fd7e14c1e3389cbc3f6d27f791
"};
Expand Down Expand Up @@ -589,4 +622,23 @@ mod tests {
Some(ImageSignature::OstreeRemote("fedora".into()))
);
}

#[test]
fn test_human_readable_booted_pinned_spec() {
// booted image, no staged/rollback
let w = human_status_from_spec_fixture(include_str!("fixtures/spec-booted-pinned.yaml"))
.expect("No spec found");
let expected = indoc::indoc! { r"
● Booted image: quay.io/centos-bootc/centos-bootc:stream9
Digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b38 (arm64)
Version: stream9.20240807.0
Pinned: yes

Other image: quay.io/centos-bootc/centos-bootc:stream9
Digest: sha256:47e5ed613a970b6574bfa954ab25bb6e85656552899aa518b5961d9645102b37 (arm64)
Version: stream9.20240807.0
Pinned: yes
"};
similar_asserts::assert_eq!(w, expected);
}
}