snix_build/buildservice/
oci.rs1use std::{
2 ffi::OsStr,
3 path::{Path, PathBuf},
4 process::Stdio,
5};
6
7use anyhow::Context;
8use futures::stream::BoxStream;
9use snix_castore::{
10 blobservice::BlobService, directoryservice::DirectoryService, fs::fuse::FuseDaemon,
11};
12use tokio::process::{Child, Command};
13use tracing::{Span, debug, instrument};
14use uuid::Uuid;
15
16use super::{BuildFailure, BuildService, BuildUpdate};
17use crate::sandbox::event::{SandboxEvent, stream_process};
18use crate::{buildservice::BuildRequest, oci::OciOutputs};
19use crate::{
20 buildservice::streaming::run_build_streaming,
21 oci::{make_bundle, make_spec},
22};
23
24const SANDBOX_SHELL: &str = env!("SNIX_BUILD_SANDBOX_SHELL");
25const MAX_CONCURRENT_BUILDS: usize = 2; pub struct OCIBuildService<BS, DS> {
28 bundle_root: PathBuf,
30
31 blob_service: BS,
33 directory_service: DS,
35
36 concurrent_builds: tokio::sync::Semaphore,
39}
40
41impl<BS, DS> OCIBuildService<BS, DS> {
42 pub fn new(bundle_root: PathBuf, blob_service: BS, directory_service: DS) -> Self {
43 Self {
48 bundle_root,
49 blob_service,
50 directory_service,
51 concurrent_builds: tokio::sync::Semaphore::new(MAX_CONCURRENT_BUILDS),
52 }
53 }
54}
55
56impl<BS, DS> OCIBuildService<BS, DS>
57where
58 BS: BlobService + Clone + 'static,
59 DS: DirectoryService + Clone + 'static,
60{
61 async fn spawn_bundle_process(
68 &self,
69 request: &BuildRequest,
70 bundle_path: &Path,
71 build_name: &str,
72 ) -> Result<(BoxStream<'static, SandboxEvent>, OciOutputs), BuildFailure> {
73 let outputs = OciOutputs::new(bundle_path, &request.scratch_paths)?;
74 let mut runtime_spec =
75 make_spec(request, true, SANDBOX_SHELL).context("failed to create spec")?;
76
77 let linux = runtime_spec.linux().clone().unwrap();
78 runtime_spec.set_linux(Some(linux));
79
80 make_bundle(request, &runtime_spec, bundle_path).context("failed to produce bundle")?;
81
82 let blob_service = self.blob_service.clone();
83 let directory_service = self.directory_service.clone();
84 let dest = bundle_path.join("inputs");
85 let root_nodes = Box::new(request.inputs.clone());
86
87 let fuse_daemon = tokio::task::spawn_blocking(move || {
88 let fs = snix_castore::fs::SnixStoreFs::new(
89 blob_service,
90 directory_service,
91 root_nodes,
92 snix_castore::fs::FSSettings {
93 list_root: true,
94 uid_gid_override: None,
95 show_xattr: false,
96 },
97 tokio::runtime::Handle::current(),
98 );
99 FuseDaemon::new(fs, dest, 4, true).context("failed to start fuse daemon")
100 })
101 .await
102 .map_err(|e| BuildFailure::Other {
103 message: e.to_string(),
104 })??;
105
106 debug!(bundle.path=?bundle_path, "about to spawn bundle");
107
108 let child = spawn_bundle(bundle_path, build_name)?;
109 let stream = stream_process(child, fuse_daemon)?;
110 Ok((stream, outputs))
111 }
112}
113
114impl<BS, DS> BuildService for OCIBuildService<BS, DS>
115where
116 BS: BlobService + Clone + 'static,
117 DS: DirectoryService + Clone + 'static,
118{
119 #[instrument(skip_all, fields(build.name=tracing::field::Empty))]
120 fn do_build(&self, request: BuildRequest) -> BoxStream<'_, BuildUpdate> {
121 let span = Span::current();
122 let build_name = Uuid::new_v4();
123 let bundle_path = self.bundle_root.join(build_name.to_string());
124 span.record("build.name", build_name.to_string());
125
126 run_build_streaming(
127 &self.concurrent_builds,
128 self.blob_service.clone(),
129 self.directory_service.clone(),
130 request.outputs.clone(),
131 request.refscan_needles.clone(),
132 move || async move {
133 self.spawn_bundle_process(&request, &bundle_path, &build_name.to_string())
134 .await
135 },
136 )
137 }
138}
139
140#[instrument(err)]
143fn spawn_bundle(
144 bundle_path: impl AsRef<OsStr> + std::fmt::Debug,
145 bundle_name: &str,
146) -> std::io::Result<Child> {
147 let mut command = Command::new("runc");
148
149 command
150 .args(&[
151 "run".into(),
152 "--bundle".into(),
153 bundle_path.as_ref().to_os_string(),
154 bundle_name.into(),
155 ])
156 .stderr(Stdio::piped())
157 .stdout(Stdio::piped())
158 .stdin(Stdio::null())
159 .kill_on_drop(true);
160
161 command.spawn()
162}