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