use std::time::Duration; use anyhow::{anyhow, Result}; use crate::config::{ ConfigOpts, ConfigOptsAcme, ConfigOptsConsul, ConfigOptsFirewall, ConfigOptsIgd, }; // This code is inspired by the Trunk crate (https://github.com/thedodd/trunk) // In this file, we take ConfigOpts and transform them into ready-to-use // RuntimeConfig. We apply default values and business logic. // Consul config is mandatory, all the others are optional. #[derive(Debug)] pub struct RuntimeConfigConsul { pub node_name: String, pub url: String, } #[derive(Debug)] pub struct RuntimeConfigAcme { pub email: String, } #[derive(Debug)] pub struct RuntimeConfigFirewall { pub refresh_time: Duration, } #[derive(Debug)] pub struct RuntimeConfigIgd { pub private_ip: String, pub expiration_time: Duration, pub refresh_time: Duration, } #[derive(Debug)] pub struct RuntimeConfig { pub consul: RuntimeConfigConsul, pub acme: Option, pub firewall: Option, pub igd: Option, } impl RuntimeConfig { pub fn new(opts: ConfigOpts) -> Result { let consul = RuntimeConfigConsul::new(opts.consul)?; let acme = RuntimeConfigAcme::new(opts.acme)?; let firewall = RuntimeConfigFirewall::new(opts.firewall)?; let igd = RuntimeConfigIgd::new(opts.igd)?; Ok(Self { acme, consul, firewall, igd, }) } } impl RuntimeConfigConsul { pub(super) fn new(opts: ConfigOptsConsul) -> Result { let node_name = match opts.node_name { Some(n) => n, _ => return Err(anyhow!("'DIPLONAT_CONSUL_NODE_NAME' is required")), }; let url = match opts.url { Some(url) => url, _ => super::CONSUL_URL.to_string(), }; Ok(Self { node_name, url }) } } impl RuntimeConfigAcme { pub fn new(opts: ConfigOptsAcme) -> Result> { if !opts.enable { return Ok(None) }; let email = match opts.email { Some(email) => email, _ => { return Err(anyhow!( "'DIPLONAT_ACME_EMAIL' is required if ACME is enabled" )) } }; Ok(Some(Self { email })) } } impl RuntimeConfigFirewall { pub(super) fn new(opts: ConfigOptsFirewall) -> Result> { if !opts.enable { return Ok(None) } let refresh_time = Duration::from_secs( match opts.refresh_time { Some(t) => t, _ => super::REFRESH_TIME, } .into(), ); Ok(Some(Self { refresh_time })) } } impl RuntimeConfigIgd { pub(super) fn new(opts: ConfigOptsIgd) -> Result> { if !opts.enable { return Ok(None) } let private_ip = match opts.private_ip { Some(ip) => ip, _ => { return Err(anyhow!( "'DIPLONAT_IGD_PRIVATE_IP' is required if IGD is enabled" )) } }; let expiration_time = Duration::from_secs( match opts.expiration_time { Some(t) => t.into(), _ => super::EXPIRATION_TIME, } .into(), ); let refresh_time = Duration::from_secs( match opts.refresh_time { Some(t) => t, _ => super::REFRESH_TIME, } .into(), ); if refresh_time.as_secs() * 2 > expiration_time.as_secs() { return Err(anyhow!( "IGD expiration time (currently: {}s) must be at least twice bigger than refresh time \ (currently: {}s)", expiration_time.as_secs(), refresh_time.as_secs() )) } Ok(Some(Self { private_ip, expiration_time, refresh_time, })) } }