use subbeat::types::{DatasourceConfig, InfluxConfig, PrometheusConfig}; use std::env; #[derive(Clone)] pub struct WebhookAlertingConfig { pub endpoint: String, } #[derive(Clone)] pub enum AlertingType { Webhook(WebhookAlertingConfig), } #[derive(Clone)] pub struct AlertingConfig { pub alerting_type: AlertingType, pub interval: u64, // interval in seconds } #[derive(Clone)] pub struct Config { pub port: u16, pub datasource_config: DatasourceConfig, pub alerting: Option, } fn resolve_datasource(config: &config::Config) -> anyhow::Result { if config.get::("prometheus.url").is_ok() { println!("using Prometheus"); return Ok(DatasourceConfig::Prometheus(PrometheusConfig { url: config.get("prometheus.url")?, query: config.get("prometheus.query")?, })); } if config.get::("influx.url").is_ok() { println!("using Influx"); return Ok(DatasourceConfig::Influx(InfluxConfig { url: config.get("influx.url")?, org_id: config.get("influx.org_id")?, token: config.get("influx.token")?, query: config.get("influx.query")?, })); } return Err(anyhow::format_err!("please configure a datasource")); } fn resolve_alerting(config: &config::Config) -> anyhow::Result> { if config.get::("alerting.type").is_err() { return Ok(None); } if config.get::("alerting.endpoint").is_err() { return Err(anyhow::format_err!("missing endpoint param in alerting")); } if config.get::("alerting.interval").is_err() { return Err(anyhow::format_err!("missing interval param in alerting")); } if config.get::("alerting.interval").is_err() { return Err(anyhow::format_err!( "alerting interval should be a positive integer number" )); } let analytic_type = config.get::("alerting.type").unwrap(); if analytic_type != "webhook" { return Err(anyhow::format_err!( "unknown alerting type: {}", analytic_type )); } let endpoint = config.get::("alerting.endpoint").unwrap(); let interval = config.get::("alerting.interval").unwrap(); return Ok(Some(AlertingConfig { alerting_type: AlertingType::Webhook(WebhookAlertingConfig { endpoint }), interval, })); } // config::Environment doesn't support nested configs, e.g. `alerting.type`, // so I've copied this from: // https://github.com/rust-lang/mdBook/blob/f3e5fce6bf5e290c713f4015947dc0f0ad172d20/src/config.rs#L132 // so that `__` can be used in env variables instead of `.`, // e.g. `HASTIC_ALERTING__TYPE` -> alerting.type pub fn update_from_env(config: &mut config::Config) { let overrides = env::vars().filter_map(|(key, value)| parse_env(&key).map(|index| (index, value))); for (key, value) in overrides { config.set(&key, value).unwrap(); } } pub fn print_config(config: config::Config) { // TODO: support any nesting level let sections = config.to_owned().cache.into_table().unwrap(); for (section_name, values) in sections { match values.clone().into_table() { Err(_) => println!("{} => {}", section_name, values), Ok(section) => { for (key, value) in section { println!("{}.{} => {}", section_name, key, value); } } } } } fn parse_env(key: &str) -> Option { const PREFIX: &str = "HASTIC_"; if key.starts_with(PREFIX) { let key = &key[PREFIX.len()..]; Some(key.to_lowercase().replace("__", ".")) } else { None } } impl Config { pub fn new() -> anyhow::Result { let mut config = config::Config::default(); if std::path::Path::new("config.toml").exists() { config.merge(config::File::with_name("config")).unwrap(); } update_from_env(&mut config); if config.get::("port").is_err() { config.set("port", 4347).unwrap(); } print_config(config.clone()); Ok(Config { port: config.get::("port").unwrap(), datasource_config: resolve_datasource(&config)?, alerting: resolve_alerting(&config)?, }) } }