tag2upload_service_manager/
config.rs1
2use crate::prelude::*;
3
4define_derive_deftly! {
5 DefaultViaSerde:
6
7 impl Default for $ttype {
8 fn default() -> $ttype {
9 serde_json::from_value(json!({})).expect("defaults all OK")
10 }
11 }
12}
13
14#[derive(Deserialize, derive_more::Debug)]
15pub struct Config {
16 pub t2u: T2u,
17
18 #[serde(default)]
19 pub intervals: Intervals,
20
21 #[serde(default)]
22 pub timeouts: Timeouts,
23
24 #[serde(default)]
25 pub limits: Limits,
26
27 pub files: Files,
28
29 pub vhosts: ui_vhost::Vhosts,
30
31 #[serde(default)]
32 pub log: Log,
33
34 #[serde(default)]
35 pub testing: Testing,
36
37 #[serde(default)]
38 pub retry: RetryPolicy,
39
40 #[debug(skip)]
44 pub rocket: rocket::Config,
45}
46
47#[derive(Deserialize, Debug)]
49pub struct ComputedConfig {
50 pub unified_webhook_acl: Vec<dns::AllowedClient>,
51 pub bsql_timeout: bsql::Timeout,
52}
53
54#[derive(Deserialize, Debug, Deftly)]
55#[derive_deftly(DefaultViaSerde)]
56pub struct Testing {
57 #[serde(default)]
68 pub time_offset: i64,
69
70 pub fake_https_dir: Option<String>,
83
84 #[serde(default)]
88 pub allowed_source_packages: Option<Vec<AllowedSourcePackage>>,
89}
90
91#[derive(Debug, Clone, Deref, Deftly)]
92#[derive_deftly(DeserializeViaFromStr)]
93#[deftly(deser(inner, expect = "glob pattern"))]
94pub struct AllowedSourcePackage(glob::Pattern);
95
96#[derive(Deserialize, Debug)]
97pub struct T2u {
98 pub distro: String,
99 pub forges: Vec<Forge>,
100}
101
102#[derive(Deserialize, Debug, Deftly)]
103#[derive_deftly(DefaultViaSerde)]
104pub struct Intervals {
105 #[serde(default = "days::<3>")]
106 pub max_tag_age: HtDuration,
107
108 #[serde(default = "secs::<1000>")]
109 pub max_tag_age_skew: HtDuration,
110
111 #[serde(default = "days::<32>")]
112 pub expire: HtDuration,
113
114 #[serde(default = "hours::<5>")]
115 pub expire_every: HtDuration,
116
117 #[serde(default = "days::<1>")]
118 pub show_recent: HtDuration,
119}
120
121#[derive(Deserialize, Debug, Deftly)]
122pub struct Files {
123 pub db: String,
124
125 pub o2m_socket: String,
126
127 pub scratch_dir: Option<String>,
128
129 pub archive_dir: String,
130
131 pub template_dir: Option<String>,
136
137 pub port_report_file: Option<String>,
142
143 pub self_git_dir: Option<String>,
147}
148
149#[derive(Deserialize, Debug, Deftly)]
150#[derive_deftly(DefaultViaSerde)]
151pub struct Log {
152 #[serde(with = "serde_log_level")]
158 #[serde(default)]
159 pub level: Option<logging::LevelFilter>,
160
161 #[serde(default)]
170 pub tracing: String,
171
172 pub dir: Option<String>,
179
180 #[serde(default)]
184 pub schedule: tracing_logrotate::Config,
185}
186
187#[derive(Deserialize, Debug, Deftly)]
188#[derive_deftly(DefaultViaSerde)]
189pub struct Timeouts {
190 #[serde(default = "secs::<100>")]
191 pub http_request: HtDuration,
192
193 #[serde(default = "secs::<100>")]
194 pub git_query: HtDuration,
195
196 #[serde(default = "secs::<500>")]
197 pub git_clone: HtDuration,
198
199 #[serde(default = "secs::<10>")]
200 pub unpause_poll: HtDuration,
201
202 #[serde(default = "secs::<100>")]
204 pub disconnected_worker_expire: HtDuration,
205
206 #[serde(default)]
210 pub socket_stat_interval: Option<HtDuration>,
211
212 #[serde(default = "secs::<10>")]
214 pub db_timeout: HtDuration,
215
216 #[serde(default = "u32_::<100>")]
223 pub db_retries: u32,
224}
225
226#[derive(Deserialize, Debug, Deftly)]
227#[derive_deftly(DefaultViaSerde)]
228pub struct RetryPolicy {
229 #[serde(default = "u32_::<15>")]
230 pub min_retries: u32,
231 #[serde(default = "u32_::<10>")]
232 pub min_salient_retries: u32,
233 #[serde(default = "secs::<100>")]
234 pub timeout_initial: HtDuration,
235 #[serde(default = "f32_per_mil::<12_000>")]
236 pub timeout_increase: f32,
237 #[serde(default = "hours::<12>")]
238 pub timeout_mintotal: HtDuration,
239}
240
241#[derive(Deserialize, Debug, Deftly)]
242#[derive_deftly(DefaultViaSerde)]
243pub struct Limits {
244 #[serde(default = "usize_::<16384>")]
245 pub o2m_line: usize,
246}
247
248#[derive(Deserialize, Debug)]
249pub struct Forge {
250 pub host: Hostname,
251 pub kind: String,
252 pub allow: Vec<dns::AllowedClient>,
253 #[serde(default = "u32_::<3>")]
254 pub max_concurrent_fetch: u32,
255}
256
257const MAX_MAX_CONCURRENT_FETCH: u32 = 16;
258
259type HtD = HtDuration;
262
263fn htd_from_secs(secs: u64) -> HtD { Duration::from_secs(secs).into() }
265pub fn secs<const SECS: u64>() -> HtD { htd_from_secs(SECS ) }
266pub fn hours<const HRS: u64>() -> HtD { htd_from_secs( HRS * 3600 ) }
267pub fn days<const DAYS: u64>() -> HtD { htd_from_secs(DAYS * 86400) }
268pub fn u32_ <const U: u32 >() -> u32 { U }
270pub fn usize_<const U: usize>() -> usize { U }
271pub fn f32_per_mil<const M: u64>() -> f32 { M as f32 / 1000. }
272
273impl Config {
274 pub fn check(&self) -> Result<(), StartupError> {
275 let mut errs = vec![];
276 self.t2u.check_inner(&mut errs);
277 self.intervals.check_inner(&mut errs);
278 self.testing.check_inner(&mut errs);
279 self.files.check_inner(&mut errs);
280
281 if errs.is_empty() {
282 return Ok(());
283 }
284 for e in errs {
285 eprintln!("configuration error: {e:#}");
286 }
287 Err(StartupError::InvalidConfig)
288 }
289}
290
291impl Files {
292 fn check_inner(&self, errs: &mut Vec<AE>) {
293 let archive_dir = &self.archive_dir;
294 match (|| {
295 let md = fs::metadata(archive_dir).context("stat")?;
296 if !md.is_dir() {
297 return Err(anyhow!("is not a directory"));
298 }
299 unix_access(&archive_dir, libc::W_OK | libc::X_OK)
300 .context("check writeability")?;
301 Ok(())
302 })() {
303 Err(e) => errs.push(
304 e
305 .context(archive_dir.clone())
306 .context("config.files.archive_dir")
307 ),
308 Ok(()) => {},
309 }
310 }
311}
312
313impl T2u {
314 fn check_inner(&self, errs: &mut Vec<AE>) {
315 if self.forges.is_empty() {
316 errs.push(anyhow!("no forges configured!"));
317 }
318 for (host, kind) in self.forges.iter()
319 .map(|f| (&f.host, &f.kind))
320 .duplicates()
321 {
322 errs.push(anyhow!("duplicate forge kind and host {kind} {host}"));
323 }
324 for forge in &self.forges {
325 forge.check_inner(errs);
326 }
327 }
328}
329
330impl Forge {
331 fn check_inner(&self, errs: &mut Vec<AE>) {
332 if self.max_concurrent_fetch > MAX_MAX_CONCURRENT_FETCH {
333 errs.push(anyhow!(
334 "forge {} max_concurrent_fetch={} > hardcoded limit {}",
335 self.host, self. max_concurrent_fetch,
336 MAX_MAX_CONCURRENT_FETCH,
337 ));
338 }
339 }
340}
341
342impl Testing {
343 fn check_inner(&self, errs: &mut Vec<AE>) {
344 if let Some(fake) = &self.fake_https_dir {
345 if !fake.starts_with('/') {
346 errs.push(anyhow!("t2u.fake_https_dir must be absolute"));
347 }
348 }
349 }
350}
351
352impl Intervals {
353 fn check_inner(&self, errs: &mut Vec<AE>) {
354 let Intervals { max_tag_age, max_tag_age_skew, expire, .. } = *self;
355 let min_expire = HtDuration::from(
356 max_tag_age.checked_add(*max_tag_age_skew)
357 .unwrap_or_else(|| {
358 errs.push(anyhow!(
359 "max_tag_age and/or max_tag_age_slew far too large"
360 ));
361 Duration::ZERO
362 })
363 );
364 if !(*expire > *min_expire) {
365 errs.push(anyhow!(
366 "expiry {expire} too short, must be > max_tag_age {max_tag_age} + max_tag_age_skew {max_tag_age_skew}, > {min_expire}"
367 ))
368 }
369 }
370}
371
372impl TryFrom<&Config> for ComputedConfig {
373 type Error = StartupError;
374 fn try_from(config: &Config) -> Result<ComputedConfig, StartupError> {
375 let unified_webhook_acl = config.t2u.forges.iter()
376 .map(|forge| &forge.allow)
377 .flatten()
378 .cloned()
379 .collect_vec();
380 let bsql_timeout = bsql::Timeout::new(
381 *config.timeouts.db_timeout,
382 config.timeouts.db_retries,
383 );
384 Ok(ComputedConfig {
385 unified_webhook_acl,
386 bsql_timeout,
387 })
388 }
389}
390
391#[test]
392fn timeouts_defaults() {
393 let _: Timeouts = Timeouts::default();
394}
395
396mod serde_log_level {
397 use super::*;
398 use logging::*;
399
400 pub(super) fn deserialize<'de, D: Deserializer<'de>>(
401 deser: D,
402 ) -> Result<Option<LevelFilter>, D::Error> {
403 let s: String = String::deserialize(deser)?.to_ascii_uppercase();
404 let l: LevelFilter = s.parse()
405 .map_err(|_| D::Error::invalid_value(
406 serde::de::Unexpected::Str(&s),
407 &"log level",
408 ))?;
409 Ok(Some(l))
410 }
411}