Skip to main content

snix_build/buildservice/
bwrap.rs

1use std::path::{Path, PathBuf};
2
3use futures::stream::BoxStream;
4use snix_castore::{
5    blobservice::BlobService, directoryservice::DirectoryService, fs::fuse::FuseDaemon,
6};
7use tracing::{Span, info, instrument};
8use uuid::Uuid;
9
10use super::BuildService;
11use crate::{
12    buildservice::{BuildConstraints, BuildRequest, BuildUpdate, streaming::run_build_streaming},
13    bwrap::Bwrap,
14    sandbox::SandboxSpec,
15};
16const SANDBOX_SHELL: &str = env!("SNIX_BUILD_SANDBOX_SHELL");
17
18pub struct BubblewrapBuildService<BS, DS> {
19    /// Root path in which all builds run
20    workdir: PathBuf,
21
22    /// Handle to a [BlobService], used by filesystems spawned during builds.
23    blob_service: BS,
24    /// Handle to a [DirectoryService], used by filesystems spawned during builds.
25    directory_service: DS,
26
27    // semaphore to track number of concurrently running builds.
28    // this is necessary, as otherwise we very quickly run out of open file handles.
29    concurrent_builds: tokio::sync::Semaphore,
30}
31impl<BS, DS> BubblewrapBuildService<BS, DS> {
32    pub fn new(workdir: PathBuf, blob_service: BS, directory_service: DS) -> Self {
33        // We map root inside the container to the uid/gid this is running at,
34        // and allocate one for uid 1000 into the container from the range we
35        // got in /etc/sub{u,g}id.
36        // FUTUREWORK: use different uids?
37        Self {
38            workdir,
39            blob_service,
40            directory_service,
41            concurrent_builds: tokio::sync::Semaphore::new(2),
42        }
43    }
44}
45
46impl<BS, DS> BubblewrapBuildService<BS, DS>
47where
48    BS: BlobService + Clone + 'static,
49    DS: DirectoryService + Clone + 'static,
50{
51    fn make_spec(&self, request: BuildRequest, sandbox_path: &Path) -> SandboxSpec {
52        let blob_service = self.blob_service.clone();
53        let directory_service = self.directory_service.clone();
54
55        SandboxSpec::builder()
56            .host_workdir(sandbox_path.to_path_buf())
57            .sandbox_workdir(request.working_dir)
58            .scratches(request.scratch_paths)
59            .command(request.command_args)
60            .env_vars(request.environment_vars)
61            .additional_files(request.additional_files)
62            .with_inputs(request.inputs_dir, move |path| {
63                let root_nodes = Box::new(request.inputs.clone());
64                let fs = snix_castore::fs::SnixStoreFs::new(
65                    blob_service.clone(),
66                    directory_service.clone(),
67                    root_nodes,
68                    snix_castore::fs::FSSettings {
69                        list_root: true,
70                        uid_gid_override: None,
71                        show_xattr: false,
72                    },
73                    tokio::runtime::Handle::current(),
74                );
75                // FUTUREWORK: make fuse daemon threads configurable?
76                FuseDaemon::new(fs, path, 4, false)
77            })
78            .allow_network(
79                request
80                    .constraints
81                    .contains(&BuildConstraints::NetworkAccess),
82            )
83            .provide_shell(
84                request
85                    .constraints
86                    .contains(&BuildConstraints::ProvideBinSh)
87                    .then_some(SANDBOX_SHELL.into()),
88            )
89            .build()
90    }
91}
92
93impl<BS, DS> BuildService for BubblewrapBuildService<BS, DS>
94where
95    BS: BlobService + Clone + 'static,
96    DS: DirectoryService + Clone + 'static,
97{
98    #[instrument(skip_all, fields(build.name=tracing::field::Empty))]
99    fn do_build(&self, request: BuildRequest) -> BoxStream<'_, BuildUpdate> {
100        let span = Span::current();
101        let build_name = Uuid::new_v4();
102        let sandbox_path = self.workdir.join(build_name.to_string());
103        span.record("build.name", build_name.to_string());
104
105        let outputs = request.outputs.clone();
106        let needles = request.refscan_needles.clone();
107
108        let spec = self.make_spec(request, &sandbox_path);
109
110        run_build_streaming(
111            &self.concurrent_builds,
112            self.blob_service.clone(),
113            self.directory_service.clone(),
114            outputs,
115            needles,
116            move || async move {
117                info!("Starting bwrap build");
118
119                let (stream, finder) = Bwrap::initialize(spec)?.run()?;
120                Ok((stream, finder))
121            },
122        )
123    }
124}