use crate::domain::{self, acme, checker, config}; use crate::error::{MapUnexpected, ToUnexpected}; use crate::{error, origin}; use std::{future, pin, sync}; #[derive(thiserror::Error, Debug)] pub enum GetConfigError { #[error("not found")] NotFound, #[error(transparent)] Unexpected(#[from] error::Unexpected), } impl From for GetConfigError { fn from(e: config::GetError) -> GetConfigError { match e { config::GetError::NotFound => GetConfigError::NotFound, config::GetError::Unexpected(e) => GetConfigError::Unexpected(e), } } } #[derive(thiserror::Error, Debug)] pub enum GetOriginError { #[error("not found")] NotFound, #[error(transparent)] Unexpected(#[from] error::Unexpected), } impl From for GetOriginError { fn from(e: config::GetError) -> GetOriginError { match e { config::GetError::NotFound => GetOriginError::NotFound, config::GetError::Unexpected(e) => GetOriginError::Unexpected(e), } } } #[derive(thiserror::Error, Debug)] pub enum SyncError { #[error("not found")] NotFound, #[error("already in progress")] AlreadyInProgress, #[error(transparent)] Unexpected(#[from] error::Unexpected), } impl From for SyncError { fn from(e: config::GetError) -> SyncError { match e { config::GetError::NotFound => SyncError::NotFound, config::GetError::Unexpected(e) => SyncError::Unexpected(e), } } } #[derive(thiserror::Error, Debug)] pub enum SyncWithConfigError { #[error("invalid url")] InvalidURL, #[error("invalid branch name")] InvalidBranchName, #[error("already in progress")] AlreadyInProgress, #[error("target A/AAAA not set")] TargetANotSet, #[error("challenge token not set")] ChallengeTokenNotSet, #[error(transparent)] Unexpected(#[from] error::Unexpected), } impl From for SyncWithConfigError { fn from(e: origin::store::SyncError) -> SyncWithConfigError { match e { origin::store::SyncError::InvalidURL => SyncWithConfigError::InvalidURL, origin::store::SyncError::InvalidBranchName => SyncWithConfigError::InvalidBranchName, origin::store::SyncError::AlreadyInProgress => SyncWithConfigError::AlreadyInProgress, origin::store::SyncError::Unexpected(e) => SyncWithConfigError::Unexpected(e), } } } impl From for SyncWithConfigError { fn from(e: checker::CheckDomainError) -> SyncWithConfigError { match e { checker::CheckDomainError::TargetANotSet => SyncWithConfigError::TargetANotSet, checker::CheckDomainError::ChallengeTokenNotSet => { SyncWithConfigError::ChallengeTokenNotSet } checker::CheckDomainError::Unexpected(e) => SyncWithConfigError::Unexpected(e), } } } impl From for SyncWithConfigError { fn from(e: config::SetError) -> SyncWithConfigError { match e { config::SetError::Unexpected(e) => SyncWithConfigError::Unexpected(e), } } } pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyError; pub type AllDomainsResult = config::AllDomainsResult; #[mockall::automock( type Origin=origin::MockOrigin; type SyncWithConfigFuture=future::Ready>; type SyncAllOriginsErrorsIter=Vec<(Option,error::Unexpected)>; )] pub trait Manager { type Origin<'mgr>: origin::Origin + 'mgr where Self: 'mgr; type SyncWithConfigFuture<'mgr>: future::Future> + Send + Unpin + 'mgr where Self: 'mgr; type SyncAllOriginsErrorsIter<'mgr>: IntoIterator, error::Unexpected)> + 'mgr where Self: 'mgr; fn get_config(&self, domain: &domain::Name) -> Result; fn get_origin(&self, domain: &domain::Name) -> Result, GetOriginError>; fn sync_with_config( &self, domain: domain::Name, config: config::Config, ) -> Self::SyncWithConfigFuture<'_>; fn sync_all_origins(&self) -> Result, error::Unexpected>; fn get_acme_http01_challenge_key( &self, token: &str, ) -> Result; fn all_domains(&self) -> AllDomainsResult>>; } pub trait BoxedManager: Manager + Send + Sync + Clone {} struct ManagerImpl where OriginStore: origin::store::BoxedStore, DomainConfigStore: config::BoxedStore, AcmeManager: acme::manager::BoxedManager, { origin_store: OriginStore, domain_config_store: DomainConfigStore, domain_checker: checker::DNSChecker, acme_manager: Option, } pub fn new( origin_store: OriginStore, domain_config_store: DomainConfigStore, domain_checker: checker::DNSChecker, acme_manager: Option, ) -> impl BoxedManager where OriginStore: origin::store::BoxedStore, DomainConfigStore: config::BoxedStore, AcmeManager: acme::manager::BoxedManager, { sync::Arc::new(ManagerImpl { origin_store, domain_config_store, domain_checker, acme_manager, }) } impl BoxedManager for sync::Arc> where OriginStore: origin::store::BoxedStore, DomainConfigStore: config::BoxedStore, AcmeManager: acme::manager::BoxedManager, { } impl Manager for sync::Arc> where OriginStore: origin::store::BoxedStore, DomainConfigStore: config::BoxedStore, AcmeManager: acme::manager::BoxedManager, { type Origin<'mgr> = OriginStore::Origin<'mgr> where Self: 'mgr; type SyncWithConfigFuture<'mgr> = pin::Pin> + Send + 'mgr>> where Self: 'mgr; type SyncAllOriginsErrorsIter<'mgr> = Box, error::Unexpected)> + 'mgr> where Self: 'mgr; fn get_config(&self, domain: &domain::Name) -> Result { Ok(self.domain_config_store.get(domain)?) } fn get_origin(&self, domain: &domain::Name) -> Result, GetOriginError> { let config = self.domain_config_store.get(domain)?; let origin = self .origin_store .get(config.origin_descr) // if there's a config there should be an origin, any error here is unexpected .map_unexpected()?; Ok(origin) } fn sync_with_config( &self, domain: domain::Name, config: config::Config, ) -> Self::SyncWithConfigFuture<'_> { Box::pin(async move { let config_hash = config.hash().map_unexpected()?; self.domain_checker .check_domain(&domain, &config_hash) .await?; self.origin_store .sync(config.origin_descr.clone(), origin::store::Limits {})?; self.domain_config_store.set(&domain, &config)?; if let Some(ref acme_manager) = self.acme_manager { acme_manager.sync_domain(domain.clone()).await?; } Ok(()) }) } fn sync_all_origins(&self) -> Result, error::Unexpected> { let iter = self.origin_store.all_descrs().map_unexpected()?.into_iter(); Ok(Box::from(iter.filter_map(|descr| { if let Err(err) = descr { return Some((None, err.to_unexpected())); } let descr = descr.unwrap(); if let Err(err) = self .origin_store .sync(descr.clone(), origin::store::Limits {}) { return Some((Some(descr), err.to_unexpected())); } None }))) } fn get_acme_http01_challenge_key( &self, token: &str, ) -> Result { if let Some(ref acme_manager) = self.acme_manager { return acme_manager.get_http01_challenge_key(token); } Err(GetAcmeHttp01ChallengeKeyError::NotFound) } fn all_domains(&self) -> AllDomainsResult>> { self.domain_config_store.all_domains() } }