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, 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 pub rocket: rocket::Config,
41}
42
43#[derive(Deserialize, Debug)]
45pub struct ComputedConfig {
46 pub unified_webhook_acl: Vec<dns::AllowedClient>,
47}
48
49#[derive(Deserialize, Debug, Deftly)]
50#[derive_deftly(DefaultViaSerde)]
51pub struct Testing {
52 #[serde(default)]
63 pub time_offset: i64,
64
65 pub fake_https_dir: Option<String>,
78
79 #[serde(default)]
83 pub allowed_source_packages: Option<Vec<AllowedSourcePackage>>,
84}
85
86#[derive(Debug, Clone, Deref, Deftly)]
87#[derive_deftly(DeserializeViaFromStr)]
88#[deftly(deser(inner, expect = "glob pattern"))]
89pub struct AllowedSourcePackage(glob::Pattern);
90
91#[derive(Deserialize, Debug)]
92pub struct T2u {
93 pub distro: String,
94 pub forges: Vec<Forge>,
95}
96
97#[derive(Deserialize, Debug, Deftly)]
98#[derive_deftly(DefaultViaSerde)]
99pub struct Intervals {
100 #[serde(default = "days::<3>")]
101 pub max_tag_age: HtDuration,
102
103 #[serde(default = "secs::<1000>")]
104 pub max_tag_age_skew: HtDuration,
105
106 #[serde(default = "days::<32>")]
107 pub expire: HtDuration,
108
109 #[serde(default = "hours::<5>")]
110 pub expire_every: HtDuration,
111
112 #[serde(default = "days::<1>")]
113 pub show_recent: HtDuration,
114}
115
116#[derive(Deserialize, Debug, Deftly)]
117pub struct Files {
118 pub db: String,
119
120 pub o2m_socket: String,
121
122 pub scratch_dir: Option<String>,
123
124 pub archive_dir: String,
125
126 pub template_dir: Option<String>,
131
132 pub port_report_file: Option<String>,
137
138 pub self_git_dir: Option<String>,
142}
143
144#[derive(Deserialize, Debug, Deftly)]
145#[derive_deftly(DefaultViaSerde)]
146pub struct Log {
147 #[serde(with = "serde_log_level")]
153 #[serde(default)]
154 pub level: Option<logging::LevelFilter>,
155
156 #[serde(default)]
165 pub tracing: String,
166
167 pub dir: Option<String>,
174
175 #[serde(default)]
179 pub schedule: tracing_logrotate::ScheduleConfig,
180}
181
182#[derive(Deserialize, Debug, Deftly)]
183#[derive_deftly(DefaultViaSerde)]
184pub struct Timeouts {
185 #[serde(default = "secs::<100>")]
186 pub http_request: HtDuration,
187
188 #[serde(default = "secs::<500>")]
189 pub git_clone: HtDuration,
190
191 #[serde(default = "secs::<10>")]
192 pub unpause_poll: HtDuration,
193
194 #[serde(default = "secs::<100>")]
196 pub disconnected_worker_expire: HtDuration,
197
198 #[serde(default)]
202 pub socket_stat_interval: Option<HtDuration>,
203}
204
205#[derive(Deserialize, Debug, Deftly)]
206#[derive_deftly(DefaultViaSerde)]
207pub struct Limits {
208 #[serde(default = "usize_::<16384>")]
209 pub o2m_line: usize,
210}
211
212#[derive(Deserialize, Debug)]
213pub struct Forge {
214 pub host: Hostname,
215 pub kind: String,
216 pub allow: Vec<dns::AllowedClient>,
217 #[serde(default = "u32_::<3>")]
218 pub max_concurrent_fetch: u32,
219}
220
221const MAX_MAX_CONCURRENT_FETCH: u32 = 16;
222
223type HtD = HtDuration;
226
227fn htd_from_secs(secs: u64) -> HtD { Duration::from_secs(secs).into() }
229pub fn secs<const SECS: u64>() -> HtD { htd_from_secs(SECS ) }
230pub fn hours<const HRS: u64>() -> HtD { htd_from_secs( HRS * 3600 ) }
231pub fn days<const DAYS: u64>() -> HtD { htd_from_secs(DAYS * 86400) }
232pub fn u32_ <const U: u32 >() -> u32 { U }
234pub fn usize_<const U: usize>() -> usize { U }
235
236impl Config {
237 pub fn check(&self) -> Result<(), StartupError> {
238 let mut errs = vec![];
239 self.t2u.check_inner(&mut errs);
240 self.intervals.check_inner(&mut errs);
241 self.testing.check_inner(&mut errs);
242 self.files.check_inner(&mut errs);
243
244 if errs.is_empty() {
245 return Ok(());
246 }
247 for e in errs {
248 eprintln!("configuration error: {e:#}");
249 }
250 Err(StartupError::InvalidConfig)
251 }
252}
253
254impl Files {
255 fn check_inner(&self, errs: &mut Vec<AE>) {
256 let archive_dir = &self.archive_dir;
257 match (|| {
258 let md = fs::metadata(archive_dir).context("stat")?;
259 if !md.is_dir() {
260 return Err(anyhow!("is not a directory"));
261 }
262 unix_access(&archive_dir, libc::W_OK | libc::X_OK)
263 .context("check writeability")?;
264 Ok(())
265 })() {
266 Err(e) => errs.push(
267 e
268 .context(archive_dir.clone())
269 .context("config.files.archive_dir")
270 ),
271 Ok(()) => {},
272 }
273 }
274}
275
276impl T2u {
277 fn check_inner(&self, errs: &mut Vec<AE>) {
278 if self.forges.is_empty() {
279 errs.push(anyhow!("no forges configured!"));
280 }
281 for (host, kind) in self.forges.iter()
282 .map(|f| (&f.host, &f.kind))
283 .duplicates()
284 {
285 errs.push(anyhow!("duplicate forge kind and host {kind} {host}"));
286 }
287 for forge in &self.forges {
288 forge.check_inner(errs);
289 }
290 }
291}
292
293impl Forge {
294 fn check_inner(&self, errs: &mut Vec<AE>) {
295 if self.max_concurrent_fetch > MAX_MAX_CONCURRENT_FETCH {
296 errs.push(anyhow!(
297 "forge {} max_concurrent_fetch={} > hardcoded limit {}",
298 self.host, self. max_concurrent_fetch,
299 MAX_MAX_CONCURRENT_FETCH,
300 ));
301 }
302 }
303}
304
305impl Testing {
306 fn check_inner(&self, errs: &mut Vec<AE>) {
307 if let Some(fake) = &self.fake_https_dir {
308 if !fake.starts_with('/') {
309 errs.push(anyhow!("t2u.fake_https_dir must be absolute"));
310 }
311 }
312 }
313}
314
315impl Intervals {
316 fn check_inner(&self, errs: &mut Vec<AE>) {
317 let Intervals { max_tag_age, max_tag_age_skew, expire, .. } = *self;
318 let min_expire = HtDuration::from(
319 max_tag_age.checked_add(*max_tag_age_skew)
320 .unwrap_or_else(|| {
321 errs.push(anyhow!(
322 "max_tag_age and/or max_tag_age_slew far too large"
323 ));
324 Duration::ZERO
325 })
326 );
327 if !(*expire > *min_expire) {
328 errs.push(anyhow!(
329 "expiry {expire} too short, must be > max_tag_age {max_tag_age} + max_tag_age_skew {max_tag_age_skew}, > {min_expire}"
330 ))
331 }
332 }
333}
334
335impl TryFrom<&Config> for ComputedConfig {
336 type Error = StartupError;
337 fn try_from(config: &Config) -> Result<ComputedConfig, StartupError> {
338 let unified_webhook_acl = config.t2u.forges.iter()
339 .map(|forge| &forge.allow)
340 .flatten()
341 .cloned()
342 .collect_vec();
343 Ok(ComputedConfig {
344 unified_webhook_acl,
345 })
346 }
347}
348
349#[test]
350fn timeouts_defaults() {
351 let _: Timeouts = Timeouts::default();
352}
353
354mod serde_log_level {
355 use super::*;
356 use logging::*;
357
358 pub(super) fn deserialize<'de, D: Deserializer<'de>>(
359 deser: D,
360 ) -> Result<Option<LevelFilter>, D::Error> {
361 let s: String = String::deserialize(deser)?.to_ascii_uppercase();
362 let l: LevelFilter = s.parse()
363 .map_err(|_| D::Error::invalid_value(
364 serde::de::Unexpected::Str(&s),
365 &"log level",
366 ))?;
367 Ok(Some(l))
368 }
369}