Get rid of origin::Origin trait completely, move read_file_into onto the origin::Store itself
This commit is contained in:
parent
773001b158
commit
5e264093ec
@ -25,19 +25,34 @@ impl From<config::GetError> for GetConfigError {
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum GetOriginError {
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
pub enum ReadFileIntoError {
|
||||
#[error("domain not found")]
|
||||
DomainNotFound,
|
||||
|
||||
#[error("file not found")]
|
||||
FileNotFound,
|
||||
|
||||
#[error(transparent)]
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
impl From<config::GetError> for GetOriginError {
|
||||
fn from(e: config::GetError) -> GetOriginError {
|
||||
impl From<config::GetError> for ReadFileIntoError {
|
||||
fn from(e: config::GetError) -> Self {
|
||||
match e {
|
||||
config::GetError::NotFound => GetOriginError::NotFound,
|
||||
config::GetError::Unexpected(e) => GetOriginError::Unexpected(e),
|
||||
config::GetError::NotFound => Self::DomainNotFound,
|
||||
config::GetError::Unexpected(e) => Self::Unexpected(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<origin::ReadFileIntoError> for ReadFileIntoError {
|
||||
fn from(e: origin::ReadFileIntoError) -> Self {
|
||||
match e {
|
||||
origin::ReadFileIntoError::DescrNotSynced => {
|
||||
Self::Unexpected(unexpected::Error::from("origin descr not synced"))
|
||||
}
|
||||
origin::ReadFileIntoError::FileNotFound => Self::FileNotFound,
|
||||
origin::ReadFileIntoError::Unexpected(e) => Self::Unexpected(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,13 +99,13 @@ pub enum SyncWithConfigError {
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
impl From<origin::store::SyncError> for SyncWithConfigError {
|
||||
fn from(e: origin::store::SyncError) -> SyncWithConfigError {
|
||||
impl From<origin::SyncError> for SyncWithConfigError {
|
||||
fn from(e: origin::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),
|
||||
origin::SyncError::InvalidURL => SyncWithConfigError::InvalidURL,
|
||||
origin::SyncError::InvalidBranchName => SyncWithConfigError::InvalidBranchName,
|
||||
origin::SyncError::AlreadyInProgress => SyncWithConfigError::AlreadyInProgress,
|
||||
origin::SyncError::Unexpected(e) => SyncWithConfigError::Unexpected(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -121,10 +136,12 @@ pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyEr
|
||||
pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert {
|
||||
fn get_config(&self, domain: &domain::Name) -> Result<config::Config, GetConfigError>;
|
||||
|
||||
fn get_origin(
|
||||
fn read_file_into(
|
||||
&self,
|
||||
domain: &domain::Name,
|
||||
) -> Result<sync::Arc<dyn origin::Origin>, GetOriginError>;
|
||||
path: &str,
|
||||
into: &mut dyn std::io::Write,
|
||||
) -> Result<(), ReadFileIntoError>;
|
||||
|
||||
fn sync_cert<'mgr>(
|
||||
&'mgr self,
|
||||
@ -146,7 +163,7 @@ pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert {
|
||||
}
|
||||
|
||||
pub struct ManagerImpl {
|
||||
origin_store: Box<dyn origin::store::Store + Send + Sync>,
|
||||
origin_store: Box<dyn origin::Store + Send + Sync>,
|
||||
domain_config_store: Box<dyn config::Store + Send + Sync>,
|
||||
domain_checker: checker::DNSChecker,
|
||||
acme_manager: Option<Box<dyn acme::manager::Manager + Send + Sync>>,
|
||||
@ -154,7 +171,7 @@ pub struct ManagerImpl {
|
||||
|
||||
impl ManagerImpl {
|
||||
pub fn new<
|
||||
OriginStore: origin::store::Store + Send + Sync + 'static,
|
||||
OriginStore: origin::Store + Send + Sync + 'static,
|
||||
DomainConfigStore: config::Store + Send + Sync + 'static,
|
||||
AcmeManager: acme::manager::Manager + Send + Sync + 'static,
|
||||
>(
|
||||
@ -212,17 +229,16 @@ impl Manager for ManagerImpl {
|
||||
Ok(self.domain_config_store.get(domain)?)
|
||||
}
|
||||
|
||||
fn get_origin(
|
||||
fn read_file_into(
|
||||
&self,
|
||||
domain: &domain::Name,
|
||||
) -> Result<sync::Arc<dyn origin::Origin>, GetOriginError> {
|
||||
path: &str,
|
||||
into: &mut dyn std::io::Write,
|
||||
) -> Result<(), ReadFileIntoError> {
|
||||
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)
|
||||
self.origin_store
|
||||
.read_file_into(&config.origin_descr, path, into)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sync_cert<'mgr>(
|
||||
|
@ -73,7 +73,7 @@ async fn main() {
|
||||
)
|
||||
.init();
|
||||
|
||||
let origin_store = domani::origin::store::git::FSStore::new(config.origin_store_git_dir_path)
|
||||
let origin_store = domani::origin::git::FSStore::new(config.origin_store_git_dir_path)
|
||||
.expect("git origin store initialization failed");
|
||||
|
||||
let domain_checker = domani::domain::checker::DNSChecker::new(
|
||||
|
@ -1,11 +1,38 @@
|
||||
use crate::error::unexpected;
|
||||
use std::sync;
|
||||
|
||||
pub mod git;
|
||||
pub mod mux;
|
||||
|
||||
mod descr;
|
||||
pub use self::descr::Descr;
|
||||
pub mod store;
|
||||
pub use descr::Descr;
|
||||
|
||||
#[derive(thiserror::Error, Clone, Debug, PartialEq)]
|
||||
pub enum SyncError {
|
||||
#[error("invalid url")]
|
||||
InvalidURL,
|
||||
|
||||
#[error("invalid branch name")]
|
||||
InvalidBranchName,
|
||||
|
||||
#[error("already in progress")]
|
||||
AlreadyInProgress,
|
||||
|
||||
#[error(transparent)]
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Clone, Debug, PartialEq)]
|
||||
pub enum AllDescrsError {
|
||||
#[error(transparent)]
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ReadFileIntoError {
|
||||
#[error("descr not synced")]
|
||||
DescrNotSynced,
|
||||
|
||||
#[error("file not found")]
|
||||
FileNotFound,
|
||||
|
||||
@ -14,12 +41,41 @@ pub enum ReadFileIntoError {
|
||||
}
|
||||
|
||||
#[mockall::automock]
|
||||
/// Describes an origin which has already been synced locally and is available for reading files
|
||||
/// from.
|
||||
pub trait Origin {
|
||||
/// Describes a storage mechanism for Origins. Each Origin is uniquely identified by its Descr.
|
||||
pub trait Store {
|
||||
/// If the origin is of a kind which can be updated, sync will pull down the latest version of
|
||||
/// the origin into the storage.
|
||||
fn sync(&self, descr: &Descr) -> Result<(), SyncError>;
|
||||
|
||||
fn read_file_into(
|
||||
&self,
|
||||
descr: &Descr,
|
||||
path: &str,
|
||||
into: &mut dyn std::io::Write,
|
||||
) -> Result<(), ReadFileIntoError>;
|
||||
|
||||
fn all_descrs(&self) -> Result<Vec<Descr>, AllDescrsError>;
|
||||
}
|
||||
|
||||
pub fn new_mock() -> sync::Arc<sync::Mutex<MockStore>> {
|
||||
sync::Arc::new(sync::Mutex::new(MockStore::new()))
|
||||
}
|
||||
|
||||
impl Store for sync::Arc<sync::Mutex<MockStore>> {
|
||||
fn sync(&self, descr: &Descr) -> Result<(), SyncError> {
|
||||
self.lock().unwrap().sync(descr)
|
||||
}
|
||||
|
||||
fn all_descrs(&self) -> Result<Vec<Descr>, AllDescrsError> {
|
||||
self.lock().unwrap().all_descrs()
|
||||
}
|
||||
|
||||
fn read_file_into(
|
||||
&self,
|
||||
descr: &Descr,
|
||||
path: &str,
|
||||
into: &mut dyn std::io::Write,
|
||||
) -> Result<(), ReadFileIntoError> {
|
||||
self.lock().unwrap().read_file_into(descr, path, into)
|
||||
}
|
||||
}
|
||||
|
@ -1,52 +1,17 @@
|
||||
use crate::error::unexpected::{self, Intoable, Mappable};
|
||||
use crate::origin::{self, store};
|
||||
use crate::origin;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{collections, fs, io, sync};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Origin {
|
||||
struct RepoSnapshot {
|
||||
repo: sync::Arc<gix::ThreadSafeRepository>,
|
||||
tree_object_id: gix::ObjectId,
|
||||
}
|
||||
|
||||
impl origin::Origin for Origin {
|
||||
fn read_file_into(
|
||||
&self,
|
||||
path: &str,
|
||||
into: &mut dyn std::io::Write,
|
||||
) -> Result<(), origin::ReadFileIntoError> {
|
||||
let mut clean_path = Path::new(path);
|
||||
clean_path = clean_path.strip_prefix("/").unwrap_or(clean_path);
|
||||
|
||||
let repo = self.repo.to_thread_local();
|
||||
|
||||
let file_object = repo
|
||||
.find_object(self.tree_object_id)
|
||||
.map_unexpected_while(|| format!("finding tree object {}", self.tree_object_id))?
|
||||
.peel_to_tree()
|
||||
.map_unexpected_while(|| format!("peeling tree object {}", self.tree_object_id))?
|
||||
.lookup_entry_by_path(clean_path)
|
||||
.map_unexpected_while(|| {
|
||||
format!(
|
||||
"looking up {} in tree object {}",
|
||||
clean_path.display(),
|
||||
self.tree_object_id
|
||||
)
|
||||
})?
|
||||
.ok_or(origin::ReadFileIntoError::FileNotFound)?
|
||||
.object()
|
||||
.or_unexpected()?;
|
||||
|
||||
into.write_all(file_object.data.as_ref())
|
||||
.or_unexpected_while("copying out file")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
enum GetOriginError {
|
||||
enum CreateRepoSnapshotError {
|
||||
#[error("invalid branch name")]
|
||||
InvalidBranchName,
|
||||
|
||||
@ -54,8 +19,8 @@ enum GetOriginError {
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
/// Implements the Store trait for any Descr::Git based Origins, storing the git repos on disk. If
|
||||
/// any non-git Descrs are used then this implementation will panic.
|
||||
/// Implements the Store trait for Descr::Git, storing the git repos on disk. If any non-git Descrs
|
||||
/// are used then this implementation will panic.
|
||||
pub struct FSStore {
|
||||
dir_path: PathBuf,
|
||||
|
||||
@ -63,7 +28,7 @@ pub struct FSStore {
|
||||
// more than one origin to be syncing at a time
|
||||
sync_guard: sync::Mutex<collections::HashMap<origin::Descr, ()>>,
|
||||
|
||||
origins: sync::RwLock<collections::HashMap<origin::Descr, sync::Arc<Origin>>>,
|
||||
repo_snapshots: sync::RwLock<collections::HashMap<origin::Descr, sync::Arc<RepoSnapshot>>>,
|
||||
}
|
||||
|
||||
impl FSStore {
|
||||
@ -72,7 +37,7 @@ impl FSStore {
|
||||
Ok(Self {
|
||||
dir_path,
|
||||
sync_guard: sync::Mutex::new(collections::HashMap::new()),
|
||||
origins: sync::RwLock::new(collections::HashMap::new()),
|
||||
repo_snapshots: sync::RwLock::new(collections::HashMap::new()),
|
||||
})
|
||||
}
|
||||
|
||||
@ -88,11 +53,11 @@ impl FSStore {
|
||||
format!("origin/{branch_name}")
|
||||
}
|
||||
|
||||
fn get_origin(
|
||||
fn create_repo_snapshot(
|
||||
&self,
|
||||
repo: gix::Repository,
|
||||
descr: &origin::Descr,
|
||||
) -> Result<Origin, GetOriginError> {
|
||||
) -> Result<RepoSnapshot, CreateRepoSnapshotError> {
|
||||
let origin::Descr::Git {
|
||||
ref branch_name, ..
|
||||
} = descr;
|
||||
@ -102,7 +67,7 @@ impl FSStore {
|
||||
let commit_object_id = repo
|
||||
.try_find_reference(&branch_ref)
|
||||
.map_unexpected_while(|| format!("finding branch ref {branch_ref}"))?
|
||||
.ok_or(GetOriginError::InvalidBranchName)?
|
||||
.ok_or(CreateRepoSnapshotError::InvalidBranchName)?
|
||||
.peel_to_id_in_place()
|
||||
.or_unexpected_while("peeling id in place")?
|
||||
.detach();
|
||||
@ -114,13 +79,60 @@ impl FSStore {
|
||||
.map_unexpected_while(|| format!("parsing {commit_object_id} as commit"))?
|
||||
.tree();
|
||||
|
||||
Ok(Origin {
|
||||
Ok(RepoSnapshot {
|
||||
repo: sync::Arc::new(repo.into()),
|
||||
tree_object_id,
|
||||
})
|
||||
}
|
||||
|
||||
fn sync_inner(&self, descr: &origin::Descr) -> Result<gix::Repository, store::SyncError> {
|
||||
fn get_repo_snapshot(
|
||||
&self,
|
||||
descr: &origin::Descr,
|
||||
) -> Result<Option<sync::Arc<RepoSnapshot>>, unexpected::Error> {
|
||||
{
|
||||
let repo_snapshots = self.repo_snapshots.read().unwrap();
|
||||
if let Some(repo_snapshot) = repo_snapshots.get(descr) {
|
||||
return Ok(Some(repo_snapshot.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let repo_path = self.repo_path(descr);
|
||||
|
||||
match fs::read_dir(&repo_path) {
|
||||
Ok(_) => (),
|
||||
Err(e) => match e.kind() {
|
||||
io::ErrorKind::NotFound => return Ok(None),
|
||||
_ => {
|
||||
return Err(e.into_unexpected_while(format!(
|
||||
"checking if {} exists",
|
||||
repo_path.display()
|
||||
)))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
let repo = gix::open(&repo_path)
|
||||
.map_unexpected_while(|| format!("opening {} as git repo", repo_path.display()))?;
|
||||
|
||||
let repo_snapshot = self
|
||||
.create_repo_snapshot(repo, descr)
|
||||
.map_err(|e| match e {
|
||||
// it's not expected that the branch name is invalid at this point, it must have
|
||||
// existed for sync to have been successful.
|
||||
CreateRepoSnapshotError::InvalidBranchName => e.into_unexpected().into(),
|
||||
CreateRepoSnapshotError::Unexpected(e) => e,
|
||||
})?;
|
||||
|
||||
let repo_snapshot = sync::Arc::new(repo_snapshot);
|
||||
|
||||
let mut repo_snapshots = self.repo_snapshots.write().unwrap();
|
||||
|
||||
(*repo_snapshots).insert(descr.clone(), repo_snapshot.clone());
|
||||
|
||||
Ok(Some(repo_snapshot))
|
||||
}
|
||||
|
||||
fn sync_inner(&self, descr: &origin::Descr) -> Result<gix::Repository, origin::SyncError> {
|
||||
use gix::clone::Error as gixCloneErr;
|
||||
use gix::progress::Discard;
|
||||
|
||||
@ -141,10 +153,10 @@ impl FSStore {
|
||||
let (repo, _) = gix::prepare_clone_bare(url.clone(), repo_path)
|
||||
.map_err(|e| match e {
|
||||
gixCloneErr::Init(gix::init::Error::InvalidBranchName { .. }) => {
|
||||
store::SyncError::InvalidBranchName
|
||||
origin::SyncError::InvalidBranchName
|
||||
}
|
||||
gixCloneErr::UrlParse(_) | gixCloneErr::CanonicalizeUrl { .. } => {
|
||||
store::SyncError::InvalidURL
|
||||
origin::SyncError::InvalidURL
|
||||
}
|
||||
_ => e
|
||||
.into_unexpected_while(format!(
|
||||
@ -155,14 +167,14 @@ impl FSStore {
|
||||
.into(),
|
||||
})?
|
||||
.fetch_only(Discard, should_interrupt)
|
||||
.map_err(|_| store::SyncError::InvalidURL)?;
|
||||
.map_err(|_| origin::SyncError::InvalidURL)?;
|
||||
|
||||
// Check to make sure the branch name exists
|
||||
// TODO if this fails we should delete repo_path
|
||||
let branch_ref = self.branch_ref(branch_name);
|
||||
repo.try_find_reference(&branch_ref)
|
||||
.map_unexpected_while(|| format!("finding branch ref {branch_ref}"))?
|
||||
.ok_or(store::SyncError::InvalidBranchName)?;
|
||||
.ok_or(origin::SyncError::InvalidBranchName)?;
|
||||
|
||||
// Add the descr to the repo directory, so we can know the actual descr later
|
||||
// TODO if this fails we should delete repo_path
|
||||
@ -199,7 +211,7 @@ impl FSStore {
|
||||
}
|
||||
|
||||
impl super::Store for FSStore {
|
||||
fn sync(&self, descr: &origin::Descr) -> Result<(), store::SyncError> {
|
||||
fn sync(&self, descr: &origin::Descr) -> Result<(), origin::SyncError> {
|
||||
// attempt to lock this descr for syncing, doing so within a new scope so the mutex
|
||||
// isn't actually being held for the whole method duration.
|
||||
let is_already_syncing = {
|
||||
@ -211,7 +223,7 @@ impl super::Store for FSStore {
|
||||
};
|
||||
|
||||
if is_already_syncing {
|
||||
return Err(store::SyncError::AlreadyInProgress);
|
||||
return Err(origin::SyncError::AlreadyInProgress);
|
||||
}
|
||||
|
||||
let res = self.sync_inner(descr);
|
||||
@ -224,7 +236,7 @@ impl super::Store for FSStore {
|
||||
};
|
||||
|
||||
// repo is synced at this point (though the sync lock is still held), just gotta create
|
||||
// the origin and store it.
|
||||
// the RepoSnapshot and store it.
|
||||
//
|
||||
// TODO this is a bit of a memory leak, but by the time we get
|
||||
// to that point this should all be backed by something which isn't local storage
|
||||
@ -232,56 +244,22 @@ impl super::Store for FSStore {
|
||||
|
||||
// calling this while the sync lock is held isn't ideal, but it's convenient and
|
||||
// shouldn't be too terrible generally
|
||||
let origin = self.get_origin(repo, descr).map_err(|e| match e {
|
||||
GetOriginError::InvalidBranchName => store::SyncError::InvalidBranchName,
|
||||
GetOriginError::Unexpected(e) => store::SyncError::Unexpected(e),
|
||||
})?;
|
||||
let repo_snapshot = self
|
||||
.create_repo_snapshot(repo, descr)
|
||||
.map_err(|e| match e {
|
||||
CreateRepoSnapshotError::InvalidBranchName => origin::SyncError::InvalidBranchName,
|
||||
CreateRepoSnapshotError::Unexpected(e) => origin::SyncError::Unexpected(e),
|
||||
})?;
|
||||
|
||||
let mut origins = self.origins.write().unwrap();
|
||||
(*origins).insert(descr.clone(), sync::Arc::new(origin));
|
||||
let mut repo_snapshots = self.repo_snapshots.write().unwrap();
|
||||
(*repo_snapshots).insert(descr.clone(), sync::Arc::new(repo_snapshot));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get(&self, descr: &origin::Descr) -> Result<sync::Arc<dyn origin::Origin>, store::GetError> {
|
||||
{
|
||||
let origins = self.origins.read().unwrap();
|
||||
if let Some(origin) = origins.get(descr) {
|
||||
return Ok(origin.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let repo_path = self.repo_path(descr);
|
||||
|
||||
fs::read_dir(&repo_path).map_err(|e| match e.kind() {
|
||||
io::ErrorKind::NotFound => store::GetError::NotFound,
|
||||
_ => e
|
||||
.into_unexpected_while(format!("checking if {} exists", repo_path.display()))
|
||||
.into(),
|
||||
})?;
|
||||
|
||||
let repo = gix::open(&repo_path)
|
||||
.map_unexpected_while(|| format!("opening {} as git repo", repo_path.display()))?;
|
||||
|
||||
let origin = self.get_origin(repo, descr).map_err(|e| match e {
|
||||
// it's not expected that the branch name is invalid at this point, it must have
|
||||
// existed for sync to have been successful.
|
||||
GetOriginError::InvalidBranchName => e.into_unexpected().into(),
|
||||
GetOriginError::Unexpected(e) => store::GetError::Unexpected(e),
|
||||
})?;
|
||||
|
||||
let origin = sync::Arc::new(origin);
|
||||
|
||||
let mut origins = self.origins.write().unwrap();
|
||||
|
||||
(*origins).insert(descr.clone(), origin.clone());
|
||||
|
||||
Ok(origin)
|
||||
}
|
||||
|
||||
fn all_descrs(&self) -> Result<Vec<origin::Descr>, store::AllDescrsError> {
|
||||
fn all_descrs(&self) -> Result<Vec<origin::Descr>, origin::AllDescrsError> {
|
||||
fs::read_dir(&self.dir_path).or_unexpected()?.map(
|
||||
|dir_entry_res: io::Result<fs::DirEntry>| -> Result<origin::Descr, store::AllDescrsError> {
|
||||
|dir_entry_res: io::Result<fs::DirEntry>| -> Result<origin::Descr, origin::AllDescrsError> {
|
||||
let descr_id: String = dir_entry_res
|
||||
.or_unexpected()?
|
||||
.file_name()
|
||||
@ -309,11 +287,55 @@ impl super::Store for FSStore {
|
||||
},
|
||||
).try_collect()
|
||||
}
|
||||
|
||||
fn read_file_into(
|
||||
&self,
|
||||
descr: &origin::Descr,
|
||||
path: &str,
|
||||
into: &mut dyn std::io::Write,
|
||||
) -> Result<(), origin::ReadFileIntoError> {
|
||||
let repo_snapshot = match self.get_repo_snapshot(descr) {
|
||||
Ok(Some(repo_snapshot)) => repo_snapshot,
|
||||
Ok(None) => return Err(origin::ReadFileIntoError::DescrNotSynced),
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
let mut clean_path = Path::new(path);
|
||||
clean_path = clean_path.strip_prefix("/").unwrap_or(clean_path);
|
||||
|
||||
let repo = repo_snapshot.repo.to_thread_local();
|
||||
|
||||
let file_object = repo
|
||||
.find_object(repo_snapshot.tree_object_id)
|
||||
.map_unexpected_while(|| {
|
||||
format!("finding tree object {}", repo_snapshot.tree_object_id)
|
||||
})?
|
||||
.peel_to_tree()
|
||||
.map_unexpected_while(|| {
|
||||
format!("peeling tree object {}", repo_snapshot.tree_object_id)
|
||||
})?
|
||||
.lookup_entry_by_path(clean_path)
|
||||
.map_unexpected_while(|| {
|
||||
format!(
|
||||
"looking up {} in tree object {}",
|
||||
clean_path.display(),
|
||||
repo_snapshot.tree_object_id
|
||||
)
|
||||
})?
|
||||
.ok_or(origin::ReadFileIntoError::FileNotFound)?
|
||||
.object()
|
||||
.or_unexpected()?;
|
||||
|
||||
into.write_all(file_object.data.as_ref())
|
||||
.or_unexpected_while("copying out file")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::origin::{self, store, store::Store};
|
||||
use crate::origin::{self, Store};
|
||||
use tempdir::TempDir;
|
||||
|
||||
#[test]
|
||||
@ -337,17 +359,10 @@ mod tests {
|
||||
store.sync(&descr).expect("sync should succeed");
|
||||
store.sync(&descr).expect("second sync should succeed");
|
||||
|
||||
assert!(matches!(
|
||||
store.get(&other_descr),
|
||||
Err::<_, store::GetError>(store::GetError::NotFound),
|
||||
));
|
||||
|
||||
let origin = store.get(&descr).expect("origin retrieved");
|
||||
|
||||
let assert_write = |path: &str| {
|
||||
let mut into: Vec<u8> = vec![];
|
||||
origin
|
||||
.read_file_into(path, &mut into)
|
||||
store
|
||||
.read_file_into(&descr, path, &mut into)
|
||||
.expect("write should succeed");
|
||||
assert!(into.len() > 0);
|
||||
};
|
||||
@ -355,10 +370,17 @@ mod tests {
|
||||
assert_write("src/lib.rs");
|
||||
assert_write("/src/lib.rs");
|
||||
|
||||
// File doesn't exist
|
||||
let mut into: Vec<u8> = vec![];
|
||||
|
||||
// RepoSnapshot doesn't exist
|
||||
assert!(matches!(
|
||||
origin.read_file_into("DNE", &mut into),
|
||||
store.read_file_into(&other_descr, "DNE", &mut into),
|
||||
Err::<_, origin::ReadFileIntoError>(origin::ReadFileIntoError::DescrNotSynced),
|
||||
));
|
||||
|
||||
// File doesn't exist
|
||||
assert!(matches!(
|
||||
store.read_file_into(&descr, "DNE", &mut into),
|
||||
Err::<(), origin::ReadFileIntoError>(origin::ReadFileIntoError::FileNotFound),
|
||||
));
|
||||
assert_eq!(into.len(), 0);
|
@ -1,10 +1,9 @@
|
||||
use crate::error::unexpected::Mappable;
|
||||
use crate::origin::{self, store};
|
||||
use std::sync;
|
||||
use crate::origin;
|
||||
|
||||
pub struct Store<F, S>
|
||||
where
|
||||
S: store::Store + 'static,
|
||||
S: origin::Store + 'static,
|
||||
F: Fn(&origin::Descr) -> Option<S> + Sync + Send,
|
||||
{
|
||||
mapping_fn: F,
|
||||
@ -13,7 +12,7 @@ where
|
||||
|
||||
impl<F, S> Store<F, S>
|
||||
where
|
||||
S: store::Store + 'static,
|
||||
S: origin::Store + 'static,
|
||||
F: Fn(&origin::Descr) -> Option<S> + Sync + Send,
|
||||
{
|
||||
pub fn new(mapping_fn: F, stores: Vec<S>) -> Store<F, S> {
|
||||
@ -21,24 +20,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, S> store::Store for Store<F, S>
|
||||
impl<F, S> origin::Store for Store<F, S>
|
||||
where
|
||||
S: store::Store + 'static,
|
||||
S: origin::Store + 'static,
|
||||
F: Fn(&origin::Descr) -> Option<S> + Sync + Send,
|
||||
{
|
||||
fn sync(&self, descr: &origin::Descr) -> Result<(), store::SyncError> {
|
||||
fn sync(&self, descr: &origin::Descr) -> Result<(), origin::SyncError> {
|
||||
(self.mapping_fn)(descr)
|
||||
.or_unexpected_while(format!("mapping {:?} to store", &descr))?
|
||||
.sync(descr)
|
||||
}
|
||||
|
||||
fn get(&self, descr: &origin::Descr) -> Result<sync::Arc<dyn origin::Origin>, store::GetError> {
|
||||
(self.mapping_fn)(descr)
|
||||
.or_unexpected_while(format!("mapping {:?} to store", &descr))?
|
||||
.get(descr)
|
||||
}
|
||||
|
||||
fn all_descrs(&self) -> Result<Vec<origin::Descr>, store::AllDescrsError> {
|
||||
fn all_descrs(&self) -> Result<Vec<origin::Descr>, origin::AllDescrsError> {
|
||||
let mut res = Vec::<origin::Descr>::new();
|
||||
|
||||
for store in self.stores.iter() {
|
||||
@ -47,20 +40,31 @@ where
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn read_file_into(
|
||||
&self,
|
||||
descr: &origin::Descr,
|
||||
path: &str,
|
||||
into: &mut dyn std::io::Write,
|
||||
) -> Result<(), origin::ReadFileIntoError> {
|
||||
(self.mapping_fn)(descr)
|
||||
.or_unexpected_while(format!("mapping {:?} to store", &descr))?
|
||||
.read_file_into(descr, path, into)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::origin::{self, store};
|
||||
use crate::origin;
|
||||
use std::sync;
|
||||
|
||||
struct Harness {
|
||||
descr_a: origin::Descr,
|
||||
descr_b: origin::Descr,
|
||||
descr_unknown: origin::Descr,
|
||||
store_a: sync::Arc<sync::Mutex<store::MockStore>>,
|
||||
store_b: sync::Arc<sync::Mutex<store::MockStore>>,
|
||||
store: Box<dyn store::Store>,
|
||||
store_a: sync::Arc<sync::Mutex<origin::MockStore>>,
|
||||
store_b: sync::Arc<sync::Mutex<origin::MockStore>>,
|
||||
store: Box<dyn origin::Store>,
|
||||
}
|
||||
|
||||
impl Harness {
|
||||
@ -75,8 +79,8 @@ mod tests {
|
||||
branch_name: "B".to_string(),
|
||||
};
|
||||
|
||||
let store_a = store::new_mock();
|
||||
let store_b = store::new_mock();
|
||||
let store_a = origin::new_mock();
|
||||
let store_b = origin::new_mock();
|
||||
|
||||
Harness {
|
||||
descr_a: descr_a.clone(),
|
||||
@ -119,7 +123,7 @@ mod tests {
|
||||
.expect_sync()
|
||||
.withf(move |descr: &origin::Descr| descr == &descr_a)
|
||||
.times(1)
|
||||
.return_const(Ok::<(), store::SyncError>(()));
|
||||
.return_const(Ok::<(), origin::SyncError>(()));
|
||||
}
|
||||
|
||||
assert_eq!(Ok(()), h.store.sync(&h.descr_a));
|
||||
@ -132,7 +136,7 @@ mod tests {
|
||||
.expect_sync()
|
||||
.withf(move |descr: &origin::Descr| descr == &descr_b)
|
||||
.times(1)
|
||||
.return_const(Ok::<(), store::SyncError>(()));
|
||||
.return_const(Ok::<(), origin::SyncError>(()));
|
||||
}
|
||||
|
||||
assert_eq!(Ok(()), h.store.sync(&h.descr_b));
|
||||
@ -149,7 +153,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.expect_all_descrs()
|
||||
.times(1)
|
||||
.return_const(Ok::<Vec<origin::Descr>, store::AllDescrsError>(vec![h
|
||||
.return_const(Ok::<Vec<origin::Descr>, origin::AllDescrsError>(vec![h
|
||||
.descr_a
|
||||
.clone()]));
|
||||
|
||||
@ -158,7 +162,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.expect_all_descrs()
|
||||
.times(1)
|
||||
.return_const(Ok::<Vec<origin::Descr>, store::AllDescrsError>(vec![h
|
||||
.return_const(Ok::<Vec<origin::Descr>, origin::AllDescrsError>(vec![h
|
||||
.descr_b
|
||||
.clone()]));
|
||||
|
@ -1,65 +0,0 @@
|
||||
use crate::error::unexpected;
|
||||
use crate::origin;
|
||||
use std::sync;
|
||||
|
||||
pub mod git;
|
||||
pub mod mux;
|
||||
|
||||
#[derive(thiserror::Error, Clone, Debug, PartialEq)]
|
||||
pub enum SyncError {
|
||||
#[error("invalid url")]
|
||||
InvalidURL,
|
||||
|
||||
#[error("invalid branch name")]
|
||||
InvalidBranchName,
|
||||
|
||||
#[error("already in progress")]
|
||||
AlreadyInProgress,
|
||||
|
||||
#[error(transparent)]
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Clone, Debug, PartialEq)]
|
||||
pub enum GetError {
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
|
||||
#[error(transparent)]
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Clone, Debug, PartialEq)]
|
||||
pub enum AllDescrsError {
|
||||
#[error(transparent)]
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
#[mockall::automock]
|
||||
/// Describes a storage mechanism for Origins. Each Origin is uniquely identified by its Descr.
|
||||
pub trait Store {
|
||||
/// If the origin is of a kind which can be updated, sync will pull down the latest version of
|
||||
/// the origin into the storage.
|
||||
fn sync(&self, descr: &origin::Descr) -> Result<(), SyncError>;
|
||||
|
||||
fn get(&self, descr: &origin::Descr) -> Result<sync::Arc<dyn origin::Origin>, GetError>;
|
||||
fn all_descrs(&self) -> Result<Vec<origin::Descr>, AllDescrsError>;
|
||||
}
|
||||
|
||||
pub fn new_mock() -> sync::Arc<sync::Mutex<MockStore>> {
|
||||
sync::Arc::new(sync::Mutex::new(MockStore::new()))
|
||||
}
|
||||
|
||||
impl Store for sync::Arc<sync::Mutex<MockStore>> {
|
||||
fn sync(&self, descr: &origin::Descr) -> Result<(), SyncError> {
|
||||
self.lock().unwrap().sync(descr)
|
||||
}
|
||||
|
||||
fn get(&self, descr: &origin::Descr) -> Result<sync::Arc<dyn origin::Origin>, GetError> {
|
||||
self.lock().unwrap().get(descr)
|
||||
}
|
||||
|
||||
fn all_descrs(&self) -> Result<Vec<origin::Descr>, AllDescrsError> {
|
||||
self.lock().unwrap().all_descrs()
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ use std::str::FromStr;
|
||||
use std::{future, net, sync};
|
||||
|
||||
use crate::error::unexpected;
|
||||
use crate::{domain, origin, service, util};
|
||||
use crate::{domain, service, util};
|
||||
|
||||
type SvcResponse = Result<Response<hyper::body::Body>, String>;
|
||||
|
||||
@ -170,23 +170,16 @@ impl<'svc> Service {
|
||||
false => path,
|
||||
};
|
||||
|
||||
let origin = match self.domain_manager.get_origin(&domain) {
|
||||
Ok(o) => o,
|
||||
Err(domain::manager::GetOriginError::NotFound) => {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
match self.domain_manager.read_file_into(&domain, path, &mut buf) {
|
||||
Ok(_) => self.serve_string(200, path, buf),
|
||||
Err(domain::manager::ReadFileIntoError::DomainNotFound) => {
|
||||
return self.render_error_page(404, "Domain not found")
|
||||
}
|
||||
Err(domain::manager::GetOriginError::Unexpected(e)) => {
|
||||
return self.render_error_page(500, format!("failed to fetch origin: {e}").as_str())
|
||||
}
|
||||
};
|
||||
|
||||
let mut buf = Vec::<u8>::new();
|
||||
match origin.read_file_into(path, &mut buf) {
|
||||
Ok(_) => self.serve_string(200, path, buf),
|
||||
Err(origin::ReadFileIntoError::FileNotFound) => {
|
||||
Err(domain::manager::ReadFileIntoError::FileNotFound) => {
|
||||
self.render_error_page(404, "File not found")
|
||||
}
|
||||
Err(origin::ReadFileIntoError::Unexpected(e)) => {
|
||||
Err(domain::manager::ReadFileIntoError::Unexpected(e)) => {
|
||||
self.render_error_page(500, format!("failed to fetch file {path}: {e}").as_str())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user