Skip to content

Commit 8d9b923

Browse files
cli: Implement bootc status for composefs native booted system
Signed-off-by: Pragyan Poudyal <[email protected]>
1 parent 8e68e45 commit 8d9b923

File tree

1 file changed

+141
-39
lines changed

1 file changed

+141
-39
lines changed

lib/src/status.rs

Lines changed: 141 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,25 @@ 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 cap_std_ext::cap_std;
810
use fn_error_context::context;
911
use ostree::glib;
1012
use ostree_container::OstreeImageReference;
1113
use ostree_ext::container as ostree_container;
14+
use ostree_ext::container::deploy::ORIGIN_CONTAINER;
1215
use ostree_ext::container_utils::composefs_booted;
1316
use ostree_ext::container_utils::ostree_booted;
17+
use ostree_ext::containers_image_proxy;
1418
use ostree_ext::keyfileext::KeyFileExt;
1519
use ostree_ext::oci_spec;
1620
use ostree_ext::ostree;
21+
use tokio::io::AsyncReadExt;
1722

1823
use crate::cli::OutputFormat;
24+
use crate::spec::ImageStatus;
1925
use crate::spec::{BootEntry, BootOrder, Host, HostSpec, HostStatus, HostType};
2026
use crate::spec::{ImageReference, ImageSignature};
2127
use crate::store::{CachedImageStatus, ContainerImageStore, Storage};
@@ -287,6 +293,140 @@ pub(crate) fn get_status(
287293
Ok((deployments, host))
288294
}
289295

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+
290430
/// Implementation of the `bootc status` CLI command.
291431
#[context("Status")]
292432
pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
@@ -301,45 +441,7 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
301441
let (_deployments, host) = get_status(&sysroot, booted_deployment.as_ref())?;
302442
host
303443
} else if composefs_booted()? {
304-
let dir_contents = std::fs::read_dir("/sysroot/composefs/images")?;
305-
306-
let host_spec = HostSpec {
307-
image: Some(ImageReference {
308-
image: "".into(),
309-
transport: "".into(),
310-
signature: None,
311-
}),
312-
boot_order: BootOrder::Default,
313-
};
314-
315-
let mut host = Host::new(host_spec);
316-
317-
let cmdline = crate::kernel::parse_cmdline()?;
318-
let booted = cmdline.iter().find_map(|x| x.strip_prefix("composefs="));
319-
320-
let Some(booted) = booted else {
321-
anyhow::bail!("Failed to find composefs parameter in kernel cmdline");
322-
};
323-
324-
host.status = HostStatus {
325-
staged: None,
326-
booted: Some(BootEntry {
327-
image: None,
328-
cached_update: None,
329-
incompatible: false,
330-
pinned: false,
331-
store: None,
332-
ostree: None,
333-
composefs: Some(crate::spec::BootEntryComposefs {
334-
verity: booted.into(),
335-
}),
336-
}),
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)