bootc_lib/bootc_composefs/
repo.rs1use fn_error_context::context;
2use std::sync::Arc;
3
4use anyhow::{Context, Result};
5
6use cfsctl::composefs;
7use cfsctl::composefs_boot;
8use cfsctl::composefs_oci;
9use composefs::fsverity::{FsVerityHashValue, Sha512HashValue};
10use composefs_boot::{BootOps, bootloader::BootEntry as ComposefsBootEntry};
11use composefs_oci::{
12 PullResult, 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 allow_missing_fsverity: bool,
30) -> Result<PullResult<Sha512HashValue>> {
31 const COMPOSEFS_REPO_INIT_JOURNAL_ID: &str = "5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9";
32
33 let rootfs_dir = &root_setup.physical_root;
34 let image_name = &state.source.imageref.name;
35 let transport = &state.source.imageref.transport;
36
37 tracing::info!(
38 message_id = COMPOSEFS_REPO_INIT_JOURNAL_ID,
39 bootc.operation = "repository_init",
40 bootc.source_image = %image_name,
41 bootc.transport = %transport,
42 bootc.allow_missing_fsverity = allow_missing_fsverity,
43 "Initializing composefs repository for image {}:{}",
44 transport,
45 image_name
46 );
47
48 crate::store::ensure_composefs_dir(rootfs_dir)?;
49
50 let mut repo = open_composefs_repo(rootfs_dir)?;
51 repo.set_insecure(allow_missing_fsverity);
52
53 let OstreeExtImgRef {
54 name: image_name,
55 transport,
56 } = &state.source.imageref;
57
58 let mut config = crate::deploy::new_proxy_config();
59 ostree_ext::container::merge_default_container_proxy_opts(&mut config)?;
60
61 composefs_oci_pull(
63 &Arc::new(repo),
64 &format!("{transport}{image_name}"),
65 None,
66 Some(config),
67 )
68 .await
69}
70
71pub(crate) fn get_imgref(transport: &str, image: &str) -> String {
79 let img = image.strip_prefix(":").unwrap_or(&image);
80 let transport = transport.strip_suffix(":").unwrap_or(&transport);
81
82 if transport == "registry" || transport == "docker://" {
83 format!("docker://{img}")
84 } else if transport == "docker-daemon" {
85 format!("docker-daemon:{img}")
86 } else {
87 format!("{transport}:{img}")
88 }
89}
90
91#[context("Pulling composefs repository")]
94pub(crate) async fn pull_composefs_repo(
95 transport: &String,
96 image: &String,
97 allow_missing_fsverity: bool,
98) -> Result<(
99 crate::store::ComposefsRepository,
100 Vec<ComposefsBootEntry<Sha512HashValue>>,
101 Sha512HashValue,
102 crate::store::ComposefsFilesystem,
103)> {
104 const COMPOSEFS_PULL_JOURNAL_ID: &str = "4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8";
105
106 tracing::info!(
107 message_id = COMPOSEFS_PULL_JOURNAL_ID,
108 bootc.operation = "pull",
109 bootc.source_image = image,
110 bootc.transport = transport,
111 bootc.allow_missing_fsverity = allow_missing_fsverity,
112 "Pulling composefs image {}:{}",
113 transport,
114 image
115 );
116
117 let rootfs_dir = Dir::open_ambient_dir("/sysroot", ambient_authority())?;
118
119 let mut repo = open_composefs_repo(&rootfs_dir).context("Opening composefs repo")?;
120 repo.set_insecure(allow_missing_fsverity);
121
122 let final_imgref = get_imgref(transport, image);
123
124 tracing::debug!("Image to pull {final_imgref}");
125
126 let mut config = crate::deploy::new_proxy_config();
127 ostree_ext::container::merge_default_container_proxy_opts(&mut config)?;
128
129 let pull_result = composefs_oci_pull(&Arc::new(repo), &final_imgref, None, Some(config))
130 .await
131 .context("Pulling composefs repo")?;
132
133 tracing::info!(
134 message_id = COMPOSEFS_PULL_JOURNAL_ID,
135 id = pull_result.config_digest,
136 verity = pull_result.config_verity.to_hex(),
137 "Pulled image into repository"
138 );
139
140 let mut repo = open_composefs_repo(&rootfs_dir)?;
141 repo.set_insecure(allow_missing_fsverity);
142
143 let mut fs: crate::store::ComposefsFilesystem =
144 create_composefs_filesystem(&repo, &pull_result.config_digest, None)
145 .context("Failed to create composefs filesystem")?;
146
147 let entries = fs.transform_for_boot(&repo)?;
148 let id = fs.commit_image(&repo, None)?;
149
150 Ok((repo, entries, id, fs))
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 const IMAGE_NAME: &str = "quay.io/example/image:latest";
158
159 #[test]
160 fn test_get_imgref_registry_transport() {
161 assert_eq!(
162 get_imgref("registry:", IMAGE_NAME),
163 format!("docker://{IMAGE_NAME}")
164 );
165 }
166
167 #[test]
168 fn test_get_imgref_containers_storage() {
169 assert_eq!(
170 get_imgref("containers-storage", IMAGE_NAME),
171 format!("containers-storage:{IMAGE_NAME}")
172 );
173
174 assert_eq!(
175 get_imgref("containers-storage:", IMAGE_NAME),
176 format!("containers-storage:{IMAGE_NAME}")
177 );
178 }
179
180 #[test]
181 fn test_get_imgref_edge_cases() {
182 assert_eq!(
183 get_imgref("registry", IMAGE_NAME),
184 format!("docker://{IMAGE_NAME}")
185 );
186 }
187
188 #[test]
189 fn test_get_imgref_docker_daemon_transport() {
190 assert_eq!(
191 get_imgref("docker-daemon", IMAGE_NAME),
192 format!("docker-daemon:{IMAGE_NAME}")
193 );
194 }
195}