bootc_lib/bootc_composefs/
repo.rs

1use fn_error_context::context;
2use std::sync::Arc;
3
4use anyhow::{Context, Result};
5
6use ostree_ext::composefs::fsverity::{FsVerityHashValue, Sha512HashValue};
7use ostree_ext::composefs_boot::{BootOps, bootloader::BootEntry as ComposefsBootEntry};
8use ostree_ext::composefs_oci::{
9    image::create_filesystem as create_composefs_filesystem, pull as composefs_oci_pull,
10};
11
12use ostree_ext::container::ImageReference as OstreeExtImgRef;
13
14use cap_std_ext::cap_std::{ambient_authority, fs::Dir};
15
16use crate::install::{RootSetup, State};
17
18pub(crate) fn open_composefs_repo(rootfs_dir: &Dir) -> Result<crate::store::ComposefsRepository> {
19    crate::store::ComposefsRepository::open_path(rootfs_dir, "composefs")
20        .context("Failed to open composefs repository")
21}
22
23pub(crate) async fn initialize_composefs_repository(
24    state: &State,
25    root_setup: &RootSetup,
26) -> Result<(String, impl FsVerityHashValue)> {
27    let rootfs_dir = &root_setup.physical_root;
28
29    crate::store::ensure_composefs_dir(rootfs_dir)?;
30
31    let repo = open_composefs_repo(rootfs_dir)?;
32
33    let OstreeExtImgRef {
34        name: image_name,
35        transport,
36    } = &state.source.imageref;
37
38    // transport's display is already of type "<transport_type>:"
39    composefs_oci_pull(
40        &Arc::new(repo),
41        &format!("{transport}{image_name}"),
42        None,
43        None,
44    )
45    .await
46}
47
48/// skopeo (in composefs-rs) doesn't understand "registry:"
49/// This function will convert it to "docker://" and return the image ref
50///
51/// Ex
52/// docker://quay.io/some-image
53/// containers-storage:some-image
54/// docker-daemon:some-image-id
55pub(crate) fn get_imgref(transport: &str, image: &str) -> String {
56    let img = image.strip_prefix(":").unwrap_or(&image);
57    let transport = transport.strip_suffix(":").unwrap_or(&transport);
58
59    if transport == "registry" || transport == "docker://" {
60        format!("docker://{img}")
61    } else if transport == "docker-daemon" {
62        format!("docker-daemon:{img}")
63    } else {
64        format!("{transport}:{img}")
65    }
66}
67
68/// Pulls the `image` from `transport` into a composefs repository at /sysroot
69/// Checks for boot entries in the image and returns them
70#[context("Pulling composefs repository")]
71pub(crate) async fn pull_composefs_repo(
72    transport: &String,
73    image: &String,
74) -> Result<(
75    crate::store::ComposefsRepository,
76    Vec<ComposefsBootEntry<Sha512HashValue>>,
77    Sha512HashValue,
78    crate::store::ComposefsFilesystem,
79)> {
80    let rootfs_dir = Dir::open_ambient_dir("/sysroot", ambient_authority())?;
81
82    let repo = open_composefs_repo(&rootfs_dir).context("Opening composefs repo")?;
83
84    let final_imgref = get_imgref(transport, image);
85
86    tracing::debug!("Image to pull {final_imgref}");
87
88    let (id, verity) = composefs_oci_pull(&Arc::new(repo), &final_imgref, None, None)
89        .await
90        .context("Pulling composefs repo")?;
91
92    tracing::info!("ID: {id}, Verity: {}", verity.to_hex());
93
94    let repo = open_composefs_repo(&rootfs_dir)?;
95    let mut fs: crate::store::ComposefsFilesystem =
96        create_composefs_filesystem(&repo, &id, None)
97            .context("Failed to create composefs filesystem")?;
98
99    let entries = fs.transform_for_boot(&repo)?;
100    let id = fs.commit_image(&repo, None)?;
101
102    Ok((repo, entries, id, fs))
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    const IMAGE_NAME: &str = "quay.io/example/image:latest";
110
111    #[test]
112    fn test_get_imgref_registry_transport() {
113        assert_eq!(
114            get_imgref("registry:", IMAGE_NAME),
115            format!("docker://{IMAGE_NAME}")
116        );
117    }
118
119    #[test]
120    fn test_get_imgref_containers_storage() {
121        assert_eq!(
122            get_imgref("containers-storage", IMAGE_NAME),
123            format!("containers-storage:{IMAGE_NAME}")
124        );
125
126        assert_eq!(
127            get_imgref("containers-storage:", IMAGE_NAME),
128            format!("containers-storage:{IMAGE_NAME}")
129        );
130    }
131
132    #[test]
133    fn test_get_imgref_edge_cases() {
134        assert_eq!(
135            get_imgref("registry", IMAGE_NAME),
136            format!("docker://{IMAGE_NAME}")
137        );
138    }
139
140    #[test]
141    fn test_get_imgref_docker_daemon_transport() {
142        assert_eq!(
143            get_imgref("docker-daemon", IMAGE_NAME),
144            format!("docker-daemon:{IMAGE_NAME}")
145        );
146    }
147}