Skip to main content

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    #[serde(skip_serializing_if = "Option::is_none")]
35    pub(crate) version: Option<String>,
36    /// The timestamp
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub(crate) timestamp: Option<chrono::DateTime<chrono::Utc>>,
39    /// The `uname -r` of the kernel doing the installation
40    pub(crate) kernel: String,
41    /// The state of SELinux at install time
42    pub(crate) selinux: String,
43}
44
45impl InstallAleph {
46    #[context("Creating aleph data")]
47    pub(crate) fn new(
48        src_imageref: &ostree_container::OstreeImageReference,
49        target_imgref: &ostree_container::OstreeImageReference,
50        imgstate: &ostree_container::store::LayeredImageState,
51        selinux_state: &SELinuxFinalState,
52    ) -> Result<Self> {
53        let uname = rustix::system::uname();
54        let oci_labels = crate::status::labels_of_config(&imgstate.configuration);
55        let timestamp = oci_labels
56            .and_then(|l| {
57                l.get(oci_spec::image::ANNOTATION_CREATED)
58                    .map(|s| s.as_str())
59            })
60            .and_then(bootc_utils::try_deserialize_timestamp);
61        let labels: BTreeMap<String, String> = oci_labels
62            .map(|l| l.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
63            .unwrap_or_default();
64        // When installing via osbuild, the source image is usually a
65        // temporary local container storage path (e.g. `/tmp/...`) which is not useful.
66        let image = if src_imageref.imgref.name.starts_with("/tmp") {
67            tracing::debug!("Not serializing the source imageref as it's a local temporary image.");
68            None
69        } else {
70            Some(src_imageref.imgref.name.clone())
71        };
72        let r = InstallAleph {
73            image,
74            target_image: target_imgref.imgref.name.clone(),
75            digest: imgstate.manifest_digest.to_string(),
76            labels,
77            version: imgstate.version().as_ref().map(|s| s.to_string()),
78            timestamp,
79            kernel: uname.release().to_str()?.to_string(),
80            selinux: selinux_state.to_aleph().to_string(),
81        };
82        Ok(r)
83    }
84
85    /// Serialize to a file in the target root.
86    pub(crate) fn write_to(&self, root: &Dir) -> Result<()> {
87        root.atomic_replace_with(BOOTC_ALEPH_PATH, |f| {
88            anyhow::Ok(self.to_canon_json_writer(f)?)
89        })
90        .context("Writing aleph version")
91    }
92}