snix_build/buildservice/
from_addr.rs1#[cfg(target_os = "linux")]
2use crate::buildservice::bwrap::BubblewrapBuildService;
3
4use super::{BuildService, DummyBuildService, grpc::GRPCBuildService};
5use snix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
6use url::Url;
7
8#[cfg(target_os = "linux")]
9use super::oci::OCIBuildService;
10#[cfg(all(not(target_os = "linux"), doc))]
11struct OCIBuildService;
12#[cfg(all(not(target_os = "linux"), doc))]
13struct BubblewrapBuildService;
14
15#[cfg_attr(target_os = "macos", allow(unused_variables))]
26pub async fn from_addr<BS, DS>(
27 uri: &str,
28 blob_service: BS,
29 directory_service: DS,
30) -> std::io::Result<Box<dyn BuildService>>
31where
32 BS: BlobService + Send + Sync + Clone + 'static,
33 DS: DirectoryService + Send + Sync + Clone + 'static,
34{
35 let url =
36 Url::parse(uri).map_err(|e| std::io::Error::other(format!("unable to parse url: {e}")))?;
37
38 Ok(match url.scheme() {
39 "dummy" => {
40 if url.has_authority() {
42 Err(std::io::Error::other("dummy must not have authority"))?
43 }
44 if !url.path().is_empty() {
45 Err(std::io::Error::other("dummy must not have path"))?
46 }
47 Box::<DummyBuildService>::default()
48 }
49 #[cfg(target_os = "linux")]
50 "oci" => {
51 if url.has_authority() {
53 Err(std::io::Error::other("oci must not have authority"))?
54 }
55 if url.path().is_empty() {
57 Err(std::io::Error::other("oci needs a bundle dir as path"))?
58 }
59
60 Box::new(OCIBuildService::new(
63 url.path().into(),
64 blob_service,
65 directory_service,
66 ))
67 }
68 #[cfg(target_os = "linux")]
69 "bwrap" => {
70 if url.has_authority() {
72 Err(std::io::Error::other("bwrap must not have authority"))?
73 }
74 if url.path().is_empty() {
76 Err(std::io::Error::other("bwap needs a bundle dir as path"))?
77 }
78
79 Box::new(BubblewrapBuildService::new(
80 url.path().into(),
81 blob_service,
82 directory_service,
83 ))
84 }
85 scheme => {
86 if scheme.starts_with("grpc+") {
87 let client =
88 crate::proto::build_service_client::BuildServiceClient::with_interceptor(
89 snix_castore::tonic::channel_from_url(&url)
90 .await
91 .map_err(std::io::Error::other)?,
92 snix_tracing::propagate::tonic::send_trace,
93 );
94 Box::new(GRPCBuildService::from_client(client))
97 } else {
98 Err(std::io::Error::other(format!(
99 "unknown scheme: {}",
100 url.scheme()
101 )))?
102 }
103 }
104 })
105}
106
107#[cfg(test)]
108mod tests {
109 use super::from_addr;
110 use rstest::rstest;
111 use snix_castore::blobservice::{BlobService, MemoryBlobService};
112 use std::sync::Arc;
113 #[cfg(target_os = "linux")]
114 use std::sync::LazyLock;
115 #[cfg(target_os = "linux")]
116 use tempfile::TempDir;
117
118 #[cfg(target_os = "linux")]
119 static TMPDIR_OCI_1: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
120 #[cfg(target_os = "linux")]
121 static TMPDIR_OCI_2: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
122 #[cfg(target_os = "linux")]
123 static TMPDIR_BWRAP_1: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
124 #[cfg(target_os = "linux")]
125 static TMPDIR_BWRAP_2: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
126
127 #[rstest]
128 #[case::unsupported_scheme("http://foo.example/test", false)]
130 #[case::valid_dummy("dummy:", true)]
132 #[case::invalid_dummy_authority("dummy://", false)]
134 #[case::grpc_valid_unix_socket("grpc+unix:/path/to/somewhere", true)]
136 #[case::grpc_invalid_unix_socket_authority("grpc+unix:///path/to/somewhere", false)]
138 #[case::grpc_invalid_unix_socket_and_host("grpc+unix://host.example/path/to/somewhere", false)]
140 #[case::grpc_valid_ipv6_localhost_port_12345("grpc+http://[::1]:12345", true)]
142 #[case::grpc_valid_http_host_without_port("grpc+http://localhost", true)]
144 #[case::grpc_valid_https_host_without_port("grpc+https://localhost", true)]
146 #[case::grpc_invalid_host_and_path("grpc+http://localhost/some-path", false)]
148 #[cfg_attr(target_os = "linux", case::oci_missing_bundle_dir("oci:", false))]
150 #[cfg_attr(target_os = "linux", case::oci_bundle_path(&format!("oci:{}", TMPDIR_OCI_1.path().to_str().unwrap()), true))]
152 #[cfg_attr(
154 target_os = "linux",
155 case::oci_bundle_path_authority(&format!("oci://{}", TMPDIR_OCI_2.path().to_str().unwrap()), false)
156 )]
157 #[cfg_attr(target_os = "linux", case::bwrap_missing_bundle_dir("bwrap:", false))]
159 #[cfg_attr(target_os = "linux", case::bwrap_bundle_path(&format!("bwrap:{}", TMPDIR_BWRAP_1.path().to_str().unwrap()), true))]
161 #[cfg_attr(
163 target_os = "linux",
164 case::bwrap_bundle_path_authority(&format!("bwrap://{}", TMPDIR_BWRAP_2.path().to_str().unwrap()), false)
165 )]
166 #[tokio::test]
167 async fn test_from_addr(#[case] uri_str: &str, #[case] exp_succeed: bool) {
168 let blob_service: Arc<dyn BlobService> = Arc::from(MemoryBlobService::default());
169 let directory_service = snix_castore::utils::gen_test_directory_service();
170
171 let resp = from_addr(uri_str, blob_service, directory_service).await;
172
173 if exp_succeed {
174 resp.expect("should succeed");
175 } else {
176 assert!(resp.is_err(), "should fail");
177 }
178 }
179}