Skip to content

Commit 42d816c

Browse files
cli: Implement bootc status for composefs native booted system
Signed-off-by: Pragyan Poudyal <[email protected]>
1 parent 92dba1c commit 42d816c

File tree

1 file changed

+142
-40
lines changed

1 file changed

+142
-40
lines changed

crates/lib/src/status.rs

Lines changed: 142 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,27 @@ use std::collections::VecDeque;
33
use std::io::IsTerminal;
44
use std::io::Read;
55
use std::io::Write;
6+
use std::str::FromStr;
67

78
use anyhow::{Context, Result};
9+
use bootc_utils::try_deserialize_timestamp;
810
use canon_json::CanonJsonSerialize;
11+
use cap_std_ext::cap_std;
912
use fn_error_context::context;
1013
use ostree::glib;
1114
use ostree_container::OstreeImageReference;
1215
use ostree_ext::container as ostree_container;
16+
use ostree_ext::container::deploy::ORIGIN_CONTAINER;
1317
use ostree_ext::container_utils::composefs_booted;
1418
use ostree_ext::container_utils::ostree_booted;
19+
use ostree_ext::containers_image_proxy;
1520
use ostree_ext::keyfileext::KeyFileExt;
1621
use ostree_ext::oci_spec;
1722
use ostree_ext::ostree;
23+
use tokio::io::AsyncReadExt;
1824

1925
use crate::cli::OutputFormat;
26+
use crate::spec::ImageStatus;
2027
use crate::spec::{BootEntry, BootOrder, Host, HostSpec, HostStatus, HostType};
2128
use crate::spec::{ImageReference, ImageSignature};
2229
use crate::store::{CachedImageStatus, ContainerImageStore, Storage};
@@ -286,6 +293,140 @@ pub(crate) fn get_status(
286293
Ok((deployments, host))
287294
}
288295

