bootc_lib/install/
aleph.rs

1use std::collections::BTreeMap;
2
3use anyhow::{Context as _, Result};
4use canon_json::CanonJsonSerialize as _;
5use cap_std_ext::{cap_std::fs::Dir, dirext::CapStdExtDirExt as _};
6use fn_error_context::context;
7use ostree_ext::{container as ostree_container, oci_spec};
8use serde::Serialize;
9
10use super::SELinuxFinalState;
11
12/// Path to initially deployed version information
13pub(crate) const BOOTC_ALEPH_PATH: &str = ".bootc-aleph.json";
14
15/// The "aleph" version information is injected into /root/.bootc-aleph.json
16/// and contains the image ID that was initially used to install.  This can
17/// be used to trace things like the specific version of `mkfs.ext4` or
18/// kernel version that was used.
19#[derive(Debug, Serialize)]
20#[serde(rename_all = "kebab-case")]
21pub(crate) struct InstallAleph {
22    /// Digested pull spec for installed image
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub(crate) image: Option<String>,
25    /// The manifest digest of the installed image
26    pub(crate) digest: String,
27    /// The target image reference, used for subsequent updates
28    #[serde(rename = "target-image")]
29    pub(crate) target_image: String,
30    /// The OCI image labels from the installed image
31    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
32    pub(crate) labels: BTreeMap<String, String>,
33    /// The version number
34    pub(crate) version: Option<String>,
35    /// The timestamp
36    pub(crate) timestamp: Option<chrono::DateTime<chrono::Utc>>,
37    /// The `uname -r` of the kernel doing the installation
38    pub(crate) kernel: String,
39    /// The state of SELinux at install time
40    pub(crate) selinux: String,
41}
42
43impl InstallAleph {
44    #[context("Creating aleph data")]
45    pub(crate) fn new(
46        src_imageref: &ostree_container::OstreeImageReference,
47        target_imgref: &ostree_container::OstreeImageReference,
48        imgstate: &ostree_container::store::LayeredImageState,
49        selinux_state: &SELinuxFinalState,
50    ) -> Result<Self> {
51        let uname = rustix::system::uname();
52        let oci_labels = crate::status::labels_of_config(&imgstate.configuration);
53        let timestamp = oci_labels
54            .and_then(|l| {
55                l.get(oci_spec::image::ANNOTATION_CREATED)
56                    .map(|s| s.as_str())
57            })
58            .and_then(bootc_utils::try_deserialize_timestamp);
59        let labels: BTreeMap<String, String> = oci_labels
60            .map(|l| l.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
61            .unwrap_or_default();
62        // When installing via osbuild, the source image is usually a
63        // temporary local container storage path (e.g. `/tmp/...`) which is not useful.
64        let image = if src_imageref.imgref.name.starts_with("/tmp") {
65            tracing::debug!("Not serializing the source imageref as it's a local temporary image.");
66            None
67        } else {
68            Some(src_imageref.imgref.name.clone())
69        };
70        let r = InstallAleph {
71            image,
72            target_image: target_imgref.imgref.name.clone(),
73            digest: imgstate.manifest_digest.to_string(),
74            labels,
75            version: imgstate.version().as_ref().map(|s| s.to_string()),
76            timestamp,
77            kernel: uname.release().to_str()?.to_string(),
78            selinux: selinux_state.to_aleph().to_string(),
79        };
80        Ok(r)
81    }
82
83    /// Serialize to a file in the target root.
84    pub(crate) fn write_to(&self, root: &Dir) -> Result<()> {
85        root.atomic_replace_with(BOOTC_ALEPH_PATH, |f| {
86            anyhow::Ok(self.to_canon_json_writer(f)?)
87        })
88        .context("Writing aleph version")
89    }
90}