From 69a7f370e4a27a6bd5d4cab649761e9b9acdfcd9 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 19 Dec 2023 20:46:36 -0500 Subject: [PATCH] Add `HostType`, change `status` to always succeed Closes: https://github.com/containers/bootc/discussions/242 Basically it'd be useful for folks to be able to run `bootc status --json` and have that always work - if we're not on a bootc-booted system then we just output null data. Specifically we drop the `isContainer` field and replace it with an optional non-exhaustive enumeration. For anything we don't know about we just output `None` i.e. `null` in JSON. Signed-off-by: Colin Walters --- lib/src/privtests.rs | 5 +++-- lib/src/spec.rs | 27 +++++++++++++++++++++++---- lib/src/status.rs | 33 ++++++++++++++++++--------------- tests/kolainst/basic | 7 +++++++ 4 files changed, 51 insertions(+), 21 deletions(-) diff --git a/lib/src/privtests.rs b/lib/src/privtests.rs index e58c060ba..d1290778a 100644 --- a/lib/src/privtests.rs +++ b/lib/src/privtests.rs @@ -6,6 +6,8 @@ use fn_error_context::context; use rustix::fd::AsFd; use xshell::{cmd, Shell}; +use crate::spec::HostType; + use super::cli::TestingOpts; use super::spec::Host; @@ -100,10 +102,9 @@ pub(crate) fn impl_run_host() -> Result<()> { #[context("Container tests")] pub(crate) fn impl_run_container() -> Result<()> { - assert!(ostree_ext::container_utils::is_ostree_container()?); let sh = Shell::new()?; let host: Host = serde_yaml::from_str(&cmd!(sh, "bootc status").read()?)?; - assert!(host.status.is_container); + assert!(matches!(host.status.ty, None)); println!("ok status"); for c in ["upgrade", "update"] { diff --git a/lib/src/spec.rs b/lib/src/spec.rs index b13170669..78e73c2a7 100644 --- a/lib/src/spec.rs +++ b/lib/src/spec.rs @@ -7,6 +7,8 @@ use crate::k8sapitypes; const API_VERSION: &str = "org.containers.bootc/v1alpha1"; const KIND: &str = "BootcHost"; +/// The default object name we use; there's only one. +pub(crate) const OBJECT_NAME: &str = "host"; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] @@ -94,6 +96,16 @@ pub struct BootEntry { pub ostree: Option, } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +/// The detected type of running system. Note that this is not exhaustive +/// and new variants may be added in the future. +pub enum HostType { + /// The current system is deployed in a bootc compatible way. + BootcHost, +} + /// The status of the host system #[derive(Debug, Clone, Serialize, Default, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "camelCase")] @@ -105,15 +117,16 @@ pub struct HostStatus { /// The previously booted image pub rollback: Option, - /// Whether or not the current system state is an ostree-based container - pub is_container: bool, + /// The detected type of system + #[serde(rename = "type")] + pub ty: Option, } impl Host { /// Create a new host - pub fn new(name: &str, spec: HostSpec) -> Self { + pub fn new(spec: HostSpec) -> Self { let metadata = k8sapitypes::ObjectMeta { - name: Some(name.to_owned()), + name: Some(OBJECT_NAME.to_owned()), ..Default::default() }; Self { @@ -128,6 +141,12 @@ impl Host { } } +impl Default for Host { + fn default() -> Self { + Self::new(Default::default()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lib/src/status.rs b/lib/src/status.rs index 838ea5740..f882c4917 100644 --- a/lib/src/status.rs +++ b/lib/src/status.rs @@ -1,8 +1,9 @@ use std::collections::VecDeque; -use crate::spec::{BootEntry, Host, HostSpec, HostStatus, ImageStatus}; +use crate::spec::{BootEntry, Host, HostSpec, HostStatus, HostType, ImageStatus}; use crate::spec::{ImageReference, ImageSignature}; use anyhow::{Context, Result}; +use camino::Utf8Path; use fn_error_context::context; use ostree::glib; use ostree_container::OstreeImageReference; @@ -12,8 +13,6 @@ use ostree_ext::oci_spec; use ostree_ext::ostree; use ostree_ext::sysroot::SysrootLock; -const OBJECT_NAME: &str = "host"; - impl From for ImageSignature { fn from(sig: ostree_container::SignatureSource) -> Self { use ostree_container::SignatureSource; @@ -223,8 +222,6 @@ pub(crate) fn get_status( other, }; - let is_container = ostree_ext::container_utils::is_ostree_container()?; - let staged = deployments .staged .as_ref() @@ -250,12 +247,24 @@ pub(crate) fn get_status( image: Some(img.image.clone()), }) .unwrap_or_default(); - let mut host = Host::new(OBJECT_NAME, spec); + + let ty = if booted + .as_ref() + .map(|b| b.image.is_some()) + .unwrap_or_default() + { + // We're only of type BootcHost if we booted via container image + Some(HostType::BootcHost) + } else { + None + }; + + let mut host = Host::new(spec); host.status = HostStatus { staged, booted, rollback, - is_container, + ty, }; Ok((deployments, host)) } @@ -263,14 +272,8 @@ pub(crate) fn get_status( /// Implementation of the `bootc status` CLI command. #[context("Status")] pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> { - let host = if ostree_ext::container_utils::is_ostree_container()? { - let status = HostStatus { - is_container: true, - ..Default::default() - }; - let mut r = Host::new(OBJECT_NAME, HostSpec { image: None }); - r.status = status; - r + let host = if !Utf8Path::new("/run/ostree-booted").try_exists()? { + Default::default() } else { crate::cli::require_root()?; let sysroot = super::cli::get_locked_sysroot().await?; diff --git a/tests/kolainst/basic b/tests/kolainst/basic index f002a14b8..612a3a1c6 100755 --- a/tests/kolainst/basic +++ b/tests/kolainst/basic @@ -19,6 +19,13 @@ case "${AUTOPKGTEST_REBOOT_MARK:-}" in echo "booted into $image" echo "ok status test" + host_ty=$(jq -r '.status.type' < status.json) + test "${host_ty}" = "bootcHost" + # Now fake things out with an empty /run + unshare -m /bin/sh -c 'mount -t tmpfs tmpfs /run; bootc status --json > status-no-run.json' + host_ty_norun=$(jq -r '.status.type' < status-no-run.json) + test "${host_ty_norun}" = "null" + test "null" = $(jq '.status.staged' < status.json) # Should be a no-op bootc update