Skip to main content

snix_store/
utils.rs

1use std::{collections::HashMap, sync::Arc};
2
3use snix_castore::utils as castore_utils;
4use snix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
5use url::Url;
6
7use crate::composition::REG;
8use crate::nar::NarCalculationService;
9use crate::pathinfoservice::PathInfoService;
10use snix_castore::composition::{
11    Composition, DeserializeWithRegistry, ServiceBuilder, with_registry,
12};
13
14#[derive(serde::Deserialize, Default)]
15pub struct CompositionConfigs {
16    pub blobservices:
17        HashMap<String, DeserializeWithRegistry<Box<dyn ServiceBuilder<Output = dyn BlobService>>>>,
18    pub directoryservices: HashMap<
19        String,
20        DeserializeWithRegistry<Box<dyn ServiceBuilder<Output = dyn DirectoryService>>>,
21    >,
22    pub pathinfoservices: HashMap<
23        String,
24        DeserializeWithRegistry<Box<dyn ServiceBuilder<Output = dyn PathInfoService>>>,
25    >,
26}
27
28/// Provides a set of clap arguments to configure snix-\[ca\]store services.
29///
30/// This particular variant has defaults tailored for usecases accessing data
31/// directly locally, like the `snix store daemon` command.
32#[derive(clap::Parser, Clone)]
33#[group(id = "StoreServiceUrls")]
34pub struct ServiceUrls {
35    #[clap(flatten)]
36    pub castore_service_addrs: castore_utils::ServiceUrls,
37
38    #[arg(long, env, default_value = "redb:/var/lib/snix-store/pathinfo.redb")]
39    pub path_info_service_addr: String,
40}
41
42/// Provides a set of clap arguments to configure snix-\[ca\]store services.
43///
44/// This particular variant has defaults tailored for usecases accessing data
45/// from another running snix daemon, via gRPC.
46#[derive(clap::Parser, Clone)]
47#[group(id = "StoreServiceUrlsGrpc")]
48pub struct ServiceUrlsGrpc {
49    #[clap(flatten)]
50    castore_service_addrs: castore_utils::ServiceUrlsGrpc,
51
52    #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
53    path_info_service_addr: String,
54}
55
56/// Provides a set of clap arguments to configure snix-\[ca\]store services.
57///
58/// This particular variant has defaults tailored for usecases keeping all data
59/// in memory.
60/// It's currently used in snix-cli-eval, as we don't really care about persistency
61/// there yet, and using something else here might make some perf output harder
62/// to interpret.
63#[derive(clap::Parser, Clone)]
64#[group(id = "StoreServiceUrlsMemory")]
65pub struct ServiceUrlsMemory {
66    #[clap(flatten)]
67    castore_service_addrs: castore_utils::ServiceUrlsMemory,
68
69    #[arg(long, env, default_value = "redb+memory:")]
70    path_info_service_addr: String,
71}
72
73impl From<ServiceUrlsGrpc> for ServiceUrls {
74    fn from(urls: ServiceUrlsGrpc) -> ServiceUrls {
75        ServiceUrls {
76            castore_service_addrs: urls.castore_service_addrs.into(),
77            path_info_service_addr: urls.path_info_service_addr,
78        }
79    }
80}
81
82impl From<ServiceUrlsMemory> for ServiceUrls {
83    fn from(urls: ServiceUrlsMemory) -> ServiceUrls {
84        ServiceUrls {
85            castore_service_addrs: urls.castore_service_addrs.into(),
86            path_info_service_addr: urls.path_info_service_addr,
87        }
88    }
89}
90
91/// Deserializes service addresses into composition config, configuring each
92/// service as the single "root".
93/// If the `xp-composition-cli` feature is enabled, and a file specified in the
94/// `--experimental-store-composition` parameter, this is used instead.
95pub async fn addrs_to_configs(
96    urls: impl Into<ServiceUrls>,
97) -> Result<CompositionConfigs, Box<dyn std::error::Error + Send + Sync>> {
98    let urls: ServiceUrls = urls.into();
99
100    #[cfg(feature = "xp-composition-cli")]
101    if let Some(conf_path) = urls.castore_service_addrs.experimental_store_composition {
102        let conf_text = tokio::fs::read_to_string(conf_path).await?;
103        return Ok(with_registry(&REG, || toml::from_str(&conf_text))?);
104    }
105
106    let mut configs: CompositionConfigs = Default::default();
107
108    let blob_service_url = Url::parse(&urls.castore_service_addrs.blob_service_addr)?;
109    let directory_service_url = Url::parse(&urls.castore_service_addrs.directory_service_addr)?;
110    let path_info_service_url = Url::parse(&urls.path_info_service_addr)?;
111
112    configs.blobservices.insert(
113        "root".into(),
114        with_registry(&REG, || blob_service_url.try_into())?,
115    );
116    configs.directoryservices.insert(
117        "root".into(),
118        with_registry(&REG, || directory_service_url.try_into())?,
119    );
120    configs.pathinfoservices.insert(
121        "root".into(),
122        with_registry(&REG, || path_info_service_url.try_into())?,
123    );
124
125    Ok(configs)
126}
127
128/// Construct the store handles from their addrs.
129pub async fn construct_services(
130    urls: impl Into<ServiceUrls>,
131) -> Result<
132    (
133        Arc<dyn BlobService>,
134        Arc<dyn DirectoryService>,
135        Arc<dyn PathInfoService>,
136        Arc<dyn NarCalculationService>,
137    ),
138    Box<dyn std::error::Error + Send + Sync>,
139> {
140    let configs = addrs_to_configs(urls).await?;
141    construct_services_from_configs(configs).await
142}
143
144/// Construct the store handles from their addrs.
145pub async fn construct_services_from_configs(
146    configs: CompositionConfigs,
147) -> Result<
148    (
149        Arc<dyn BlobService>,
150        Arc<dyn DirectoryService>,
151        Arc<dyn PathInfoService>,
152        Arc<dyn NarCalculationService>,
153    ),
154    Box<dyn std::error::Error + Send + Sync>,
155> {
156    let mut comp = Composition::new(&REG);
157
158    comp.extend(configs.blobservices);
159    comp.extend(configs.directoryservices);
160    comp.extend(configs.pathinfoservices);
161
162    let blob_service: Arc<dyn BlobService> = comp.build("root").await?;
163    let directory_service: Arc<dyn DirectoryService> = comp.build("root").await?;
164    let path_info_service: Arc<dyn PathInfoService> = comp.build("root").await?;
165
166    // HACK: The grpc client also implements NarCalculationService, and we
167    // really want to use it (otherwise we'd need to fetch everything again for hashing).
168    // Until we revamped store composition and config, detect this special case here.
169    let nar_calculation_service: Arc<dyn NarCalculationService> = path_info_service
170        .nar_calculation_service()
171        .unwrap_or_else(|| {
172            Arc::new(crate::nar::Renderer::new(
173                blob_service.clone(),
174                directory_service.clone(),
175            ))
176        });
177
178    Ok((
179        blob_service,
180        directory_service,
181        path_info_service,
182        nar_calculation_service,
183    ))
184}
185
186/// Returns a new [PathInfoService]. Should only be used for tests.
187pub fn gen_test_pathinfo_service() -> impl PathInfoService + Clone {
188    crate::pathinfoservice::RedbPathInfoService::new_temporary(
189        "test".to_string(),
190        crate::pathinfoservice::RedbPathInfoServiceConfig::default(),
191    )
192    .expect("creating pathinfoservice to succeed")
193}