tag2upload_service_manager/
config.rs
use crate::prelude::*;
define_derive_deftly! {
DefaultViaSerde:
impl Default for $ttype {
fn default() -> $ttype {
serde_json::from_value(json!({})).expect("defaults all OK")
}
}
}
#[derive(Deserialize, Debug)]
pub struct Config {
pub t2u: T2u,
#[serde(default)]
pub intervals: Intervals,
#[serde(default)]
pub timeouts: Timeouts,
#[serde(default)]
pub limits: Limits,
pub files: Files,
#[serde(default)]
pub log: Log,
#[serde(default)]
pub testing: Testing,
pub rocket: rocket::Config,
}
#[derive(Deserialize, Debug, Deftly)]
#[derive_deftly(DefaultViaSerde)]
pub struct Testing {
#[serde(default)]
pub time_offset: i64,
pub fake_https_dir: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct T2u {
pub distro: String,
pub forges: Vec<Forge>,
}
#[derive(Deserialize, Debug, Deftly)]
#[derive_deftly(DefaultViaSerde)]
pub struct Intervals {
#[serde(default = "days::<3>")]
pub max_tag_age: HtDuration,
#[serde(default = "secs::<1000>")]
pub max_tag_age_skew: HtDuration,
#[serde(default = "days::<15>")]
pub expire: HtDuration,
#[serde(default = "hours::<5>")]
pub expire_every: HtDuration,
#[serde(default = "days::<1>")]
pub show_recent: HtDuration,
}
#[derive(Deserialize, Debug, Deftly)]
pub struct Files {
pub db: String,
pub o2m_socket: String,
pub scratch_dir: Option<String>,
pub archive_dir: String,
pub template_dir: Option<String>,
pub port_report_file: Option<String>,
}
#[derive(Deserialize, Debug, Deftly)]
#[derive_deftly(DefaultViaSerde)]
pub struct Log {
#[serde(default = "logging::default_level_filter")]
#[serde(with = "serde_log_level")]
pub level: logging::LevelFilter,
#[serde(default)]
pub tracing: String,
}
#[derive(Deserialize, Debug, Deftly)]
#[derive_deftly(DefaultViaSerde)]
pub struct Timeouts {
#[serde(default = "secs::<100>")]
pub http_request: HtDuration,
#[serde(default = "secs::<500>")]
pub git_clone: HtDuration,
#[serde(default)]
pub socket_stat_interval: Option<HtDuration>,
}
#[derive(Deserialize, Debug, Deftly)]
#[derive_deftly(DefaultViaSerde)]
pub struct Limits {
#[serde(default = "usize_::<16384>")]
pub o2m_line: usize,
}
#[derive(Deserialize, Debug)]
pub struct Forge {
pub host: Hostname,
pub kind: String,
pub allow: Vec<dns::AllowedCaller>,
}
type HtD = HtDuration;
fn secs<const SECS: u64>() -> HtD { Duration::from_secs(SECS).into() }
fn days<const DAYS: u64>() -> HtD { Duration::from_secs(DAYS * 86400).into() }
fn hours<const HRS: u64>() -> HtD { Duration::from_secs(HRS * 3600).into() }
fn usize_<const U: usize>() -> usize { U }
impl Config {
pub fn check(&self) -> Result<(), StartupError> {
let mut errs = vec![];
self.t2u.check_inner(&mut errs);
self.intervals.check_inner(&mut errs);
self.testing.check_inner(&mut errs);
self.files.check_inner(&mut errs);
if errs.is_empty() {
return Ok(());
}
for e in errs {
eprintln!("configuration error: {e:#}");
}
Err(StartupError::InvalidConfig)
}
}
impl Files {
fn check_inner(&self, errs: &mut Vec<AE>) {
let archive_dir = &self.archive_dir;
match (|| {
let md = fs::metadata(archive_dir).context("stat")?;
if !md.is_dir() {
return Err(anyhow!("is not a directory"));
}
unix_access(&archive_dir, libc::W_OK | libc::X_OK)
.context("check writeability")?;
Ok(())
})() {
Err(e) => errs.push(
e
.context(archive_dir.clone())
.context("config.files.archive_dir")
),
Ok(()) => {},
}
}
}
impl T2u {
fn check_inner(&self, errs: &mut Vec<AE>) {
if self.forges.is_empty() {
errs.push(anyhow!("no forges configured!"));
}
for (host, kind) in self.forges.iter()
.map(|f| (&f.host, &f.kind))
.duplicates()
{
errs.push(anyhow!("duplicate forge kind and host {kind} {host}"));
}
}
}
impl Testing {
fn check_inner(&self, errs: &mut Vec<AE>) {
if let Some(fake) = &self.fake_https_dir {
if !fake.starts_with('/') {
errs.push(anyhow!("t2u.fake_https_dir must be absolute"));
}
}
}
}
impl Intervals {
fn check_inner(&self, errs: &mut Vec<AE>) {
let Intervals { max_tag_age, max_tag_age_skew, expire, .. } = *self;
let min_expire = HtDuration::from(
max_tag_age.checked_add(*max_tag_age_skew)
.unwrap_or_else(|| {
errs.push(anyhow!(
"max_tag_age and/or max_tag_age_slew far too large"
));
Duration::ZERO
})
);
if !(*expire > *min_expire) {
errs.push(anyhow!(
"expiry {expire} too short, must be > max_tag_age {max_tag_age} + max_tag_age_skew {max_tag_age_skew}, > {min_expire}"
))
}
}
}
#[test]
fn timeouts_defaults() {
let _: Timeouts = Timeouts::default();
}
mod serde_log_level {
use super::*;
use logging::*;
pub(super) fn deserialize<'de, D: Deserializer<'de>>(
deser: D,
) -> Result<LevelFilter, D::Error> {
let s: String = String::deserialize(deser)?.to_ascii_uppercase();
let l: LevelFilter = s.parse()
.map_err(|_| D::Error::invalid_value(
serde::de::Unexpected::Str(&s),
&"log level",
))?;
Ok(l)
}
}