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