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.
This commit is contained in:
parent
42a033a50b
commit
420f1ff42a
2
TODO
2
TODO
@ -1,3 +1 @@
|
|||||||
- expect statements (pretend it's "expected", not "expect")
|
|
||||||
- map_unexpected annotation string
|
|
||||||
- clean up main a lot
|
- clean up main a lot
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
use std::{future, pin, sync, time};
|
use std::{future, pin, sync, time};
|
||||||
|
|
||||||
use crate::domain::{self, acme};
|
use crate::domain::{self, acme};
|
||||||
use crate::error;
|
use crate::error::unexpected::{self, Intoable, Mappable};
|
||||||
use crate::error::{MapUnexpected, ToUnexpected};
|
|
||||||
|
|
||||||
const LETS_ENCRYPT_URL: &str = "https://acme-v02.api.letsencrypt.org/directory";
|
const LETS_ENCRYPT_URL: &str = "https://acme-v02.api.letsencrypt.org/directory";
|
||||||
|
|
||||||
pub type GetHttp01ChallengeKeyError = acme::store::GetHttp01ChallengeKeyError;
|
pub type GetHttp01ChallengeKeyError = acme::store::GetHttp01ChallengeKeyError;
|
||||||
|
|
||||||
#[mockall::automock(
|
#[mockall::automock(
|
||||||
type SyncDomainFuture=future::Ready<Result<(), error::Unexpected>>;
|
type SyncDomainFuture=future::Ready<Result<(), unexpected::Error>>;
|
||||||
)]
|
)]
|
||||||
pub trait Manager {
|
pub trait Manager {
|
||||||
type SyncDomainFuture<'mgr>: future::Future<Output = Result<(), error::Unexpected>>
|
type SyncDomainFuture<'mgr>: future::Future<Output = Result<(), unexpected::Error>>
|
||||||
+ Send
|
+ Send
|
||||||
+ Unpin
|
+ Unpin
|
||||||
+ 'mgr
|
+ 'mgr
|
||||||
@ -37,14 +36,14 @@ where
|
|||||||
pub async fn new<Store>(
|
pub async fn new<Store>(
|
||||||
store: Store,
|
store: Store,
|
||||||
contact_email: &str,
|
contact_email: &str,
|
||||||
) -> Result<impl BoxedManager, error::Unexpected>
|
) -> Result<impl BoxedManager, unexpected::Error>
|
||||||
where
|
where
|
||||||
Store: acme::store::BoxedStore,
|
Store: acme::store::BoxedStore,
|
||||||
{
|
{
|
||||||
let dir = acme2::DirectoryBuilder::new(LETS_ENCRYPT_URL.to_string())
|
let dir = acme2::DirectoryBuilder::new(LETS_ENCRYPT_URL.to_string())
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("creating acme2 directory builder")?;
|
||||||
|
|
||||||
let mut contact = String::from("mailto:");
|
let mut contact = String::from("mailto:");
|
||||||
contact.push_str(contact_email);
|
contact.push_str(contact_email);
|
||||||
@ -55,17 +54,29 @@ where
|
|||||||
|
|
||||||
match store.get_account_key() {
|
match store.get_account_key() {
|
||||||
Ok(account_key) => {
|
Ok(account_key) => {
|
||||||
builder.private_key((&account_key).try_into().map_unexpected()?);
|
builder.private_key(
|
||||||
|
(&account_key)
|
||||||
|
.try_into()
|
||||||
|
.or_unexpected_while("parsing private key")?,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(acme::store::GetAccountKeyError::NotFound) => (),
|
Err(acme::store::GetAccountKeyError::NotFound) => (),
|
||||||
Err(acme::store::GetAccountKeyError::Unexpected(err)) => return Err(err.to_unexpected()),
|
Err(acme::store::GetAccountKeyError::Unexpected(err)) => return Err(err.into_unexpected()),
|
||||||
}
|
}
|
||||||
|
|
||||||
let account = builder.build().await.map_unexpected()?;
|
let account = builder
|
||||||
let account_key: acme::PrivateKey =
|
.build()
|
||||||
account.private_key().as_ref().try_into().map_unexpected()?;
|
.await
|
||||||
|
.or_unexpected_while("building account")?;
|
||||||
|
let account_key: acme::PrivateKey = account
|
||||||
|
.private_key()
|
||||||
|
.as_ref()
|
||||||
|
.try_into()
|
||||||
|
.or_unexpected_while("parsing private key back out")?;
|
||||||
|
|
||||||
store.set_account_key(&account_key).map_unexpected()?;
|
store
|
||||||
|
.set_account_key(&account_key)
|
||||||
|
.or_unexpected_while("storing account key")?;
|
||||||
|
|
||||||
Ok(sync::Arc::new(ManagerImpl { store, account }))
|
Ok(sync::Arc::new(ManagerImpl { store, account }))
|
||||||
}
|
}
|
||||||
@ -76,7 +87,7 @@ impl<Store> Manager for sync::Arc<ManagerImpl<Store>>
|
|||||||
where
|
where
|
||||||
Store: acme::store::BoxedStore,
|
Store: acme::store::BoxedStore,
|
||||||
{
|
{
|
||||||
type SyncDomainFuture<'mgr> = pin::Pin<Box<dyn future::Future<Output = Result<(), error::Unexpected>> + Send + 'mgr>>
|
type SyncDomainFuture<'mgr> = pin::Pin<Box<dyn future::Future<Output = Result<(), unexpected::Error>> + Send + 'mgr>>
|
||||||
where Self: 'mgr;
|
where Self: 'mgr;
|
||||||
|
|
||||||
fn sync_domain(&self, domain: domain::Name) -> Self::SyncDomainFuture<'_> {
|
fn sync_domain(&self, domain: domain::Name) -> Self::SyncDomainFuture<'_> {
|
||||||
@ -92,10 +103,10 @@ where
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|cert| openssl::x509::X509::try_from(&cert))
|
.map(|cert| openssl::x509::X509::try_from(&cert))
|
||||||
.try_collect::<Vec<openssl::x509::X509>>()
|
.try_collect::<Vec<openssl::x509::X509>>()
|
||||||
.map_unexpected()?
|
.or_unexpected_while("parsing x509 certs")?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.reduce(|a, b| if a.not_after() < b.not_after() { a } else { b })
|
.reduce(|a, b| if a.not_after() < b.not_after() { a } else { b })
|
||||||
.ok_or(error::Unexpected::from(
|
.ok_or(unexpected::Error::from(
|
||||||
"expected there to be more than one cert",
|
"expected there to be more than one cert",
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
@ -107,28 +118,34 @@ where
|
|||||||
log::info!("Fetching a new certificate for domain {}", domain.as_str());
|
log::info!("Fetching a new certificate for domain {}", domain.as_str());
|
||||||
let mut builder = acme2::OrderBuilder::new(self.account.clone());
|
let mut builder = acme2::OrderBuilder::new(self.account.clone());
|
||||||
builder.add_dns_identifier(domain.as_str().to_string());
|
builder.add_dns_identifier(domain.as_str().to_string());
|
||||||
let order = builder.build().await.map_unexpected()?;
|
let order = builder
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.or_unexpected_while("building order")?;
|
||||||
|
|
||||||
let authorizations = order.authorizations().await.map_unexpected()?;
|
let authorizations = order
|
||||||
|
.authorizations()
|
||||||
|
.await
|
||||||
|
.or_unexpected_while("fetching authorizations")?;
|
||||||
|
|
||||||
for auth in authorizations {
|
for auth in authorizations {
|
||||||
let challenge = auth
|
let challenge = auth
|
||||||
.get_challenge("http-01")
|
.get_challenge("http-01")
|
||||||
.ok_or(error::Unexpected::from("expected http-01 challenge"))?;
|
.ok_or(unexpected::Error::from("expected http-01 challenge"))?;
|
||||||
|
|
||||||
let challenge_token = challenge
|
let challenge_token = challenge
|
||||||
.token
|
.token
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(error::Unexpected::from("expected challenge to have token"))?;
|
.ok_or(unexpected::Error::from("expected challenge to have token"))?;
|
||||||
|
|
||||||
let challenge_key = challenge
|
let challenge_key = challenge
|
||||||
.key_authorization()
|
.key_authorization()
|
||||||
.map_unexpected()?
|
.or_unexpected_while("getting challenge key from authorization")?
|
||||||
.ok_or(error::Unexpected::from("expected challenge to have key"))?;
|
.ok_or(unexpected::Error::from("expected challenge to have key"))?;
|
||||||
|
|
||||||
self.store
|
self.store
|
||||||
.set_http01_challenge_key(challenge_token, &challenge_key)
|
.set_http01_challenge_key(challenge_token, &challenge_key)
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("storing challenge token")?;
|
||||||
|
|
||||||
// At this point the manager is prepared to serve the challenge key via the
|
// At this point the manager is prepared to serve the challenge key via the
|
||||||
// `get_http01_challenge_key` method. It is expected that there is some http
|
// `get_http01_challenge_key` method. It is expected that there is some http
|
||||||
@ -141,7 +158,10 @@ where
|
|||||||
"Waiting for ACME challenge to be validated for domain {}",
|
"Waiting for ACME challenge to be validated for domain {}",
|
||||||
domain.as_str(),
|
domain.as_str(),
|
||||||
);
|
);
|
||||||
let challenge = challenge.validate().await.map_unexpected()?;
|
let challenge = challenge
|
||||||
|
.validate()
|
||||||
|
.await
|
||||||
|
.or_unexpected_while("initiating challenge validation")?;
|
||||||
|
|
||||||
// Poll the challenge every 5 seconds until it is in either the
|
// Poll the challenge every 5 seconds until it is in either the
|
||||||
// `valid` or `invalid` state.
|
// `valid` or `invalid` state.
|
||||||
@ -150,12 +170,12 @@ where
|
|||||||
// no matter what the result is, clean up the challenge key
|
// no matter what the result is, clean up the challenge key
|
||||||
self.store
|
self.store
|
||||||
.del_http01_challenge_key(challenge_token)
|
.del_http01_challenge_key(challenge_token)
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("deleting challenge token")?;
|
||||||
|
|
||||||
let challenge = challenge_res.map_unexpected()?;
|
let challenge = challenge_res.or_unexpected_while("getting challenge status")?;
|
||||||
|
|
||||||
if challenge.status != acme2::ChallengeStatus::Valid {
|
if challenge.status != acme2::ChallengeStatus::Valid {
|
||||||
return Err(error::Unexpected::from(
|
return Err(unexpected::Error::from(
|
||||||
format!(
|
format!(
|
||||||
"expected challenge status to be valid, instead it was {:?}",
|
"expected challenge status to be valid, instead it was {:?}",
|
||||||
challenge.status
|
challenge.status
|
||||||
@ -174,10 +194,10 @@ where
|
|||||||
let authorization = auth
|
let authorization = auth
|
||||||
.wait_done(time::Duration::from_secs(5), 3)
|
.wait_done(time::Duration::from_secs(5), 3)
|
||||||
.await
|
.await
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("waiting for authorization status")?;
|
||||||
|
|
||||||
if authorization.status != acme2::AuthorizationStatus::Valid {
|
if authorization.status != acme2::AuthorizationStatus::Valid {
|
||||||
return Err(error::Unexpected::from(
|
return Err(unexpected::Error::from(
|
||||||
format!(
|
format!(
|
||||||
"expected authorization status to be valid, instead it was {:?}",
|
"expected authorization status to be valid, instead it was {:?}",
|
||||||
authorization.status,
|
authorization.status,
|
||||||
@ -197,10 +217,10 @@ where
|
|||||||
let order = order
|
let order = order
|
||||||
.wait_ready(time::Duration::from_secs(5), 3)
|
.wait_ready(time::Duration::from_secs(5), 3)
|
||||||
.await
|
.await
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("waiting for order to be ready")?;
|
||||||
|
|
||||||
if order.status != acme2::OrderStatus::Ready {
|
if order.status != acme2::OrderStatus::Ready {
|
||||||
return Err(error::Unexpected::from(
|
return Err(unexpected::Error::from(
|
||||||
format!(
|
format!(
|
||||||
"expected order status to be ready, instead it was {:?}",
|
"expected order status to be ready, instead it was {:?}",
|
||||||
order.status,
|
order.status,
|
||||||
@ -212,14 +232,16 @@ where
|
|||||||
// Generate an RSA private key for the certificate.
|
// Generate an RSA private key for the certificate.
|
||||||
let pkey = acme::PrivateKey::new();
|
let pkey = acme::PrivateKey::new();
|
||||||
|
|
||||||
let acme2_pkey = (&pkey).try_into().map_unexpected()?;
|
let acme2_pkey = (&pkey)
|
||||||
|
.try_into()
|
||||||
|
.or_unexpected_while("parsing new private key")?;
|
||||||
|
|
||||||
// Create a certificate signing request for the order, and request
|
// Create a certificate signing request for the order, and request
|
||||||
// the certificate.
|
// the certificate.
|
||||||
let order = order
|
let order = order
|
||||||
.finalize(acme2::Csr::Automatic(acme2_pkey))
|
.finalize(acme2::Csr::Automatic(acme2_pkey))
|
||||||
.await
|
.await
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("finalizing order")?;
|
||||||
|
|
||||||
// Poll the order every 5 seconds until it is in either the
|
// Poll the order every 5 seconds until it is in either the
|
||||||
// `valid` or `invalid` state. Valid means that the certificate
|
// `valid` or `invalid` state. Valid means that the certificate
|
||||||
@ -231,10 +253,10 @@ where
|
|||||||
let order = order
|
let order = order
|
||||||
.wait_done(time::Duration::from_secs(5), 3)
|
.wait_done(time::Duration::from_secs(5), 3)
|
||||||
.await
|
.await
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("waiting for order to be validated")?;
|
||||||
|
|
||||||
if order.status != acme2::OrderStatus::Valid {
|
if order.status != acme2::OrderStatus::Valid {
|
||||||
return Err(error::Unexpected::from(
|
return Err(unexpected::Error::from(
|
||||||
format!(
|
format!(
|
||||||
"expected order status to be valid, instead it was {:?}",
|
"expected order status to be valid, instead it was {:?}",
|
||||||
order.status,
|
order.status,
|
||||||
@ -248,17 +270,17 @@ where
|
|||||||
let cert = order
|
let cert = order
|
||||||
.certificate()
|
.certificate()
|
||||||
.await
|
.await
|
||||||
.map_unexpected()?
|
.or_unexpected_while("fetching certificate")?
|
||||||
.ok_or(error::Unexpected::from(
|
.ok_or(unexpected::Error::from(
|
||||||
"expected the order to return a certificate",
|
"expected the order to return a certificate",
|
||||||
))?
|
))?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|cert| acme::Certificate::try_from(cert.as_ref()))
|
.map(|cert| acme::Certificate::try_from(cert.as_ref()))
|
||||||
.try_collect::<Vec<acme::Certificate>>()
|
.try_collect::<Vec<acme::Certificate>>()
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("parsing certificate")?;
|
||||||
|
|
||||||
if cert.len() <= 1 {
|
if cert.len() <= 1 {
|
||||||
return Err(error::Unexpected::from(
|
return Err(unexpected::Error::from(
|
||||||
format!(
|
format!(
|
||||||
"expected more than one certificate to be returned, instead got {}",
|
"expected more than one certificate to be returned, instead got {}",
|
||||||
cert.len(),
|
cert.len(),
|
||||||
@ -270,7 +292,7 @@ where
|
|||||||
log::info!("Certificate for {} successfully retrieved", domain.as_str());
|
log::info!("Certificate for {} successfully retrieved", domain.as_str());
|
||||||
self.store
|
self.store
|
||||||
.set_certificate(domain.as_str(), pkey, cert)
|
.set_certificate(domain.as_str(), pkey, cert)
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("storing new cert")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{fs, io, path, sync};
|
use std::{fs, path, sync};
|
||||||
|
|
||||||
use crate::domain::acme::{Certificate, PrivateKey};
|
use crate::domain::acme::{Certificate, PrivateKey};
|
||||||
use crate::error;
|
use crate::error::unexpected::{self, Mappable};
|
||||||
use crate::error::{MapUnexpected, ToUnexpected};
|
use crate::util;
|
||||||
|
|
||||||
use hex::ToHex;
|
use hex::ToHex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -16,7 +16,7 @@ pub enum GetAccountKeyError {
|
|||||||
NotFound,
|
NotFound,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
@ -25,7 +25,7 @@ pub enum GetHttp01ChallengeKeyError {
|
|||||||
NotFound,
|
NotFound,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
@ -34,24 +34,24 @@ pub enum GetCertificateError {
|
|||||||
NotFound,
|
NotFound,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[mockall::automock]
|
#[mockall::automock]
|
||||||
pub trait Store {
|
pub trait Store {
|
||||||
fn set_account_key(&self, k: &PrivateKey) -> Result<(), error::Unexpected>;
|
fn set_account_key(&self, k: &PrivateKey) -> Result<(), unexpected::Error>;
|
||||||
fn get_account_key(&self) -> Result<PrivateKey, GetAccountKeyError>;
|
fn get_account_key(&self) -> Result<PrivateKey, GetAccountKeyError>;
|
||||||
|
|
||||||
fn set_http01_challenge_key(&self, token: &str, key: &str) -> Result<(), error::Unexpected>;
|
fn set_http01_challenge_key(&self, token: &str, key: &str) -> Result<(), unexpected::Error>;
|
||||||
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError>;
|
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError>;
|
||||||
fn del_http01_challenge_key(&self, token: &str) -> Result<(), error::Unexpected>;
|
fn del_http01_challenge_key(&self, token: &str) -> Result<(), unexpected::Error>;
|
||||||
|
|
||||||
fn set_certificate(
|
fn set_certificate(
|
||||||
&self,
|
&self,
|
||||||
domain: &str,
|
domain: &str,
|
||||||
key: PrivateKey,
|
key: PrivateKey,
|
||||||
cert: Vec<Certificate>,
|
cert: Vec<Certificate>,
|
||||||
) -> Result<(), error::Unexpected>;
|
) -> Result<(), unexpected::Error>;
|
||||||
|
|
||||||
/// Returned vec is guaranteed to have len > 0
|
/// Returned vec is guaranteed to have len > 0
|
||||||
fn get_certificate(
|
fn get_certificate(
|
||||||
@ -77,10 +77,17 @@ struct BoxedFSStore(sync::Arc<FSStore>);
|
|||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
dir_path: &path::Path,
|
dir_path: &path::Path,
|
||||||
) -> Result<impl BoxedStore + rustls::server::ResolvesServerCert, error::Unexpected> {
|
) -> Result<impl BoxedStore + rustls::server::ResolvesServerCert, unexpected::Error> {
|
||||||
fs::create_dir_all(dir_path).map_unexpected()?;
|
vec![
|
||||||
fs::create_dir_all(dir_path.join("http01_challenge_keys")).map_unexpected()?;
|
dir_path,
|
||||||
fs::create_dir_all(dir_path.join("certificates")).map_unexpected()?;
|
dir_path.join("http01_challenge_keys").as_ref(),
|
||||||
|
dir_path.join("certificates").as_ref(),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.map(|dir| {
|
||||||
|
fs::create_dir_all(dir).map_unexpected_while(|| format!("creating dir {}", dir.display()))
|
||||||
|
})
|
||||||
|
.try_collect()?;
|
||||||
|
|
||||||
Ok(BoxedFSStore(sync::Arc::new(FSStore {
|
Ok(BoxedFSStore(sync::Arc::new(FSStore {
|
||||||
dir_path: dir_path.into(),
|
dir_path: dir_path.into(),
|
||||||
@ -113,47 +120,72 @@ impl BoxedFSStore {
|
|||||||
impl BoxedStore for BoxedFSStore {}
|
impl BoxedStore for BoxedFSStore {}
|
||||||
|
|
||||||
impl Store for BoxedFSStore {
|
impl Store for BoxedFSStore {
|
||||||
fn set_account_key(&self, k: &PrivateKey) -> Result<(), error::Unexpected> {
|
fn set_account_key(&self, k: &PrivateKey) -> Result<(), unexpected::Error> {
|
||||||
let mut file = fs::File::create(self.account_key_path()).map_unexpected()?;
|
let path = self.account_key_path();
|
||||||
file.write_all(k.to_string().as_bytes()).map_unexpected()?;
|
{
|
||||||
|
let mut file = fs::File::create(&path).or_unexpected_while("creating file")?;
|
||||||
|
file.write_all(k.to_string().as_bytes())
|
||||||
|
.or_unexpected_while("writing file")
|
||||||
|
}
|
||||||
|
.map_unexpected_while(|| format!("path is {}", path.display()))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_account_key(&self) -> Result<PrivateKey, GetAccountKeyError> {
|
fn get_account_key(&self) -> Result<PrivateKey, GetAccountKeyError> {
|
||||||
let mut file = fs::File::open(self.account_key_path()).map_err(|e| match e.kind() {
|
let path = self.account_key_path();
|
||||||
io::ErrorKind::NotFound => GetAccountKeyError::NotFound,
|
{
|
||||||
_ => e.to_unexpected().into(),
|
let mut file =
|
||||||
})?;
|
match util::open_file(path.as_path()).or_unexpected_while("opening_file")? {
|
||||||
|
Some(file) => file,
|
||||||
|
None => return Err(GetAccountKeyError::NotFound),
|
||||||
|
};
|
||||||
|
|
||||||
let mut key = String::new();
|
let mut key = String::new();
|
||||||
file.read_to_string(&mut key).map_unexpected()?;
|
file.read_to_string(&mut key)
|
||||||
|
.or_unexpected_while("reading file")?;
|
||||||
|
|
||||||
let key = PrivateKey::from_str(&key).map_unexpected()?;
|
let key = PrivateKey::from_str(&key).or_unexpected_while("parsing private key")?;
|
||||||
Ok(key)
|
|
||||||
|
Ok::<PrivateKey, unexpected::Error>(key)
|
||||||
|
}
|
||||||
|
.map_unexpected_while(|| format!("path is {}", path.display()))
|
||||||
|
.map_err(|err| err.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_http01_challenge_key(&self, token: &str, key: &str) -> Result<(), error::Unexpected> {
|
fn set_http01_challenge_key(&self, token: &str, key: &str) -> Result<(), unexpected::Error> {
|
||||||
let mut file = fs::File::create(self.http01_challenge_key_path(token)).map_unexpected()?;
|
let path = self.http01_challenge_key_path(token);
|
||||||
file.write_all(key.as_bytes()).map_unexpected()?;
|
{
|
||||||
|
let mut file = fs::File::create(path.as_path()).or_unexpected_while("creating file")?;
|
||||||
|
file.write_all(key.as_bytes())
|
||||||
|
.or_unexpected_while("writing file")
|
||||||
|
}
|
||||||
|
.map_unexpected_while(|| format!("path is {}", path.display()))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError> {
|
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError> {
|
||||||
|
let path = self.http01_challenge_key_path(token);
|
||||||
|
{
|
||||||
let mut file =
|
let mut file =
|
||||||
fs::File::open(self.http01_challenge_key_path(token)).map_err(|e| match e.kind() {
|
match util::open_file(path.as_path()).or_unexpected_while("opening_file")? {
|
||||||
io::ErrorKind::NotFound => GetHttp01ChallengeKeyError::NotFound,
|
Some(file) => file,
|
||||||
_ => e.to_unexpected().into(),
|
None => return Err(GetHttp01ChallengeKeyError::NotFound),
|
||||||
})?;
|
};
|
||||||
|
|
||||||
let mut key = String::new();
|
let mut key = String::new();
|
||||||
file.read_to_string(&mut key).map_unexpected()?;
|
file.read_to_string(&mut key)
|
||||||
|
.or_unexpected_while("reading file")?;
|
||||||
|
|
||||||
Ok(key)
|
Ok::<String, unexpected::Error>(key)
|
||||||
|
}
|
||||||
|
.map_unexpected_while(|| format!("path is {}", path.display()))
|
||||||
|
.map_err(|err| err.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn del_http01_challenge_key(&self, token: &str) -> Result<(), error::Unexpected> {
|
fn del_http01_challenge_key(&self, token: &str) -> Result<(), unexpected::Error> {
|
||||||
fs::remove_file(self.http01_challenge_key_path(token)).map_unexpected()?;
|
let path = self.http01_challenge_key_path(token);
|
||||||
Ok(())
|
fs::remove_file(path.as_path())
|
||||||
|
.map_unexpected_while(|| format!("path is {}", path.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_certificate(
|
fn set_certificate(
|
||||||
@ -161,31 +193,42 @@ impl Store for BoxedFSStore {
|
|||||||
domain: &str,
|
domain: &str,
|
||||||
key: PrivateKey,
|
key: PrivateKey,
|
||||||
cert: Vec<Certificate>,
|
cert: Vec<Certificate>,
|
||||||
) -> Result<(), error::Unexpected> {
|
) -> Result<(), unexpected::Error> {
|
||||||
let to_store = StoredPKeyCert {
|
let to_store = StoredPKeyCert {
|
||||||
private_key: key,
|
private_key: key,
|
||||||
cert,
|
cert,
|
||||||
};
|
};
|
||||||
|
|
||||||
let cert_file = fs::File::create(self.certificate_path(domain)).map_unexpected()?;
|
let path = self.certificate_path(domain);
|
||||||
|
{
|
||||||
serde_json::to_writer(cert_file, &to_store).map_unexpected()?;
|
let cert_file =
|
||||||
|
fs::File::create(path.as_path()).or_unexpected_while("creating file")?;
|
||||||
Ok(())
|
serde_json::to_writer(cert_file, &to_store).or_unexpected_while("writing cert to file")
|
||||||
|
}
|
||||||
|
.map_unexpected_while(|| format!("path is {}", path.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_certificate(
|
fn get_certificate(
|
||||||
&self,
|
&self,
|
||||||
domain: &str,
|
domain: &str,
|
||||||
) -> Result<(PrivateKey, Vec<Certificate>), GetCertificateError> {
|
) -> Result<(PrivateKey, Vec<Certificate>), GetCertificateError> {
|
||||||
let file = fs::File::open(self.certificate_path(domain)).map_err(|e| match e.kind() {
|
let path = self.certificate_path(domain);
|
||||||
io::ErrorKind::NotFound => GetCertificateError::NotFound,
|
{
|
||||||
_ => e.to_unexpected().into(),
|
let file = match util::open_file(path.as_path()).or_unexpected_while("opening_file")? {
|
||||||
})?;
|
Some(file) => file,
|
||||||
|
None => return Err(GetCertificateError::NotFound),
|
||||||
|
};
|
||||||
|
|
||||||
let stored: StoredPKeyCert = serde_json::from_reader(file).map_unexpected()?;
|
let stored: StoredPKeyCert =
|
||||||
|
serde_json::from_reader(file).or_unexpected_while("parsing json")?;
|
||||||
|
|
||||||
Ok((stored.private_key, stored.cert))
|
Ok::<(PrivateKey, Vec<Certificate>), unexpected::Error>((
|
||||||
|
stored.private_key,
|
||||||
|
stored.cert,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
.map_unexpected_while(|| format!("path is {}", path.display()))
|
||||||
|
.map_err(|err| err.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +246,7 @@ impl rustls::server::ResolvesServerCert for BoxedFSStore {
|
|||||||
}
|
}
|
||||||
Err(GetCertificateError::Unexpected(err)) => Err(err),
|
Err(GetCertificateError::Unexpected(err)) => Err(err),
|
||||||
Ok((key, cert)) => {
|
Ok((key, cert)) => {
|
||||||
match rustls::sign::any_supported_type(&key.into()).map_unexpected() {
|
match rustls::sign::any_supported_type(&key.into()).or_unexpected() {
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
Ok(key) => Ok(Some(sync::Arc::new(rustls::sign::CertifiedKey {
|
Ok(key) => Ok(Some(sync::Arc::new(rustls::sign::CertifiedKey {
|
||||||
cert: cert.into_iter().map(|cert| cert.into()).collect(),
|
cert: cert.into_iter().map(|cert| cert.into()).collect(),
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::net;
|
use std::net;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::error::MapUnexpected;
|
use crate::domain;
|
||||||
use crate::{domain, error};
|
use crate::error::unexpected::{self, Mappable};
|
||||||
|
|
||||||
use trust_dns_client::client::{AsyncClient, ClientHandle};
|
use trust_dns_client::client::{AsyncClient, ClientHandle};
|
||||||
use trust_dns_client::rr::{DNSClass, Name, RData, RecordType};
|
use trust_dns_client::rr::{DNSClass, Name, RData, RecordType};
|
||||||
@ -14,7 +14,7 @@ pub enum NewDNSCheckerError {
|
|||||||
InvalidResolverAddress,
|
InvalidResolverAddress,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
@ -26,7 +26,7 @@ pub enum CheckDomainError {
|
|||||||
ChallengeTokenNotSet,
|
ChallengeTokenNotSet,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DNSChecker {
|
pub struct DNSChecker {
|
||||||
@ -46,7 +46,7 @@ pub async fn new(
|
|||||||
|
|
||||||
let stream = udp::UdpClientStream::<tokio::net::UdpSocket>::new(resolver_addr);
|
let stream = udp::UdpClientStream::<tokio::net::UdpSocket>::new(resolver_addr);
|
||||||
|
|
||||||
let (client, bg) = AsyncClient::connect(stream).await.map_unexpected()?;
|
let (client, bg) = AsyncClient::connect(stream).await.or_unexpected()?;
|
||||||
|
|
||||||
tokio::spawn(bg);
|
tokio::spawn(bg);
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ impl DNSChecker {
|
|||||||
.await
|
.await
|
||||||
.query(domain.clone(), DNSClass::IN, RecordType::A)
|
.query(domain.clone(), DNSClass::IN, RecordType::A)
|
||||||
.await
|
.await
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("querying A record")?;
|
||||||
|
|
||||||
let records = response.answers();
|
let records = response.answers();
|
||||||
|
|
||||||
@ -91,9 +91,9 @@ impl DNSChecker {
|
|||||||
// check that the TXT record with the challenge token is correctly installed on the domain
|
// check that the TXT record with the challenge token is correctly installed on the domain
|
||||||
{
|
{
|
||||||
let domain = Name::from_str("_domiply_challenge")
|
let domain = Name::from_str("_domiply_challenge")
|
||||||
.map_unexpected()?
|
.or_unexpected_while("parsing TXT name")?
|
||||||
.append_domain(domain)
|
.append_domain(domain)
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("appending domain to TXT")?;
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.client
|
.client
|
||||||
@ -101,7 +101,7 @@ impl DNSChecker {
|
|||||||
.await
|
.await
|
||||||
.query(domain, DNSClass::IN, RecordType::TXT)
|
.query(domain, DNSClass::IN, RecordType::TXT)
|
||||||
.await
|
.await
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("querying TXT record")?;
|
||||||
|
|
||||||
let records = response.answers();
|
let records = response.answers();
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{fs, io, sync};
|
use std::{fs, io, sync};
|
||||||
|
|
||||||
use crate::error::{MapUnexpected, ToUnexpected};
|
use crate::error::unexpected::{self, Intoable, Mappable};
|
||||||
use crate::{domain, error, origin};
|
use crate::{domain, origin};
|
||||||
|
|
||||||
use hex::ToHex;
|
use hex::ToHex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -16,9 +16,9 @@ pub struct Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn hash(&self) -> Result<String, error::Unexpected> {
|
pub fn hash(&self) -> Result<String, unexpected::Error> {
|
||||||
let mut h = Sha256::new();
|
let mut h = Sha256::new();
|
||||||
serde_json::to_writer(&mut h, self).map_unexpected()?;
|
serde_json::to_writer(&mut h, self).or_unexpected()?;
|
||||||
Ok(h.finalize().encode_hex::<String>())
|
Ok(h.finalize().encode_hex::<String>())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -29,17 +29,17 @@ pub enum GetError {
|
|||||||
NotFound,
|
NotFound,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum SetError {
|
pub enum SetError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used in the return from all_domains from Store.
|
/// Used in the return from all_domains from Store.
|
||||||
pub type AllDomainsResult<T> = Result<T, error::Unexpected>;
|
pub type AllDomainsResult<T> = Result<T, unexpected::Error>;
|
||||||
|
|
||||||
#[mockall::automock]
|
#[mockall::automock]
|
||||||
pub trait Store {
|
pub trait Store {
|
||||||
@ -75,37 +75,46 @@ impl BoxedStore for sync::Arc<FSStore> {}
|
|||||||
|
|
||||||
impl Store for sync::Arc<FSStore> {
|
impl Store for sync::Arc<FSStore> {
|
||||||
fn get(&self, domain: &domain::Name) -> Result<Config, GetError> {
|
fn get(&self, domain: &domain::Name) -> Result<Config, GetError> {
|
||||||
let config_file =
|
let path = self.config_file_path(domain);
|
||||||
fs::File::open(self.config_file_path(domain)).map_err(|e| match e.kind() {
|
let config_file = fs::File::open(path.as_path()).map_err(|e| match e.kind() {
|
||||||
io::ErrorKind::NotFound => GetError::NotFound,
|
io::ErrorKind::NotFound => GetError::NotFound,
|
||||||
_ => e.to_unexpected().into(),
|
_ => e
|
||||||
|
.into_unexpected_while(format!("opening {}", path.display()))
|
||||||
|
.into(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let config = serde_json::from_reader(config_file).map_unexpected()?;
|
let config = serde_json::from_reader(config_file)
|
||||||
|
.map_unexpected_while(|| format!("json parsing {}", path.display()))?;
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(&self, domain: &domain::Name, config: &Config) -> Result<(), SetError> {
|
fn set(&self, domain: &domain::Name, config: &Config) -> Result<(), SetError> {
|
||||||
fs::create_dir_all(self.config_dir_path(domain)).map_unexpected()?;
|
let dir_path = self.config_dir_path(domain);
|
||||||
|
fs::create_dir_all(dir_path.as_path())
|
||||||
|
.map_unexpected_while(|| format!("creating dir {}", dir_path.display()))?;
|
||||||
|
|
||||||
let config_file = fs::File::create(self.config_file_path(domain)).map_unexpected()?;
|
let file_path = self.config_file_path(domain);
|
||||||
|
let config_file = fs::File::create(file_path.as_path())
|
||||||
|
.map_unexpected_while(|| format!("creating file {}", file_path.display()))?;
|
||||||
|
|
||||||
serde_json::to_writer(config_file, config).map_unexpected()?;
|
serde_json::to_writer(config_file, config)
|
||||||
|
.map_unexpected_while(|| format!("writing config to {}", file_path.display()))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_domains(&self) -> AllDomainsResult<Vec<AllDomainsResult<domain::Name>>> {
|
fn all_domains(&self) -> AllDomainsResult<Vec<AllDomainsResult<domain::Name>>> {
|
||||||
Ok(fs::read_dir(&self.dir_path)
|
Ok(fs::read_dir(&self.dir_path)
|
||||||
.map_unexpected()?
|
.or_unexpected()?
|
||||||
.map(
|
.map(
|
||||||
|dir_entry_res: io::Result<fs::DirEntry>| -> AllDomainsResult<domain::Name> {
|
|dir_entry_res: io::Result<fs::DirEntry>| -> AllDomainsResult<domain::Name> {
|
||||||
let domain = dir_entry_res.map_unexpected()?.file_name();
|
let domain = dir_entry_res.or_unexpected()?.file_name();
|
||||||
let domain = domain.to_str().ok_or_else(|| {
|
let domain = domain.to_str().ok_or(unexpected::Error::from(
|
||||||
error::Unexpected::from("couldn't convert os string to &str")
|
"couldn't convert os string to &str",
|
||||||
})?;
|
))?;
|
||||||
|
|
||||||
domain::Name::from_str(domain).map_unexpected()
|
domain::Name::from_str(domain)
|
||||||
|
.map_unexpected_while(|| format!("parsing {domain} as domain name"))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.collect())
|
.collect())
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::domain::{self, acme, checker, config};
|
use crate::domain::{self, acme, checker, config};
|
||||||
use crate::error::{MapUnexpected, ToUnexpected};
|
use crate::error::unexpected::{self, Intoable, Mappable};
|
||||||
use crate::{error, origin};
|
use crate::origin;
|
||||||
|
|
||||||
use std::{future, pin, sync};
|
use std::{future, pin, sync};
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ pub enum GetConfigError {
|
|||||||
NotFound,
|
NotFound,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<config::GetError> for GetConfigError {
|
impl From<config::GetError> for GetConfigError {
|
||||||
@ -28,7 +28,7 @@ pub enum GetOriginError {
|
|||||||
NotFound,
|
NotFound,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<config::GetError> for GetOriginError {
|
impl From<config::GetError> for GetOriginError {
|
||||||
@ -49,7 +49,7 @@ pub enum SyncError {
|
|||||||
AlreadyInProgress,
|
AlreadyInProgress,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<config::GetError> for SyncError {
|
impl From<config::GetError> for SyncError {
|
||||||
@ -79,7 +79,7 @@ pub enum SyncWithConfigError {
|
|||||||
ChallengeTokenNotSet,
|
ChallengeTokenNotSet,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<origin::store::SyncError> for SyncWithConfigError {
|
impl From<origin::store::SyncError> for SyncWithConfigError {
|
||||||
@ -120,7 +120,7 @@ pub type AllDomainsResult<T> = config::AllDomainsResult<T>;
|
|||||||
#[mockall::automock(
|
#[mockall::automock(
|
||||||
type Origin=origin::MockOrigin;
|
type Origin=origin::MockOrigin;
|
||||||
type SyncWithConfigFuture=future::Ready<Result<(), SyncWithConfigError>>;
|
type SyncWithConfigFuture=future::Ready<Result<(), SyncWithConfigError>>;
|
||||||
type SyncAllOriginsErrorsIter=Vec<(Option<origin::Descr>,error::Unexpected)>;
|
type SyncAllOriginsErrorsIter=Vec<unexpected::Error>;
|
||||||
)]
|
)]
|
||||||
pub trait Manager {
|
pub trait Manager {
|
||||||
type Origin<'mgr>: origin::Origin + 'mgr
|
type Origin<'mgr>: origin::Origin + 'mgr
|
||||||
@ -134,8 +134,7 @@ pub trait Manager {
|
|||||||
where
|
where
|
||||||
Self: 'mgr;
|
Self: 'mgr;
|
||||||
|
|
||||||
type SyncAllOriginsErrorsIter<'mgr>: IntoIterator<Item = (Option<origin::Descr>, error::Unexpected)>
|
type SyncAllOriginsErrorsIter<'mgr>: IntoIterator<Item = unexpected::Error> + 'mgr
|
||||||
+ 'mgr
|
|
||||||
where
|
where
|
||||||
Self: 'mgr;
|
Self: 'mgr;
|
||||||
|
|
||||||
@ -149,7 +148,7 @@ pub trait Manager {
|
|||||||
config: config::Config,
|
config: config::Config,
|
||||||
) -> Self::SyncWithConfigFuture<'_>;
|
) -> Self::SyncWithConfigFuture<'_>;
|
||||||
|
|
||||||
fn sync_all_origins(&self) -> Result<Self::SyncAllOriginsErrorsIter<'_>, error::Unexpected>;
|
fn sync_all_origins(&self) -> Result<Self::SyncAllOriginsErrorsIter<'_>, unexpected::Error>;
|
||||||
|
|
||||||
fn get_acme_http01_challenge_key(
|
fn get_acme_http01_challenge_key(
|
||||||
&self,
|
&self,
|
||||||
@ -214,7 +213,7 @@ where
|
|||||||
type SyncWithConfigFuture<'mgr> = pin::Pin<Box<dyn future::Future<Output = Result<(), SyncWithConfigError>> + Send + 'mgr>>
|
type SyncWithConfigFuture<'mgr> = pin::Pin<Box<dyn future::Future<Output = Result<(), SyncWithConfigError>> + Send + 'mgr>>
|
||||||
where Self: 'mgr;
|
where Self: 'mgr;
|
||||||
|
|
||||||
type SyncAllOriginsErrorsIter<'mgr> = Box<dyn Iterator<Item = (Option<origin::Descr>, error::Unexpected)> + 'mgr>
|
type SyncAllOriginsErrorsIter<'mgr> = Box<dyn Iterator<Item = unexpected::Error> + 'mgr>
|
||||||
where Self: 'mgr;
|
where Self: 'mgr;
|
||||||
|
|
||||||
fn get_config(&self, domain: &domain::Name) -> Result<config::Config, GetConfigError> {
|
fn get_config(&self, domain: &domain::Name) -> Result<config::Config, GetConfigError> {
|
||||||
@ -227,7 +226,7 @@ where
|
|||||||
.origin_store
|
.origin_store
|
||||||
.get(config.origin_descr)
|
.get(config.origin_descr)
|
||||||
// if there's a config there should be an origin, any error here is unexpected
|
// if there's a config there should be an origin, any error here is unexpected
|
||||||
.map_unexpected()?;
|
.or_unexpected()?;
|
||||||
Ok(origin)
|
Ok(origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +236,9 @@ where
|
|||||||
config: config::Config,
|
config: config::Config,
|
||||||
) -> Self::SyncWithConfigFuture<'_> {
|
) -> Self::SyncWithConfigFuture<'_> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let config_hash = config.hash().map_unexpected()?;
|
let config_hash = config
|
||||||
|
.hash()
|
||||||
|
.or_unexpected_while("calculating config hash")?;
|
||||||
|
|
||||||
self.domain_checker
|
self.domain_checker
|
||||||
.check_domain(&domain, &config_hash)
|
.check_domain(&domain, &config_hash)
|
||||||
@ -256,12 +257,16 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync_all_origins(&self) -> Result<Self::SyncAllOriginsErrorsIter<'_>, error::Unexpected> {
|
fn sync_all_origins(&self) -> Result<Self::SyncAllOriginsErrorsIter<'_>, unexpected::Error> {
|
||||||
let iter = self.origin_store.all_descrs().map_unexpected()?.into_iter();
|
let iter = self
|
||||||
|
.origin_store
|
||||||
|
.all_descrs()
|
||||||
|
.or_unexpected_while("fetching all origin descrs")?
|
||||||
|
.into_iter();
|
||||||
|
|
||||||
Ok(Box::from(iter.filter_map(|descr| {
|
Ok(Box::from(iter.filter_map(|descr| {
|
||||||
if let Err(err) = descr {
|
if let Err(err) = descr {
|
||||||
return Some((None, err.to_unexpected()));
|
return Some(err.into_unexpected());
|
||||||
}
|
}
|
||||||
|
|
||||||
let descr = descr.unwrap();
|
let descr = descr.unwrap();
|
||||||
@ -270,7 +275,7 @@ where
|
|||||||
.origin_store
|
.origin_store
|
||||||
.sync(descr.clone(), origin::store::Limits {})
|
.sync(descr.clone(), origin::store::Limits {})
|
||||||
{
|
{
|
||||||
return Some((Some(descr), err.to_unexpected()));
|
return Some(err.into_unexpected_while(format!("syncing store {:?}", descr)));
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
57
src/error.rs
57
src/error.rs
@ -1,56 +1 @@
|
|||||||
use std::error;
|
pub mod unexpected;
|
||||||
use std::fmt;
|
|
||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
/// Unexpected is a String which implements the Error trait. It is intended to be used in
|
|
||||||
/// situations where the caller is being given an error they can't really handle, except to pass it
|
|
||||||
/// along or log it.
|
|
||||||
///
|
|
||||||
/// The error is intended to also implement Send + Sync + Clone, such that it is easy to use in
|
|
||||||
/// async situations.
|
|
||||||
pub struct Unexpected(String);
|
|
||||||
|
|
||||||
impl fmt::Display for Unexpected {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "Unexpected error occurred: {}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for Unexpected {}
|
|
||||||
|
|
||||||
impl From<&str> for Unexpected {
|
|
||||||
fn from(s: &str) -> Self {
|
|
||||||
Unexpected(s.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ToUnexpected {
|
|
||||||
fn to_unexpected(&self) -> Unexpected;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: error::Error> ToUnexpected for E {
|
|
||||||
fn to_unexpected(&self) -> Unexpected {
|
|
||||||
let mut w = String::new();
|
|
||||||
write!(w, "{}", self.to_string()).expect("underlying error formatted correctly");
|
|
||||||
|
|
||||||
if let Some(src) = self.source() {
|
|
||||||
write!(w, " [{src}]").expect("underyling source formatted correctly");
|
|
||||||
}
|
|
||||||
|
|
||||||
Unexpected(w.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MapUnexpected<T> {
|
|
||||||
fn map_unexpected(self) -> Result<T, Unexpected>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E: ToUnexpected> MapUnexpected<T> for Result<T, E> {
|
|
||||||
fn map_unexpected(self) -> Result<T, Unexpected> {
|
|
||||||
match self {
|
|
||||||
Ok(t) => Ok(t),
|
|
||||||
Err(err) => Err(err.to_unexpected()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
128
src/error/unexpected.rs
Normal file
128
src/error/unexpected.rs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
use std::fmt::Write;
|
||||||
|
use std::{error, fmt};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
/// Error is a String which implements the Error trait. It is intended to be used in
|
||||||
|
/// situations where the caller is being given an error they can't really handle, except to pass it
|
||||||
|
/// along or log it.
|
||||||
|
///
|
||||||
|
/// The error is intended to also implement Send + Sync + Clone, such that it is easy to use in
|
||||||
|
/// async situations.
|
||||||
|
pub struct Error(String);
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
fn from_displays<D1, D2, D3>(prefix: Option<D1>, body: &D2, source: Option<D3>) -> Error
|
||||||
|
where
|
||||||
|
D1: fmt::Display,
|
||||||
|
D2: fmt::Display + ?Sized,
|
||||||
|
D3: fmt::Display,
|
||||||
|
{
|
||||||
|
let mut w = String::new();
|
||||||
|
|
||||||
|
if let Some(prefix) = prefix {
|
||||||
|
write!(w, "{prefix}").expect("error writing prefix");
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(w, "{body}").expect("error formatting body");
|
||||||
|
|
||||||
|
if let Some(source) = source {
|
||||||
|
write!(w, " [{source}]").expect("error formatting source");
|
||||||
|
}
|
||||||
|
|
||||||
|
Error(w.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_err<E, D>(prefix: Option<D>, e: E) -> Error
|
||||||
|
where
|
||||||
|
E: error::Error,
|
||||||
|
D: fmt::Display,
|
||||||
|
{
|
||||||
|
return Error::from_displays(prefix, &e, e.source());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from(s: &str) -> Error {
|
||||||
|
Error::from_displays(None::<String>, s, None::<String>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Unexpected error occurred: {}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for Error {}
|
||||||
|
|
||||||
|
pub trait Mappable<T> {
|
||||||
|
/// or_unexpected returns an Err(Error) wrapping self's Err, or the Ok value of self.
|
||||||
|
fn or_unexpected(self) -> Result<T, Error>;
|
||||||
|
|
||||||
|
/// or_unexpected_while is like or_unexpected, but will prefix the error message. The prefix
|
||||||
|
/// should be worded as if it started with the word "while", e.g.: `opening file {path}`.
|
||||||
|
fn or_unexpected_while<D: fmt::Display>(self, prefix: D) -> Result<T, Error>;
|
||||||
|
|
||||||
|
/// map_unexpected_while is like or_unexpected_while, but uses a closure to produce the error
|
||||||
|
/// prefix.
|
||||||
|
fn map_unexpected_while<F, D>(self, f: F) -> Result<T, Error>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> D,
|
||||||
|
D: fmt::Display;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_unexpected_maybe_while<T, E, F, D>(
|
||||||
|
res: Result<T, E>,
|
||||||
|
prefix_fn: Option<F>,
|
||||||
|
) -> Result<T, Error>
|
||||||
|
where
|
||||||
|
E: error::Error,
|
||||||
|
F: FnOnce() -> D,
|
||||||
|
D: std::fmt::Display,
|
||||||
|
{
|
||||||
|
match res {
|
||||||
|
Ok(res) => Ok(res),
|
||||||
|
Err(err) => Err(Error::from_err(prefix_fn.map(|prefix_fn| prefix_fn()), err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E: error::Error> Mappable<T> for Result<T, E> {
|
||||||
|
fn or_unexpected(self) -> Result<T, Error> {
|
||||||
|
let no_fn = None::<Box<dyn FnOnce() -> Box<dyn fmt::Display>>>; // lol, good job rust
|
||||||
|
map_unexpected_maybe_while(self, no_fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn or_unexpected_while<D: fmt::Display>(self, prefix: D) -> Result<T, Error> {
|
||||||
|
map_unexpected_maybe_while(self, Some(|| prefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_unexpected_while<F, D>(self, f: F) -> Result<T, Error>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> D,
|
||||||
|
D: fmt::Display,
|
||||||
|
{
|
||||||
|
map_unexpected_maybe_while(self, Some(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Intoable {
|
||||||
|
fn into_unexpected(self) -> Error;
|
||||||
|
|
||||||
|
/// into_unexpected_while is like to_unexpected, but will prefix the Error error. The
|
||||||
|
/// prefix should be worded as if it started with the word "while", e.g.: `opening file
|
||||||
|
/// {path}`.
|
||||||
|
fn into_unexpected_while<D>(self, prefix: D) -> Error
|
||||||
|
where
|
||||||
|
D: fmt::Display;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: error::Error> Intoable for E {
|
||||||
|
fn into_unexpected(self) -> Error {
|
||||||
|
Error::from_err(None::<String>, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_unexpected_while<D>(self, prefix: D) -> Error
|
||||||
|
where
|
||||||
|
D: fmt::Display,
|
||||||
|
{
|
||||||
|
Error::from_err(Some(prefix), self)
|
||||||
|
}
|
||||||
|
}
|
@ -4,3 +4,4 @@ pub mod domain;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod origin;
|
pub mod origin;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
|
pub mod util;
|
||||||
|
21
src/main.rs
21
src/main.rs
@ -98,8 +98,8 @@ async fn main() {
|
|||||||
let canceller = canceller.clone();
|
let canceller = canceller.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut signals =
|
let mut signals = Signals::new(signal_hook::consts::TERM_SIGNALS)
|
||||||
Signals::new(signal_hook::consts::TERM_SIGNALS).expect("initialized signals");
|
.expect("initializing signals failed");
|
||||||
|
|
||||||
if (signals.next().await).is_some() {
|
if (signals.next().await).is_some() {
|
||||||
log::info!("Gracefully shutting down...");
|
log::info!("Gracefully shutting down...");
|
||||||
@ -114,23 +114,23 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let origin_store = domiply::origin::store::git::new(config.origin_store_git_dir_path)
|
let origin_store = domiply::origin::store::git::new(config.origin_store_git_dir_path)
|
||||||
.expect("git origin store initialized");
|
.expect("git origin store initialization failed");
|
||||||
|
|
||||||
let domain_checker = domiply::domain::checker::new(
|
let domain_checker = domiply::domain::checker::new(
|
||||||
config.domain_checker_target_a,
|
config.domain_checker_target_a,
|
||||||
&config.domain_checker_resolver_addr,
|
&config.domain_checker_resolver_addr,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("domain checker initialized");
|
.expect("domain checker initialization failed");
|
||||||
|
|
||||||
let domain_config_store = domiply::domain::config::new(&config.domain_config_store_dir_path)
|
let domain_config_store = domiply::domain::config::new(&config.domain_config_store_dir_path)
|
||||||
.expect("domain config store initialized");
|
.expect("domain config store initialization failed");
|
||||||
|
|
||||||
let https_params = if let Some(https_listen_addr) = config.https_listen_addr {
|
let https_params = if let Some(https_listen_addr) = config.https_listen_addr {
|
||||||
let domain_acme_store_dir_path = config.domain_acme_store_dir_path.unwrap();
|
let domain_acme_store_dir_path = config.domain_acme_store_dir_path.unwrap();
|
||||||
|
|
||||||
let domain_acme_store = domiply::domain::acme::store::new(&domain_acme_store_dir_path)
|
let domain_acme_store = domiply::domain::acme::store::new(&domain_acme_store_dir_path)
|
||||||
.expect("domain acme store initialized");
|
.expect("domain acme store initialization failed");
|
||||||
|
|
||||||
// if https_listen_addr is set then domain_acme_contact_email is required, see the Cli/clap
|
// if https_listen_addr is set then domain_acme_contact_email is required, see the Cli/clap
|
||||||
// settings.
|
// settings.
|
||||||
@ -141,7 +141,7 @@ async fn main() {
|
|||||||
&domain_acme_contact_email,
|
&domain_acme_contact_email,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("domain acme manager initialized");
|
.expect("domain acme manager initialization failed");
|
||||||
|
|
||||||
Some(HTTPSParams {
|
Some(HTTPSParams {
|
||||||
https_listen_addr,
|
https_listen_addr,
|
||||||
@ -182,10 +182,7 @@ async fn main() {
|
|||||||
errors_iter
|
errors_iter
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|(descr, err)| match descr {
|
.for_each(|err| log::error!("syncing failed: {err}"));
|
||||||
None => log::error!("Error while syncing unknown descr: {err}"),
|
|
||||||
Some(descr) => log::error!("Failed to sync {descr:?}: {err}"),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -323,7 +320,7 @@ async fn main() {
|
|||||||
|
|
||||||
let addr = https_params.https_listen_addr;
|
let addr = https_params.https_listen_addr;
|
||||||
let addr_incoming = hyper::server::conn::AddrIncoming::bind(&addr)
|
let addr_incoming = hyper::server::conn::AddrIncoming::bind(&addr)
|
||||||
.expect("https listen socket created");
|
.expect("https listen socket creation failed");
|
||||||
|
|
||||||
let incoming =
|
let incoming =
|
||||||
tls_listener::TlsListener::new(server_config, addr_incoming).filter(|conn| {
|
tls_listener::TlsListener::new(server_config, addr_incoming).filter(|conn| {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::error;
|
use crate::error::unexpected;
|
||||||
|
|
||||||
mod descr;
|
mod descr;
|
||||||
pub use self::descr::Descr;
|
pub use self::descr::Descr;
|
||||||
@ -10,7 +10,7 @@ pub enum ReadFileIntoError {
|
|||||||
FileNotFound,
|
FileNotFound,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[mockall::automock]
|
#[mockall::automock]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::{error, origin};
|
use crate::error::unexpected;
|
||||||
|
use crate::origin;
|
||||||
|
|
||||||
pub mod git;
|
pub mod git;
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ pub enum SyncError {
|
|||||||
AlreadyInProgress,
|
AlreadyInProgress,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
@ -28,13 +29,13 @@ pub enum GetError {
|
|||||||
NotFound,
|
NotFound,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum AllDescrsError {
|
pub enum AllDescrsError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used in the return from all_descrs from Store.
|
/// Used in the return from all_descrs from Store.
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use crate::error::{MapUnexpected, ToUnexpected};
|
use crate::error::unexpected::{self, Intoable, Mappable};
|
||||||
use crate::origin::store;
|
use crate::origin::{self, store};
|
||||||
use crate::{error, origin};
|
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{collections, fs, io, sync};
|
use std::{collections, fs, io, sync};
|
||||||
@ -28,16 +27,23 @@ impl origin::Origin for sync::Arc<Origin> {
|
|||||||
|
|
||||||
let file_object = repo
|
let file_object = repo
|
||||||
.find_object(self.tree_object_id)
|
.find_object(self.tree_object_id)
|
||||||
.map_unexpected()?
|
.map_unexpected_while(|| format!("finding tree object {}", self.tree_object_id))?
|
||||||
.peel_to_tree()
|
.peel_to_tree()
|
||||||
.map_unexpected()?
|
.map_unexpected_while(|| format!("peeling tree object {}", self.tree_object_id))?
|
||||||
.lookup_entry_by_path(clean_path)
|
.lookup_entry_by_path(clean_path)
|
||||||
.map_unexpected()?
|
.map_unexpected_while(|| {
|
||||||
|
format!(
|
||||||
|
"looking up {} in tree object {}",
|
||||||
|
clean_path.display(),
|
||||||
|
self.tree_object_id
|
||||||
|
)
|
||||||
|
})?
|
||||||
.ok_or(origin::ReadFileIntoError::FileNotFound)?
|
.ok_or(origin::ReadFileIntoError::FileNotFound)?
|
||||||
.object()
|
.object()
|
||||||
.map_unexpected()?;
|
.or_unexpected()?;
|
||||||
|
|
||||||
into.write_all(file_object.data.as_ref()).map_unexpected()?;
|
into.write_all(file_object.data.as_ref())
|
||||||
|
.or_unexpected_while("copying out file")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -49,7 +55,7 @@ enum GetOriginError {
|
|||||||
InvalidBranchName,
|
InvalidBranchName,
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Unexpected(#[from] error::Unexpected),
|
Unexpected(#[from] unexpected::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// git::Store implements the Store trait for any Descr::Git based Origins. If any non-git
|
/// git::Store implements the Store trait for any Descr::Git based Origins. If any non-git
|
||||||
@ -95,19 +101,21 @@ impl Store {
|
|||||||
ref branch_name, ..
|
ref branch_name, ..
|
||||||
} = descr;
|
} = descr;
|
||||||
|
|
||||||
|
let branch_ref = self.branch_ref(branch_name);
|
||||||
|
|
||||||
let commit_object_id = repo
|
let commit_object_id = repo
|
||||||
.try_find_reference(&self.branch_ref(branch_name))
|
.try_find_reference(&branch_ref)
|
||||||
.map_unexpected()?
|
.map_unexpected_while(|| format!("finding branch ref {branch_ref}"))?
|
||||||
.ok_or(GetOriginError::InvalidBranchName)?
|
.ok_or(GetOriginError::InvalidBranchName)?
|
||||||
.peel_to_id_in_place()
|
.peel_to_id_in_place()
|
||||||
.map_unexpected()?
|
.or_unexpected_while("peeling id in place")?
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let tree_object_id = repo
|
let tree_object_id = repo
|
||||||
.find_object(commit_object_id)
|
.find_object(commit_object_id)
|
||||||
.map_unexpected()?
|
.map_unexpected_while(|| format!("finding commit object {commit_object_id}"))?
|
||||||
.try_to_commit_ref()
|
.try_to_commit_ref()
|
||||||
.map_unexpected()?
|
.map_unexpected_while(|| format!("parsing {commit_object_id} as commit"))?
|
||||||
.tree();
|
.tree();
|
||||||
|
|
||||||
Ok(sync::Arc::from(Origin {
|
Ok(sync::Arc::from(Origin {
|
||||||
@ -131,7 +139,8 @@ impl Store {
|
|||||||
// if the path doesn't exist then use the gix clone feature to clone it into the
|
// if the path doesn't exist then use the gix clone feature to clone it into the
|
||||||
// directory.
|
// directory.
|
||||||
if fs::read_dir(repo_path).is_err() {
|
if fs::read_dir(repo_path).is_err() {
|
||||||
fs::create_dir_all(repo_path).map_unexpected()?;
|
fs::create_dir_all(repo_path)
|
||||||
|
.map_unexpected_while(|| format!("creating {}", repo_path.display()))?;
|
||||||
|
|
||||||
let origin::Descr::Git {
|
let origin::Descr::Git {
|
||||||
ref url,
|
ref url,
|
||||||
@ -146,43 +155,53 @@ impl Store {
|
|||||||
gixCloneErr::UrlParse(_) | gixCloneErr::CanonicalizeUrl { .. } => {
|
gixCloneErr::UrlParse(_) | gixCloneErr::CanonicalizeUrl { .. } => {
|
||||||
store::SyncError::InvalidURL
|
store::SyncError::InvalidURL
|
||||||
}
|
}
|
||||||
_ => e.to_unexpected().into(),
|
_ => e
|
||||||
|
.into_unexpected_while(format!(
|
||||||
|
"cloning {} into {}",
|
||||||
|
url,
|
||||||
|
repo_path.display()
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
})?
|
})?
|
||||||
.fetch_only(Discard, should_interrupt)
|
.fetch_only(Discard, should_interrupt)
|
||||||
.map_err(|_| store::SyncError::InvalidURL)?;
|
.map_err(|_| store::SyncError::InvalidURL)?;
|
||||||
|
|
||||||
// Check to make sure the branch name exists
|
// Check to make sure the branch name exists
|
||||||
// TODO if this fails we should delete repo_path
|
// TODO if this fails we should delete repo_path
|
||||||
repo.try_find_reference(&self.branch_ref(branch_name))
|
let branch_ref = self.branch_ref(branch_name);
|
||||||
.map_unexpected()?
|
repo.try_find_reference(&branch_ref)
|
||||||
|
.map_unexpected_while(|| format!("finding branch ref {branch_ref}"))?
|
||||||
.ok_or(store::SyncError::InvalidBranchName)?;
|
.ok_or(store::SyncError::InvalidBranchName)?;
|
||||||
|
|
||||||
// Add the descr to the repo directory, so we can know the actual descr later
|
// Add the descr to the repo directory, so we can know the actual descr later
|
||||||
// TODO if this fails we should delete repo_path
|
// TODO if this fails we should delete repo_path
|
||||||
let descr_file =
|
let file_path = self.descr_file_path(descr.id().as_ref());
|
||||||
fs::File::create(self.descr_file_path(descr.id().as_ref())).map_unexpected()?;
|
let descr_file = fs::File::create(&file_path)
|
||||||
|
.map_unexpected_while(|| format!("creating {}", file_path.display()))?;
|
||||||
|
|
||||||
serde_json::to_writer(descr_file, &descr).map_unexpected()?;
|
serde_json::to_writer(descr_file, &descr)
|
||||||
|
.map_unexpected_while(|| format!("writing descr to {}", file_path.display()))?;
|
||||||
|
|
||||||
return Ok(repo);
|
return Ok(repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
let direction = gix::remote::Direction::Fetch;
|
let direction = gix::remote::Direction::Fetch;
|
||||||
|
|
||||||
let repo = gix::open(repo_path).map_unexpected()?;
|
let repo = gix::open(repo_path)
|
||||||
|
.map_unexpected_while(|| format!("opening repo at {}", repo_path.display()))?;
|
||||||
|
|
||||||
let remote = repo
|
let remote = repo
|
||||||
.find_default_remote(direction)
|
.find_default_remote(direction)
|
||||||
.ok_or_else(|| error::Unexpected::from("no default configured"))?
|
.ok_or_else(|| unexpected::Error::from("no default configured"))?
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("finding default remote for fetching")?;
|
||||||
|
|
||||||
remote
|
remote
|
||||||
.connect(direction)
|
.connect(direction)
|
||||||
.map_unexpected()?
|
.or_unexpected_while("connecting to remote")?
|
||||||
.prepare_fetch(Discard, Default::default())
|
.prepare_fetch(Discard, Default::default())
|
||||||
.map_unexpected()?
|
.or_unexpected_while("preparing fetch")?
|
||||||
.receive(Discard, should_interrupt)
|
.receive(Discard, should_interrupt)
|
||||||
.map_unexpected()?;
|
.or_unexpected_while("fetching from remote")?;
|
||||||
|
|
||||||
Ok(repo)
|
Ok(repo)
|
||||||
}
|
}
|
||||||
@ -253,15 +272,18 @@ impl super::Store for sync::Arc<Store> {
|
|||||||
|
|
||||||
fs::read_dir(&repo_path).map_err(|e| match e.kind() {
|
fs::read_dir(&repo_path).map_err(|e| match e.kind() {
|
||||||
io::ErrorKind::NotFound => store::GetError::NotFound,
|
io::ErrorKind::NotFound => store::GetError::NotFound,
|
||||||
_ => e.to_unexpected().into(),
|
_ => e
|
||||||
|
.into_unexpected_while(format!("checking if {} exists", repo_path.display()))
|
||||||
|
.into(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let repo = gix::open(&repo_path).map_unexpected()?;
|
let repo = gix::open(&repo_path)
|
||||||
|
.map_unexpected_while(|| format!("opening {} as git repo", repo_path.display()))?;
|
||||||
|
|
||||||
let origin = self.get_origin(repo, descr.clone()).map_err(|e| match e {
|
let origin = self.get_origin(repo, descr.clone()).map_err(|e| match e {
|
||||||
// it's not expected that the branch name is invalid at this point, it must have
|
// it's not expected that the branch name is invalid at this point, it must have
|
||||||
// existed for sync to have been successful.
|
// existed for sync to have been successful.
|
||||||
GetOriginError::InvalidBranchName => e.to_unexpected().into(),
|
GetOriginError::InvalidBranchName => e.into_unexpected().into(),
|
||||||
GetOriginError::Unexpected(e) => store::GetError::Unexpected(e),
|
GetOriginError::Unexpected(e) => store::GetError::Unexpected(e),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -273,14 +295,14 @@ impl super::Store for sync::Arc<Store> {
|
|||||||
|
|
||||||
fn all_descrs(&self) -> store::AllDescrsResult<Self::AllDescrsIter<'_>> {
|
fn all_descrs(&self) -> store::AllDescrsResult<Self::AllDescrsIter<'_>> {
|
||||||
Ok(Box::from(
|
Ok(Box::from(
|
||||||
fs::read_dir(&self.dir_path).map_unexpected()?.map(
|
fs::read_dir(&self.dir_path).or_unexpected()?.map(
|
||||||
|dir_entry_res: io::Result<fs::DirEntry>| -> store::AllDescrsResult<origin::Descr> {
|
|dir_entry_res: io::Result<fs::DirEntry>| -> store::AllDescrsResult<origin::Descr> {
|
||||||
let descr_id: String = dir_entry_res
|
let descr_id: String = dir_entry_res
|
||||||
.map_unexpected()?
|
.or_unexpected()?
|
||||||
.file_name()
|
.file_name()
|
||||||
.to_str()
|
.to_str()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
error::Unexpected::from("couldn't convert os string to &str")
|
unexpected::Error::from("couldn't convert os string to &str")
|
||||||
})?
|
})?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@ -289,9 +311,14 @@ impl super::Store for sync::Arc<Store> {
|
|||||||
// TODO it's possible that opening the file will fail if syncing is
|
// TODO it's possible that opening the file will fail if syncing is
|
||||||
// still ongoing, as writing the descr file is the last step after
|
// still ongoing, as writing the descr file is the last step after
|
||||||
// initial sync has succeeded.
|
// initial sync has succeeded.
|
||||||
let descr_file = fs::File::open(descr_file_path).map_unexpected()?;
|
let descr_file = fs::File::open(descr_file_path.as_path())
|
||||||
|
.map_unexpected_while(|| {
|
||||||
|
format!("opening descr file {}", descr_file_path.display())
|
||||||
|
})?;
|
||||||
|
|
||||||
let descr = serde_json::from_reader(descr_file).map_unexpected()?;
|
let descr = serde_json::from_reader(descr_file).map_unexpected_while(|| {
|
||||||
|
format!("reading descr file {}", descr_file_path.display())
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(descr)
|
Ok(descr)
|
||||||
},
|
},
|
||||||
|
11
src/util.rs
Normal file
11
src/util.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
use std::{fs, io, path};
|
||||||
|
|
||||||
|
pub fn open_file(path: &path::Path) -> io::Result<Option<fs::File>> {
|
||||||
|
match fs::File::open(path) {
|
||||||
|
Ok(file) => Ok(Some(file)),
|
||||||
|
Err(err) => match err.kind() {
|
||||||
|
io::ErrorKind::NotFound => Ok(None),
|
||||||
|
_ => Err(err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user