296+
#[context("Getting composefs deployment metadata")]
297+
async fn boot_entry_from_composefs_deployment(
298+
origin: tini::Ini,
299+
booted: &String,
300+
) -> Result<BootEntry> {
301+
let image = match origin.get::<String>("origin", ORIGIN_CONTAINER) {
302+
Some(img_name_from_config) => {
303+
let ostree_img_ref = OstreeImageReference::from_str(&img_name_from_config)?;
304+
let imgref = ostree_img_ref.imgref.to_string();
305+
306+
let img_ref = ImageReference::from(ostree_img_ref);
307+
308+
let config = containers_image_proxy::ImageProxyConfig::default();
309+
let proxy = containers_image_proxy::ImageProxy::new_with_config(config).await?;
310+
311+
// The image might've been removed, so don't error if we can't get the image manifest
312+
let (image_digest, version, architecture, created_at) =
313+
match proxy.open_image(&imgref).await.context("Opening image") {
314+
Ok(img) => {
315+
let (_, manifest) = proxy.fetch_manifest(&img).await?;
316+
let (mut reader, driver) =
317+
proxy.get_descriptor(&img, manifest.config()).await?;
318+
319+
let mut buf = Vec::with_capacity(manifest.config().size() as usize);
320+
buf.resize(manifest.config().size() as usize, 0);
321+
reader.read_exact(&mut buf).await?;
322+
driver.await?;
323+
324+
let config: oci_spec::image::ImageConfiguration =
325+
serde_json::from_slice(&buf)?;
326+
327+
let digest = manifest.config().digest().to_string();
328+
let arch = config.architecture().to_string();
329+
let created = config.created().clone();
330+
let version = manifest
331+
.annotations()
332+
.as_ref()
333+
.and_then(|a| a.get(oci_spec::image::ANNOTATION_VERSION).cloned());
334+
335+
(digest, version, arch, created)
336+
}
337+
338+
Err(e) => {
339+
tracing::debug!("Failed to open image {img_ref}, because {e:?}");
340+
("".into(), None, "".into(), None)
341+
}
342+
};
343+
344+
let timestamp = if let Some(created_at) = created_at {
345+
try_deserialize_timestamp(&created_at)
346+
} else {
347+
None
348+
};
349+
350+
let image_status = ImageStatus {
351+
image: img_ref,
352+
version,
353+
timestamp,
354+
image_digest,
355+
architecture,
356+
};
357+
358+
Some(image_status)
359+
}
360+
361+
// Wasn't booted using a container image. Do nothing
362+
None => None,
363+
};
364+
365+
let e = BootEntry {
366+
image,
367+
cached_update: None,
368+
incompatible: false,
369+
pinned: false,
370+
store: None,
371+
ostree: None,
372+
composefs: Some(crate::spec::BootEntryComposefs {
373+
verity: booted.into(),
374+
}),
375+
};
376+
377+
return Ok(e);
378+
}
379+
380+
#[context("Getting composefs deployment status")]
381+
pub(crate) async fn composefs_deployment_status() -> Result<Host> {
382+
let cmdline = crate::kernel::parse_cmdline()?;
383+
let booted = cmdline.iter().find_map(|x| x.strip_prefix("composefs="));
384+
385+
let Some(booted) = booted else {
386+
anyhow::bail!("Failed to find composefs parameter in kernel cmdline");
387+
};
388+
389+
let sysroot = cap_std::fs::Dir::open_ambient_dir("/sysroot", cap_std::ambient_authority())
390+
.context("Opening sysroot")?;
391+
let deployments = sysroot
392+
.read_dir("state/deploy")
393+
.context("Reading sysroot state/deploy")?;
394+
395+
let host_spec = HostSpec {
396+
image: None,
397+
boot_order: BootOrder::Default,
398+
};
399+
400+
let mut host = Host::new(host_spec);
401+
402+
for depl in deployments {
403+
let depl = depl?;
404+
405+
let depl_file_name = depl.file_name();
406+
let depl_file_name = depl_file_name.to_string_lossy();
407+
408+
// read the origin file
409+
let config = depl
410+
.open_dir()
411+
.with_context(|| format!("Failed to open {depl_file_name}"))?
412+
.read_to_string(format!("{depl_file_name}.origin"))
413+
.with_context(|| format!("Reading file {depl_file_name}.origin"))?;
414+
415+
let ini = tini::Ini::from_string(&config)
416+
.with_context(|| format!("Failed to parse file {depl_file_name}.origin as ini"))?;
417+
418+
let boot_entry = boot_entry_from_composefs_deployment(ini, &booted.into()).await?;
419+
420+
if depl.file_name() == booted {
421+
host.spec.image = boot_entry.image.as_ref().map(|x| x.image.clone());
422+
host.status.booted = Some(boot_entry);
423+
continue;
424+
}
425+
}
426+
427+
Ok(host)
428+
}
429+
289430
/// Implementation of the `bootc status` CLI command.
290431
#[context("Status")]
291432
pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
@@ -300,46 +441,7 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
300441
let (_deployments, host) = get_status(&sysroot, booted_deployment.as_ref())?;
301442
host
302443
} else if composefs_booted()? {
303-
let dir_contents = std::fs::read_dir("/sysroot/composefs/images")?;
304-
305-
let host_spec = HostSpec {
306-
image: Some(ImageReference {
307-
image: "".into(),
308-
transport: "".into(),
309-
signature: None,
310-
}),
311-
boot_order: BootOrder::Default,
312-
};
313-
314-
let mut host = Host::new(host_spec);
315-
316-
let cmdline = crate::kernel::parse_cmdline()?;
317-
let booted = cmdline.iter().find_map(|x| x.strip_prefix("composefs="));
318-
319-
let Some(booted) = booted else {
320-
anyhow::bail!("Failed to find composefs parameter in kernel cmdline");
321-
};
322-
323-
host.status = HostStatus {
324-
staged: None,
325-
booted: Some(BootEntry {
326-
image: None,
327-
cached_update: None,
328-
incompatible: false,
329-
pinned: false,
330-
store: None,
331-
ostree: None,
332-
composefs: Some(crate::spec::BootEntryComposefs {
333-
verity: booted.into(),
334-
}),
335-
}),
336-
other_deployments: vec![],
337-
rollback: None,
338-
rollback_queued: false,
339-
ty: None,
340-
};
341-
342-
host
444+
composefs_deployment_status().await?
343445
} else {
344446
Default::default()
345447
};

0 commit comments

Comments
 (0)