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