domani/src/domain/manager.rs
Brian Picciano 420f1ff42a implement error::unexpected, use it everywhere
This is an improved form of the previous `error::Unexpected` type, now
with more capabilities and generally better naming.
2023-06-17 15:41:39 +02:00

300 lines
8.8 KiB
Rust

use crate::domain::{self, acme, checker, config};
use crate::error::unexpected::{self, Intoable, Mappable};
use crate::origin;
use std::{future, pin, sync};
#[derive(thiserror::Error, Debug)]
pub enum GetConfigError {
#[error("not found")]
NotFound,
#[error(transparent)]
Unexpected(#[from] unexpected::Error),
}
impl From<config::GetError> 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] unexpected::Error),
}
impl From<config::GetError> 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] unexpected::Error),
}
impl From<config::GetError> 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] unexpected::Error),
}
impl From<origin::store::SyncError> 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<checker::CheckDomainError> 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<config::SetError> 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<T> = config::AllDomainsResult<T>;
#[mockall::automock(
type Origin=origin::MockOrigin;
type SyncWithConfigFuture=future::Ready<Result<(), SyncWithConfigError>>;
type SyncAllOriginsErrorsIter=Vec<unexpected::Error>;
)]
pub trait Manager {
type Origin<'mgr>: origin::Origin + 'mgr
where
Self: 'mgr;
type SyncWithConfigFuture<'mgr>: future::Future<Output = Result<(), SyncWithConfigError>>
+ Send
+ Unpin
+ 'mgr
where
Self: 'mgr;
type SyncAllOriginsErrorsIter<'mgr>: IntoIterator<Item = unexpected::Error> + 'mgr
where
Self: 'mgr;
fn get_config(&self, domain: &domain::Name) -> Result<config::Config, GetConfigError>;
fn get_origin(&self, domain: &domain::Name) -> Result<Self::Origin<'_>, GetOriginError>;
fn sync_with_config(
&self,
domain: domain::Name,
config: config::Config,
) -> Self::SyncWithConfigFuture<'_>;
fn sync_all_origins(&self) -> Result<Self::SyncAllOriginsErrorsIter<'_>, unexpected::Error>;
fn get_acme_http01_challenge_key(
&self,
token: &str,
) -> Result<String, GetAcmeHttp01ChallengeKeyError>;
fn all_domains(&self) -> AllDomainsResult<Vec<AllDomainsResult<domain::Name>>>;
}
pub trait BoxedManager: Manager + Send + Sync + Clone {}
struct ManagerImpl<OriginStore, DomainConfigStore, AcmeManager>
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<AcmeManager>,
}
pub fn new<OriginStore, DomainConfigStore, AcmeManager>(
origin_store: OriginStore,
domain_config_store: DomainConfigStore,
domain_checker: checker::DNSChecker,
acme_manager: Option<AcmeManager>,
) -> 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<OriginStore, DomainConfigStore, AcmeManager> BoxedManager
for sync::Arc<ManagerImpl<OriginStore, DomainConfigStore, AcmeManager>>
where
OriginStore: origin::store::BoxedStore,
DomainConfigStore: config::BoxedStore,
AcmeManager: acme::manager::BoxedManager,
{
}
impl<OriginStore, DomainConfigStore, AcmeManager> Manager
for sync::Arc<ManagerImpl<OriginStore, DomainConfigStore, AcmeManager>>
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<Box<dyn future::Future<Output = Result<(), SyncWithConfigError>> + Send + 'mgr>>
where Self: 'mgr;
type SyncAllOriginsErrorsIter<'mgr> = Box<dyn Iterator<Item = unexpected::Error> + 'mgr>
where Self: 'mgr;
fn get_config(&self, domain: &domain::Name) -> Result<config::Config, GetConfigError> {
Ok(self.domain_config_store.get(domain)?)
}
fn get_origin(&self, domain: &domain::Name) -> Result<Self::Origin<'_>, 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
.or_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()
.or_unexpected_while("calculating config hash")?;
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<Self::SyncAllOriginsErrorsIter<'_>, unexpected::Error> {
let iter = self
.origin_store
.all_descrs()
.or_unexpected_while("fetching all origin descrs")?
.into_iter();
Ok(Box::from(iter.filter_map(|descr| {
if let Err(err) = descr {
return Some(err.into_unexpected());
}
let descr = descr.unwrap();
if let Err(err) = self
.origin_store
.sync(descr.clone(), origin::store::Limits {})
{
return Some(err.into_unexpected_while(format!("syncing store {:?}", descr)));
}
None
})))
}
fn get_acme_http01_challenge_key(
&self,
token: &str,
) -> Result<String, GetAcmeHttp01ChallengeKeyError> {
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<Vec<AllDomainsResult<domain::Name>>> {
self.domain_config_store.all_domains()
}
}