diff --git a/Cargo.toml b/Cargo.toml index 329a080..37a3f94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,9 @@ build = "src/build.rs" users = "0.11.0" regex = "1.0.1" futures-util = "0.3" -async-trait = "0.1.65" -tokio = { version = "1.26", features = ["rt", "process", "io-util", "macros", "sync"] } +async-trait = "0.1" +tokio = { version = "1.6.1", features = ["rt", "process", "io-util", "macros", "sync"] } once_cell = "1.4" -slog = { version = "2", features = ["max_level_trace", "release_max_level_trace"] } -slog-term = "2.5" -slog-async = "2.7" [dev-dependencies] tempfile = "3" diff --git a/src/async_utils.rs b/src/async_utils.rs index d4443a5..aaae4db 100644 --- a/src/async_utils.rs +++ b/src/async_utils.rs @@ -36,20 +36,26 @@ impl Future for TimerFuture { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut state = self.state.lock().unwrap(); - match *state { - State::Completed => return Poll::Ready(()), - State::NotStarted(duration) => { - let thread_state = self.state.clone(); - thread::spawn(move || { - thread::sleep(duration); - let mut state = thread_state.lock().unwrap(); - if let State::Running(waker) = std::mem::replace(&mut *state, State::Completed) { - waker.wake(); - }; - }); - } - State::Running(_) => {} - }; + if let State::Completed = *state { + return Poll::Ready(()); + } + + if let State::NotStarted(duration) = *state { + let thread_state = self.state.clone(); + thread::spawn(move || { + thread::sleep(duration); + let mut state = thread_state.lock().unwrap(); + let waker = if let State::Running(waker) = &*state { + Some(waker.clone()) + } else { + None + }; + *state = State::Completed; + if let Some(w) = waker { + w.wake(); + } + }); + } *state = State::Running(cx.waker().clone()); Poll::Pending @@ -78,10 +84,10 @@ mod test { loop { futures_util::select! { _ = sleep => {}, - _ = ok => assert!(start.elapsed().as_millis() < 100), + _ = ok => assert!((Instant::now() - start).as_millis() < 100), complete => break, } } - }); + }) } } diff --git a/src/build.rs b/src/build.rs index 772339c..02566e2 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,53 +1,57 @@ use std::env; -use std::error::Error; -use std::fs::{read as read_file, File}; +use std::fs::{read_dir, File}; use std::io::ErrorKind::NotFound; +use std::io::Read; use std::io::Write; -use std::path::Path; +use std::path::{Path, PathBuf}; -pub fn create_static_output( - source_path: &Path, - mut target: impl Write, -) -> Result<(), Box> { - let const_name = source_path - .file_stem() - .ok_or("Not a filename")? - .to_str() - .ok_or("Filename is not valid unicode")? - .to_uppercase(); - let content = String::from_utf8(read_file(source_path)?)?; - let fence = content.chars().filter(|&c| c == '#').collect::() + "#"; - - writeln!( - target, - "pub const {const_name}: &str = r{fence}\"{content}\"{fence};" - )?; - Ok(()) +fn get_const_name>(p: &P) -> String { + let mut file_name_without_extension = p.clone().into(); + file_name_without_extension.set_extension(""); + String::from( + file_name_without_extension + .file_name() + .unwrap() + .to_string_lossy(), + ) + .to_uppercase() } -pub fn create_static_output_files( - source_dir: &Path, - dest_path: &Path, -) -> Result<(), Box> { - let mut f = File::create(dest_path)?; - match source_dir.read_dir() { +pub fn create_static_output_files(source_dir: &str) { + let out_dir = env::var("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("static_files.rs"); + let mut f = File::create(&dest_path).unwrap(); + match read_dir(source_dir) { Ok(dir_content) => { for maybe_dir_entry in dir_content { - create_static_output(&maybe_dir_entry?.path(), &mut f)?; + let file_path = maybe_dir_entry.unwrap().path(); + let mut buffer = String::new(); + File::open(file_path.clone()) + .unwrap() + .read_to_string(&mut buffer) + .unwrap(); + let fence = buffer.chars().filter(|c| *c == '#').collect::() + "#"; + f.write_all( + format!( + "pub const {}: &str = r{1}\"{2}\"{1};\n", + get_const_name(&file_path), + fence, + buffer + ) + .as_bytes(), + ) + .unwrap(); } } Err(err) => { if err.kind() != NotFound { - return Err(format!("Unexpected error: {err}").into()); + panic!("Unexpected error: {}", err) } } } - Ok(()) } -pub fn main() -> Result<(), Box> { - create_static_output_files( - Path::new("static_files"), - &(Path::new(&env::var("OUT_DIR")?).join("static_files.rs")), - ) +#[allow(unused)] +fn main() { + create_static_output_files("static_files"); } diff --git a/src/builder.rs b/src/builder.rs index 394d3d1..9ec4fb1 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -115,8 +115,12 @@ impl ImplementationBuilder> for DefaultBuilder { ) } - type Implementation = - CertSymbol, SetuidCommandRunner, D, PathBuf>; + type Implementation = CertSymbol< + SetuidCommandRunner<'static, String, StdCommandRunner>, + SetuidCommandRunner<'static, String, StdCommandRunner>, + D, + PathBuf, + >; fn create( resource: &Cert, target: & as Resource>::Artifact, @@ -124,7 +128,7 @@ impl ImplementationBuilder> for DefaultBuilder { ) -> Self::Implementation { CertSymbol::new( resource.0.clone(), - SetuidCommandRunner::new(user_name.0), + SetuidCommandRunner::new(user_name.0, &StdCommandRunner), root_cert.into(), account_key.into(), challenges_dir.into(), @@ -282,7 +286,7 @@ impl, C: Clone + Into> &resource.0, cert, key, - nginx::php_snippet(resource.2, pool.0, &resource.1) + &resource.3, + nginx::php_snippet(resource.2, &pool.0, &resource.1) + &resource.3, challenges_snippet_path, ), ), @@ -428,7 +432,7 @@ impl ImplementationBuilder> for DefaultBuilder { ( FileSymbol::new( conf_path.clone().into(), - php_fpm_pool_config(&user_name.0, socket_path, &resource.1), + php_fpm_pool_config(&user_name.0, &socket_path, &resource.1), ), ReloadServiceSymbol::new(StdCommandRunner, service_name.0.clone()), ) @@ -444,7 +448,7 @@ impl> ImplementationBuilder> for De FileSymbol, SystemdUserSessionSymbol<'static, String, StdCommandRunner>, OwnerSymbol, - UserServiceSymbol<'static, PathBuf, String>, + UserServiceSymbol<'static, PathBuf, String, StdCommandRunner>, ); fn create( resource: &SystemdSocketService, @@ -471,7 +475,12 @@ impl> ImplementationBuilder> for De user_name.0.clone(), StdCommandRunner, ), - UserServiceSymbol::new(socket_path.clone().into(), user_name.0.clone(), resource.1), + UserServiceSymbol::new( + socket_path.clone().into(), + user_name.0.clone(), + resource.1, + &StdCommandRunner, + ), ) } } diff --git a/src/command_runner.rs b/src/command_runner.rs index 70418d8..5be8ddc 100644 --- a/src/command_runner.rs +++ b/src/command_runner.rs @@ -63,9 +63,13 @@ impl CommandRunner for StdCommandRunner { .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .spawn()?; + .spawn() + .expect("Failed to spawn child process"); let stdin = child.stdin.as_mut().expect("Failed to open stdin"); - stdin.write_all(input).await?; + stdin + .write_all(input) + .await + .expect("Failed to write to stdin"); let res = child.wait_with_output().await; //println!("{:?}", res); #[allow(clippy::let_and_return)] @@ -74,13 +78,17 @@ impl CommandRunner for StdCommandRunner { } #[derive(Debug)] -pub struct SetuidCommandRunner> { +pub struct SetuidCommandRunner<'a, U: AsRef, C: CommandRunner> { + command_runner: &'a C, user_name: U, } -impl> SetuidCommandRunner { - pub const fn new(user_name: U) -> Self { - Self { user_name } +impl<'a, U: AsRef, C: CommandRunner> SetuidCommandRunner<'a, U, C> { + pub fn new(user_name: U, command_runner: &'a C) -> Self { + SetuidCommandRunner { + command_runner, + user_name, + } } } @@ -113,13 +121,13 @@ impl Drop for TempSetEnv<'_> { } #[async_trait(?Send)] -impl> CommandRunner for SetuidCommandRunner { +impl, C: CommandRunner> CommandRunner for SetuidCommandRunner<'_, U, C> { async fn run(&self, program: &str, args: &[&OsStr], input: &[u8]) -> IoResult { let uid = get_user_by_name(self.user_name.as_ref()) .expect("User does not exist") .uid(); let set_home = TempSetEnv::new("HOME", format!("/home/{}", self.user_name.as_ref())); - let set_dbus = TempSetEnv::new("XDG_RUNTIME_DIR", format!("/run/user/{uid}")); + let set_dbus = TempSetEnv::new("XDG_RUNTIME_DIR", format!("/run/user/{}", uid)); //println!("{} {:?}", program, args); let mut child = Command::new(program) .args(args) @@ -143,6 +151,42 @@ impl> CommandRunner for SetuidCommandRunner { } } +#[derive(Debug)] +pub struct SuCommandRunner<'a, C> +where + C: CommandRunner, +{ + command_runner: &'a C, + user_name: &'a str, +} + +impl<'a, C> SuCommandRunner<'a, C> +where + C: 'a + CommandRunner, +{ + pub fn new(user_name: &'a str, command_runner: &'a C) -> SuCommandRunner<'a, C> { + SuCommandRunner { + command_runner, + user_name, + } + } +} + +// Su doesn't set XDG_RUNTIME_DIR +// https://github.com/systemd/systemd/blob/master/src/login/pam_systemd.c#L439 +#[async_trait(?Send)] +impl<'a, C> CommandRunner for SuCommandRunner<'a, C> +where + C: 'a + CommandRunner, +{ + async fn run(&self, program: &str, args: &[&OsStr], input: &[u8]) -> IoResult { + let raw_new_args = [self.user_name, "-s", "/usr/bin/env", "--", program]; + let mut new_args: Vec<&OsStr> = raw_new_args.iter().map(AsRef::as_ref).collect(); + new_args.extend_from_slice(args); + self.command_runner.run("su", &new_args, input).await + } +} + #[cfg(test)] mod test { use crate::args; @@ -163,10 +207,10 @@ mod test { loop { futures_util::select! { _ = res => {}, - _ = ps => assert!(start.elapsed().as_millis() < 1000), + _ = ps => assert!((Instant::now() - start).as_millis() < 1000), complete => break, } } - }); + }) } } diff --git a/src/lib.rs b/src/lib.rs index a63506f..dc73e82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,6 @@ clippy::cargo_common_metadata, clippy::future_not_send, clippy::missing_errors_doc, - clippy::missing_panics_doc, clippy::module_name_repetitions, rustdoc::all, missing_docs, diff --git a/src/locator.rs b/src/locator.rs index 3322baa..2b0561c 100644 --- a/src/locator.rs +++ b/src/locator.rs @@ -37,7 +37,7 @@ pub trait Policy { #[must_use] fn path_for_data(name: impl Display) -> PathBuf { - Path::new("/root/data").join(format!("_{name}")) + Path::new("/root/data").join(format!("_{}", name)) } } @@ -121,7 +121,7 @@ impl> ResourceLocator> for DefaultLocator

> ResourceLocator> for DefaultLocator { +impl<'a, POLICY, P: AsRef> ResourceLocator> for DefaultLocator { type Prerequisites = Dir; fn locate(resource: &File

) -> ( as Resource>::Artifact, Self::Prerequisites) { ((), Dir(resource.0.as_ref().parent().unwrap().into())) @@ -370,7 +370,7 @@ impl, P: Policy> ResourceLocator> for Defaul php_version, user.0 )), user, - ServiceNameArtifact(format!("php{php_version}-fpm")), + ServiceNameArtifact(format!("php{}-fpm", php_version)), ), (), ) diff --git a/src/loggers.rs b/src/loggers.rs index f65fdb7..1357173 100644 --- a/src/loggers.rs +++ b/src/loggers.rs @@ -1,28 +1,44 @@ use std::cell::RefCell; use std::cmp::min; -use std::io::{stderr, Write}; -use std::rc::Rc; +use std::io::stderr; +use std::io::Write; // The log crate defines // 1 - Error, 2 - Warn, 3 - Info, 4 - Debug, 5 - Trace pub type Level = usize; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug)] pub struct Entry(pub Level, pub S); pub trait Logger { - fn write(&self, level: Level, msg: &str); - fn writeln(&self, level: Level, msg: &str); - fn info(&self, msg: &str) { + fn write + Into>(&self, level: Level, msg: S) + where + Self: Sized; + fn writeln + Into>(&self, level: Level, msg: S) + where + Self: Sized; + fn info + Into>(&self, msg: S) + where + Self: Sized, + { self.writeln(3, msg); } - fn debug(&self, msg: &str) { + fn debug + Into>(&self, msg: S) + where + Self: Sized, + { self.writeln(4, msg); } - fn trace(&self, msg: &str) { + fn trace + Into>(&self, msg: S) + where + Self: Sized, + { self.writeln(5, msg); } - fn put<'a>(&self, entries: impl IntoIterator>) -> usize { + fn put + Into>(&self, entries: impl IntoIterator>) -> usize + where + Self: Sized, + { let mut c = 0; for item in entries { self.writeln(item.0, item.1); @@ -38,21 +54,21 @@ pub struct StdErrLogger { } impl Logger for StdErrLogger { - fn write(&self, _level: Level, msg: &str) { + fn write + Into>(&self, _level: Level, msg: S) { *self.line_started.borrow_mut() = true; - write!(&mut stderr(), "{msg}").expect("Could not write to stderr"); + write!(&mut stderr(), "{}", msg.as_ref()).unwrap(); } - fn writeln(&self, _level: Level, msg: &str) { + fn writeln + Into>(&self, _level: Level, msg: S) { if self.line_started.replace(false) { - writeln!(&mut stderr()).expect("Could not write to stderr"); + writeln!(&mut stderr()).unwrap(); } - writeln!(&mut stderr(), "{msg}").expect("Could not write to stderr"); + writeln!(&mut stderr(), "{}", msg.as_ref()).unwrap(); } } impl Drop for StdErrLogger { fn drop(&mut self) { if *self.line_started.borrow() { - writeln!(&mut stderr()).expect("Could not write to stderr"); + writeln!(&mut stderr()).unwrap(); } } } @@ -70,21 +86,21 @@ impl<'a, L> FilteringLogger<'a, L> { } impl<'a, L: Logger> Logger for FilteringLogger<'a, L> { - fn write(&self, level: Level, msg: &str) { + fn write + Into>(&self, level: Level, str: S) { if level <= self.max_level { - self.logger.write(level, msg); + self.logger.write(level, str); } } - fn writeln(&self, level: Level, msg: &str) { + fn writeln + Into>(&self, level: Level, str: S) { if level <= self.max_level { - self.logger.writeln(level, msg); + self.logger.writeln(level, str); } } } #[derive(Clone, Debug, Default)] pub struct StoringLogger { - log: Rc>)>>, + log: RefCell<(bool, Vec>)>, } impl StoringLogger { @@ -93,28 +109,30 @@ impl StoringLogger { Self::default() } - #[must_use] pub fn release(self) -> Vec> { - Rc::try_unwrap(self.log).unwrap().into_inner().1 + self.log.into_inner().1 } } impl Logger for StoringLogger { - fn write(&self, level: Level, msg: &str) { + fn write + Into>(&self, level: Level, line: S) { let mut log = self.log.borrow_mut(); let entry = if log.0 { - log.1.pop().map(|e| Entry(min(e.0, level), e.1 + msg)) + log + .1 + .pop() + .map(|e| Entry(min(e.0, level), e.1 + line.as_ref())) } else { None } - .unwrap_or_else(|| Entry(level, msg.to_string())); + .unwrap_or_else(|| Entry(level, line.into())); log.0 = true; log.1.push(entry); } - fn writeln(&self, level: Level, msg: &str) { + fn writeln + Into>(&self, level: Level, line: S) { let mut log = self.log.borrow_mut(); log.0 = false; - log.1.push(Entry(level, msg.to_string())); + log.1.push(Entry(level, line.into())); } } diff --git a/src/resources/mod.rs b/src/resources/mod.rs index e470765..9b16405 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -42,7 +42,7 @@ impl Resource for KeyAndCertBundle { #[derive(Debug, Hash, PartialEq, Eq)] pub struct File

(pub P, pub String); -impl

Resource for File

{ +impl<'a, P> Resource for File

{ type Artifact = (); } @@ -228,7 +228,7 @@ impl Resource for Cron { use std::rc::{Rc, Weak}; pub trait FromResource { - fn from_resource(from: &Rc) -> (Self, Weak) + fn from_resource(from: R) -> (Self, Weak) where Self: Sized; } @@ -250,7 +250,8 @@ macro_rules! default_resources { } $(impl<'a, D> FromResource<$type> for DefaultResources<'a, D> { - fn from_resource(inner: &Rc<$type>) -> (Self, Weak<$type>) { + fn from_resource(from: $type) -> (Self, Weak<$type>) { + let inner = Rc::new(from); (Self::$name(Rc::clone(&inner)), Rc::downgrade(&inner)) } })* diff --git a/src/setup/cache.rs b/src/setup/cache.rs deleted file mode 100644 index 1e28287..0000000 --- a/src/setup/cache.rs +++ /dev/null @@ -1,88 +0,0 @@ -use super::{Add, AddResult, AddableResource}; -use crate::async_utils::sleep; -use crate::resources::{FromArtifact, FromResource}; -use async_trait::async_trait; -use futures_util::future::{FutureExt, Shared}; -use slog::{trace, Logger}; -use std::cell::RefCell; -use std::collections::HashMap; -use std::fmt::Debug; -use std::future::Future; -use std::hash::Hash; -use std::pin::Pin; -use std::rc::Rc; -use std::time::Duration; - -type ResourceCache = HashMap>>>>; - -#[derive(Debug)] -pub struct Cache { - resources: RefCell>, - inner: Rc, -} - -impl Cache { - pub fn new(inner: I) -> Self { - Self { - resources: RefCell::default(), - inner: Rc::new(inner), - } - } -} - -#[async_trait(?Send)] -impl Add for Cache -where - Rs: Hash + Eq + 'static + FromResource, - As: 'static + FromArtifact + Clone, - I: 'static + Add, -{ - // FIXME: https://github.com/rust-lang/rust-clippy/issues/6353 - #[allow(clippy::await_holding_refcell_ref)] - async fn add(&self, logger: &Rc, resource: Rc, force_run: bool) -> AddResult { - let (storable_resource, weak_resource) = Rs::from_resource(&resource); - let mut resources = self.resources.borrow_mut(); - let result = if let Some(future) = resources.remove(&storable_resource) { - assert!( - !force_run, - "Forcing to run an already-added resource is a logical error" - ); - resources.insert(storable_resource, future.clone()); - drop(resources); - trace!(logger, "Resource already added"); - Ok(future.await) - } else { - let inner_weak = Rc::downgrade(&self.inner); - let logger_weak = Rc::downgrade(logger); - let future = Box::pin(async move { - let inner = inner_weak.upgrade().expect("Dangling!"); - let logger = logger_weak.upgrade().expect("Dangling!"); - let resource = weak_resource.upgrade().expect("Dangling!"); - let result = inner.add(&logger, Rc::clone(&resource), force_run).await; - - // Need to convert Box to String for Clone for Shared - result - .map(|(t, did_run)| (As::from_artifact(t), did_run)) - .map_err(|e| e.to_string()) - }) - .shared(); - let future_clone = future.clone(); - resources.insert( - storable_resource, - (Box::pin(async move { - let result = future_clone.await; - if result.is_err() { - // Step back to give the initial caller time to handle the error before unwrapping - sleep(Duration::from_millis(0)).await; - } - result.unwrap() - }) as Pin>>) - .shared(), - ); - drop(resources); - let result = future.await; - result.map_err(std::convert::Into::into) - }; - result.map(|(t, did_run)| (t.into_artifact(), did_run)) - } -} diff --git a/src/setup/core.rs b/src/setup/core.rs new file mode 100644 index 0000000..4a640ea --- /dev/null +++ b/src/setup/core.rs @@ -0,0 +1,204 @@ +use super::runnable::Runnable; +use super::setup::Setup; +use super::util::{AddableResource, InternalAddResult}; +use super::SymbolRunner; +use crate::async_utils::join; +use crate::loggers::{Logger, StoringLogger}; +use crate::resources::{FromArtifact, FromResource}; +use crate::symbols::Symbol; +use crate::to_artifact::ToArtifact; +use crate::{ImplementationBuilder, ResourceLocator}; +use async_trait::async_trait; +use std::error::Error; +use std::fmt::Debug; +use std::hash::Hash; +use std::marker::PhantomData; + +#[async_trait(?Send)] +pub trait AddGeneric { + async fn add_generic(&self, x: X) -> InternalAddResult; +} + +macro_rules! add_generic { + ( $($name:ident)* ) => ( + #[async_trait(?Send)] + #[allow(non_snake_case)] + impl + AddGeneric<($($name,)*)> for Setup + where + $( + RegularSetupCore: SetupCore<$name, Self>, + As: FromArtifact<$name>, + Rs: FromResource<$name>, + $name::Artifact: Clone + ),* + { + #[allow(unused, clippy::shadow_unrelated)] + async fn add_generic(&self, ($($name,)*): ($($name,)*)) -> Result<(StoringLogger, ($($name::Artifact,)*), bool), (StoringLogger, Box)> + { + let ($($name,)*) = join!($(self.add($name, false),)*); + let logger = StoringLogger::default(); + let mut did_run_any = false; + $( + let (log, artifact, did_run) = $name?; + logger.put(log.release()); + did_run_any = did_run_any || did_run; + let $name = artifact; + )* + Ok((logger, ($($name,)*), did_run_any)) + } + } + ); +} + +for_each_tuple!(add_generic); + +// This is for self-referential T +// FIXME: Wait for specialization +#[async_trait(?Send)] +impl< + SR: 'static + SymbolRunner, + T: AddableResource, + Rs: 'static + Hash + Eq + FromResource, + As: 'static + FromArtifact + Clone, + L: 'static + ResourceLocator>, + B: 'static + ImplementationBuilder, + > AddGeneric> for Setup +where + >::Implementation: Runnable + Debug, + Self: AddGeneric, + T::Artifact: Clone, +{ + async fn add_generic(&self, r: Option) -> InternalAddResult> { + Ok(match r { + Some(r) => { + let (logger, result, did_run) = self.add(r, false).await?; + (logger, Some(result), did_run) + } + None => (StoringLogger::default(), None, false), + }) + } +} + +#[async_trait(?Send)] +impl< + T: AddableResource, + Rs: 'static + Hash + Eq + FromResource, + As: 'static + FromArtifact + Clone, + SR: 'static, + L: 'static, + B: 'static, + > AddGeneric for Setup +where + T::Artifact: Clone, + RegularSetupCore: 'static + SetupCore, +{ + async fn add_generic(&self, r: T) -> InternalAddResult { + self.add(r, false).await + } +} + +#[async_trait(?Send)] +pub trait SetupCore { + async fn add>( + &self, + setup: &S, + resource: RR, + force_run: bool, + ) -> InternalAddResult; +} + +#[derive(Debug)] +pub struct RegularSetupCore { + symbol_runner: SR, + phantom: PhantomData<(L, B)>, +} + +impl RegularSetupCore { + pub fn new(symbol_runner: SR) -> Self { + Self { + symbol_runner, + phantom: PhantomData::default(), + } + } +} + +#[async_trait(?Send)] +impl SetupCore for RegularSetupCore +where + B: ImplementationBuilder, + >::Implementation: Runnable + Debug, + L: ResourceLocator, + S: AddGeneric + AddGeneric<>::Prerequisites>, +{ + async fn add>( + &self, + setup: &S, + resource: RR, + force_run: bool, + ) -> InternalAddResult { + let resource = resource.as_ref(); + let logger = StoringLogger::new(); + logger.write(4, format!("Adding {:?} ... ", resource)); + logger.write(4, format!("(force_run is {})", force_run)); + let (location, location_prereqs) = L::locate(resource); + logger.trace(format!("Adding location prereqs for {:?}", resource)); + let result = setup.add_generic(location_prereqs).await; + if let Err((log, e)) = result { + logger.put(log.release()); + return Err((logger, e)); + } + let (location_prereq_logger, _, location_prereqs_did_run) = result.unwrap(); + logger.put(location_prereq_logger.release()); + logger.trace(format!( + "Location prereqs for {:?} did_run: {}", + resource, location_prereqs_did_run + )); + logger.trace(format!("Adding implementation prereqs for {:?}", resource)); + let result = setup.add_generic(B::prerequisites(resource)).await; + if let Err((log, e)) = result { + logger.put(log.release()); + return Err((logger, e)); + } + let (impl_prereq_logger, prereqs, prereqs_did_run) = result.unwrap(); + logger.put(impl_prereq_logger.release()); + logger.trace(format!( + "Implementation prereqs for {:?} did_run: {}", + resource, prereqs_did_run + )); + logger.trace(format!("Running implementation for {:?}", resource)); + let implementation = B::create(resource, &location, prereqs); + let did_run_result = implementation + .run( + &self.symbol_runner, + &logger, + force_run || location_prereqs_did_run || prereqs_did_run, + ) + .await; + match did_run_result { + Ok(did_run) => { + logger.write(4, "done."); + Ok((logger, location, did_run)) + } + Err(e) => Err((logger, e)), + } + } +} + +#[async_trait(?Send)] +impl SymbolRunner for RegularSetupCore { + async fn run_symbol( + &self, + symbol: &S, + parent_logger: &LOG, + force: bool, + ) -> Result> { + let logger = StoringLogger::new(); + logger.write(4, format!("Directly running {:?} ...", symbol)); + let result = self.symbol_runner.run_symbol(symbol, &logger, force).await; + logger.write(4, "done."); + let max_level = if result.is_err() { 5 } else { 3 }; + parent_logger.put(logger.release().into_iter().filter(|e| e.0 <= max_level)); + result + } +} diff --git a/src/setup/mod.rs b/src/setup/mod.rs index 8302d77..a187a88 100644 --- a/src/setup/mod.rs +++ b/src/setup/mod.rs @@ -1,388 +1,12 @@ -mod realizer; -mod symbol_runner; +mod core; mod util; +pub use util::{AddResult, AddableResource}; +mod symbol_runner; pub use symbol_runner::{ DelayingSymbolRunner, DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner, SymbolRunner, }; -mod cache; mod runnable; -use crate::loggers::Logger; -use crate::resources::{DefaultArtifacts, DefaultResources, FromArtifact, FromResource}; -use crate::symbols::Symbol; -use crate::{DefaultBuilder, DefaultLocator}; -use crate::{ImplementationBuilder, ResourceLocator}; -use async_trait::async_trait; -use cache::Cache; -use realizer::Realizer; -use runnable::Runnable; -use slog::o; -use std::error::Error; -use std::fmt::Debug; -use std::hash::Hash; -use std::rc::Rc; -use util::{Add, AddGeneric, AddResult, AddableResource, Recorder}; - -// Necessary for the recursive type -#[derive(Debug)] -pub struct ActualSetup(Cache, Rs, As>); - -#[async_trait(?Send)] -impl Add for ActualSetup -where - Cache, Rs, As>: Add, -{ - async fn add(&self, logger: &Rc, r: Rc, force_run: bool) -> AddResult { - self.0.add(logger, r, force_run).await - } -} - -// This is for self-referential T -// FIXME: Wait for specialization -#[async_trait(?Send)] -impl< - SR: 'static + SymbolRunner, - T: AddableResource, - Rs: 'static + Hash + Eq + FromResource, - As: 'static + FromArtifact + Clone, - L: 'static + ResourceLocator>, - B: 'static + ImplementationBuilder, - > AddGeneric> for ActualSetup -where - >::Implementation: Runnable + Debug, - Self: AddGeneric, - T::Artifact: Clone, - // These bounds cannot be replaced by - // `Realizer: Add` - // because the prerequisites are Option, too, and thus this would - // require AddGeneric> to already be implemented -{ - async fn add_generic( - &self, - logger: &Rc, - r: Option, - force_run: bool, - ) -> AddResult> { - Ok(match r { - Some(r) => { - let (result, did_run) = self.add(logger, Rc::new(r), force_run).await?; - (Some(result), did_run) - } - None => (None, false), - }) - } -} - -#[derive(Debug)] -pub struct Setup< - SR, - LOG, - L = DefaultLocator, - B = DefaultBuilder, - Rs = DefaultResources<'static, &'static str>, - As = DefaultArtifacts<'static, &'static str>, ->(LOG, Rc, Rc>); - -impl Setup { - pub fn new(symbol_runner: SR, logger: LOG) -> Self { - Self::new_with(symbol_runner, logger) - } -} - -impl Setup { - pub fn new_with(symbol_runner: SR, logger: LOG) -> Self { - let runner = Rc::new(symbol_runner); - let inner = Rc::new_cyclic(|inner| { - ActualSetup(Cache::new(Realizer::new(Rc::clone(&runner), inner.clone()))) - }); - Self(logger, runner, inner) - } -} - -impl< - SR: 'static, - LOG: 'static + Logger, - L: 'static, - B: 'static, - Rs: 'static + Eq + Hash, - As: 'static, - > Setup -{ - pub async fn add_force(&self, resource: R, force_run: bool) -> AddResult - where - Rs: FromResource, - As: FromArtifact + Clone, - ActualSetup: Add, - { - let recorder = Recorder::default(); - let result = { - let log = Rc::new(slog::Logger::root(recorder.clone(), o!())); - self.2.add(&log, Rc::new(resource), force_run).await - }; - self.log_result(recorder, result.as_ref().map(|(_, did_run)| *did_run)); - result - } - - pub async fn add(&self, resource: R) -> AddResult - where - Rs: FromResource, - As: FromArtifact + Clone, - R::Artifact: Clone, - ActualSetup: Add, - { - self.add_force(resource, false).await - } - - pub async fn run_symbol( - &self, - symbol: S, - force: bool, - ) -> Result> - where - SR: SymbolRunner, - { - let recorder = Recorder::default(); - let result = { - let log = Rc::new(slog::Logger::root( - recorder.clone(), - o!("symbol" => format!("{symbol:?}")), - )); - self.1.run_symbol(&symbol, &log, force).await - }; - self.log_result(recorder, result.as_ref().copied()); - result - } - - fn log_result(&self, recorder: Recorder, result: Result>) { - let log = match result { - Ok(false) => String::new(), - Ok(true) => recorder.into_string(slog::Level::Info), - Err(e) => recorder.into_string(slog::Level::Trace), - }; - if log.is_empty() { - self.0.write(3, "."); - } else { - self.0.writeln(3, &log); - } - } -} - -#[cfg(test)] -mod test { - use super::SymbolRunner; - use crate::async_utils::run; - use crate::loggers::{Entry, StoringLogger}; - use crate::resources::{FromArtifact, FromResource, Resource}; - use crate::symbols::Symbol; - use crate::to_artifact::ToArtifact; - use crate::{ImplementationBuilder, ResourceLocator, Setup}; - use async_trait::async_trait; - use regex::Regex; - use slog::{info, Logger}; - use std::cell::RefCell; - use std::error::Error; - use std::fmt::Debug; - use std::rc::{Rc, Weak}; - - struct TestSymbolRunner { - count: Rc>, - } - - #[async_trait(?Send)] - impl SymbolRunner for TestSymbolRunner { - async fn run_symbol( - &self, - symbol: &S, - logger: &Logger, - force: bool, - ) -> Result> { - info!(logger, "run"); - let run = force || !symbol.target_reached().await?; - if run { - *self.count.borrow_mut() += 1; - } - Ok(run) - } - } - - #[derive(Debug, PartialEq, Eq, Hash)] - struct TestResource(&'static str, T); - impl Resource for TestResource { - type Artifact = (); - } - - #[derive(Debug, Hash, PartialEq, Eq)] - enum Resources { - A(Rc>), - B(Rc>), - } - impl FromResource> for Resources { - fn from_resource( - inner: &Rc>, - ) -> (Self, Weak>) { - (Self::A(Rc::clone(&inner)), Rc::downgrade(&inner)) - } - } - impl FromResource> for Resources { - fn from_resource(inner: &Rc>) -> (Self, Weak>) { - (Self::B(Rc::clone(&inner)), Rc::downgrade(&inner)) - } - } - - #[derive(Clone)] - struct Artifacts; - impl FromArtifact> for Artifacts { - fn from_artifact(_from: ()) -> Self { - Self - } - #[allow(clippy::unused_unit)] - fn into_artifact(self) -> () { - #[allow(clippy::unused_unit)] - () - } - } - - struct TestResourceLocator; - impl ResourceLocator> for TestResourceLocator { - type Prerequisites = (); - fn locate(_resource: &TestResource) -> ( as ToArtifact>::Artifact, ()) { - ((), ()) - } - } - - struct TestImplementationBuilder; - impl ImplementationBuilder> for TestImplementationBuilder { - type Implementation = TestSymbol; - type Prerequisites = TestResource<()>; - - fn prerequisites(resource: &TestResource<&'static str>) -> Self::Prerequisites { - TestResource(resource.1, ()) - } - fn create( - resource: &TestResource<&'static str>, - (): &(), - _inputs: ::Artifact, - ) -> Self::Implementation { - TestSymbol { - reached: resource.0.chars().next().unwrap().is_uppercase(), - } - } - } - impl ImplementationBuilder> for TestImplementationBuilder { - type Implementation = TestSymbol; - type Prerequisites = (); - - fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {} - fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Implementation { - TestSymbol { - reached: resource.0.chars().next().unwrap().is_uppercase(), - } - } - } - - #[derive(Debug)] - struct TestSymbol { - reached: bool, - } - - #[async_trait(?Send)] - impl Symbol for TestSymbol { - async fn target_reached(&self) -> Result> { - Ok(self.reached) - } - async fn execute(&self) -> Result<(), Box> { - Ok(()) - } - } - - #[allow(clippy::type_complexity)] - fn get_setup() -> ( - Rc>, - Setup< - TestSymbolRunner, - StoringLogger, - TestResourceLocator, - TestImplementationBuilder, - Resources, - Artifacts, - >, - StoringLogger, - ) { - let count = Rc::new(RefCell::new(0)); - let runner = TestSymbolRunner { - count: Rc::clone(&count), - }; - let logger = StoringLogger::new(); - (count, Setup::new_with(runner, logger.clone()), logger) - } - - #[test] - fn correctly_uses_force() { - run(async { - let (count, setup, _) = get_setup(); - setup.add(TestResource("A", "b")).await.unwrap(); - assert_eq!(*count.borrow(), 2); - setup.add(TestResource("A", "b")).await.unwrap(); - assert_eq!(*count.borrow(), 2); - - let (count, setup, _) = get_setup(); - setup.add(TestResource("A", "B")).await.unwrap(); - assert_eq!(*count.borrow(), 0); - }); - } - - #[test] - fn run_reached_symbol() { - run(async { - let (count, setup, log) = get_setup(); - let did_run = setup - .run_symbol(TestSymbol { reached: true }, false) - .await - .unwrap(); - drop(setup); - assert!(!did_run); - assert_eq!(*count.borrow(), 0); - assert_eq!(log.release(), vec![Entry(3, ".".into())]); - }); - } - - #[test] - fn run_not_reached_symbol() { - run(async { - let (count, setup, log) = get_setup(); - let did_run = setup - .run_symbol(TestSymbol { reached: false }, false) - .await - .unwrap(); - drop(setup); - assert!(did_run); - assert_eq!(*count.borrow(), 1); - let log = log.release(); - assert_eq!(log.len(), 1); - assert_eq!(log[0].0, 3); - let re = Regex::new(r"^symbol: TestSymbol \{ reached: false \}\n \w+ \d{1,2} \d{2}:\d{2}:\d{2}.\d{3} INFO run\n$").unwrap(); - assert!(re.is_match(&log[0].1)); - }); - } - - use super::{ActualSetup, AddGeneric, Cache, Realizer}; - - #[test] - fn empty_tuple_add_generic() { - let setup = Rc::new_cyclic(|inner| { - ActualSetup(Cache::, (), ()>::new( - Realizer::new(Rc::new(()), inner.clone()), - )) - }); - run(async { - assert!(setup - .add_generic( - &Rc::new(slog::Logger::root(slog::Discard, slog::o!())), - (), - false - ) - .await - .is_ok()); - }) - } -} +#[allow(clippy::module_inception)] +mod setup; +pub use setup::SetupFacade as Setup; diff --git a/src/setup/realizer.rs b/src/setup/realizer.rs deleted file mode 100644 index 2757186..0000000 --- a/src/setup/realizer.rs +++ /dev/null @@ -1,74 +0,0 @@ -use super::{Add, AddGeneric, AddResult, AddableResource, Runnable, SymbolRunner}; -use crate::{ImplementationBuilder, ResourceLocator}; -use async_trait::async_trait; -use slog::{debug, o, trace, Logger}; -use std::fmt::Debug; -use std::marker::PhantomData; -use std::rc::{Rc, Weak}; - -#[derive(Debug)] -pub struct Realizer { - symbol_runner: Rc, - outer: Weak, - phantom: PhantomData<(L, B)>, -} - -impl Realizer { - pub fn new(symbol_runner: Rc, outer: Weak) -> Self { - Self { - symbol_runner, - outer, - phantom: PhantomData::default(), - } - } -} - -#[async_trait(?Send)] -impl Add for Realizer -where - R: AddableResource, - SR: SymbolRunner, - L: ResourceLocator, - B: ImplementationBuilder, - >::Implementation: Runnable + Debug, - S: AddGeneric + AddGeneric<>::Prerequisites>, -{ - async fn add(&self, logger: &Rc, resource: Rc, force_run: bool) -> AddResult { - let setup = self.outer.upgrade().unwrap(); - let logger = Rc::new(logger.new(o!("resource" => format!("{resource:?}")))); - trace!(logger, "(force_run is {})", force_run); - let (location, location_prereqs) = L::locate(&resource); - trace!(logger, "Adding location prereqs ..."); - let (_, location_prereqs_did_run) = (*setup) - .add_generic(&logger, location_prereqs, false) - .await?; - trace!( - logger, - "Location prereqs did_run: {}", - location_prereqs_did_run - ); - - trace!(logger, "Adding implementation prereqs ..."); - let (prereqs, prereqs_did_run) = (*setup) - .add_generic(&logger, B::prerequisites(&resource), false) - .await?; - trace!( - logger, - "Implementation prereqs did_run: {}", - prereqs_did_run - ); - - trace!(logger, "Running implementation ..."); - let implementation = B::create(&resource, &location, prereqs); - let did_run = implementation - .run( - &*self.symbol_runner, - &logger, - force_run || location_prereqs_did_run || prereqs_did_run, - ) - .await?; - debug!(logger, "done."); - - Ok((location, did_run)) - } -} diff --git a/src/setup/runnable.rs b/src/setup/runnable.rs index 092ea0f..4e3e087 100644 --- a/src/setup/runnable.rs +++ b/src/setup/runnable.rs @@ -1,17 +1,16 @@ use super::SymbolRunner; +use crate::loggers::Logger; use crate::symbols::Symbol; use async_trait::async_trait; -use slog::Logger; use std::error::Error; use std::fmt::Debug; -// A generalization over symbols and tuples of symbols #[async_trait(?Send)] pub trait Runnable { - async fn run( + async fn run( &self, runner: &R, - logger: &Logger, + logger: &L, force: bool, ) -> Result>; } @@ -22,10 +21,10 @@ impl Runnable for S where Self: Symbol + Debug, { - async fn run( + async fn run( &self, runner: &R, - logger: &Logger, + logger: &L, force: bool, ) -> Result> { runner.run_symbol(self, logger, force).await @@ -39,7 +38,7 @@ macro_rules! runnable_for_tuple { #[allow(non_snake_case)] impl<$($name: Symbol + Debug,)*> Runnable for ($($name,)*) { #[allow(unused)] - async fn run<_R: SymbolRunner>(&self, runner: &_R, logger: &Logger, force: bool) -> Result> { + async fn run<_R: SymbolRunner, _L: Logger>(&self, runner: &_R, logger: &_L, force: bool) -> Result> { let ($($name,)*) = self; let mut result = false; $(result = runner.run_symbol($name, logger, force || result).await? || result;)* @@ -55,10 +54,10 @@ for_each_tuple!(runnable_for_tuple); mod test { use super::Runnable; use crate::async_utils::run; + use crate::loggers::{Logger, StoringLogger}; use crate::symbols::Symbol; use crate::SymbolRunner; use async_trait::async_trait; - use slog::{o, Discard, Logger}; use std::cell::RefCell; use std::error::Error; use std::fmt::Debug; @@ -117,10 +116,10 @@ mod test { #[async_trait(?Send)] impl SymbolRunner for TestSymbolRunner { - async fn run_symbol( + async fn run_symbol( &self, symbol: &S, - _logger: &Logger, + _logger: &L, force: bool, ) -> Result> { let run = force || !symbol.target_reached().await?; @@ -136,7 +135,7 @@ mod test { force: bool, ) -> (Rc>, Result>) { let (count, runner) = get_runner(); - let res = run(runnable.run(&runner, &Logger::root(Discard, o!()), force)); + let res = run(runnable.run(&runner, &StoringLogger::new(), force)); (count, res) } diff --git a/src/setup/setup.rs b/src/setup/setup.rs new file mode 100644 index 0000000..6d06eef --- /dev/null +++ b/src/setup/setup.rs @@ -0,0 +1,338 @@ +use super::core::{RegularSetupCore, SetupCore}; +use super::runnable::Runnable; +use super::util::{AddResult, AddableResource, InternalAddResult}; +use super::SymbolRunner; +use crate::async_utils::sleep; +use crate::loggers::{Logger, StoringLogger}; +use crate::resources::{DefaultArtifacts, DefaultResources, FromArtifact, FromResource}; +use crate::{DefaultBuilder, DefaultLocator}; +use futures_util::future::{FutureExt, Shared}; +use std::cell::{RefCell, RefMut}; +use std::collections::HashMap; +use std::error::Error; +use std::future::Future; +use std::hash::Hash; +use std::pin::Pin; +use std::rc::Rc; +use std::time::Duration; + +type Cache = HashMap>>>>; + +#[derive(Debug)] +struct SetupInner { + core: CORE, + resources: RefCell>, +} + +#[derive(Debug)] +pub struct Setup(Rc, Rs, As>>); + +impl + Setup +{ + fn borrow_resources(&self) -> RefMut<'_, Cache> { + self.0.resources.borrow_mut() + } + + // FIXME: https://github.com/rust-lang/rust-clippy/issues/6353 + #[allow(clippy::await_holding_refcell_ref)] + pub async fn add(&self, resource: R, force_run: bool) -> InternalAddResult + where + Rs: FromResource, + As: FromArtifact + Clone, + R::Artifact: Clone, + RegularSetupCore: SetupCore, + { + let (storable_resource, weak_resource) = Rs::from_resource(resource); + let mut resources = self.borrow_resources(); + let result = if let Some(future) = resources.remove(&storable_resource) { + assert!( + !force_run, + "Forcing to run an already-added resource is a logical error" + ); + resources.insert(storable_resource, future.clone()); + drop(resources); + let logger = StoringLogger::default(); + logger.trace(format!( + "{:?} already added", + weak_resource.upgrade().expect("Dangling!") + )); + let (t, did_run) = future.await; + Ok((logger, t, did_run)) + } else { + let inner_weak = Rc::downgrade(&self.0); + let future = Box::pin(async move { + let this = Self(inner_weak.upgrade().expect("Dangling!")); + let resource = weak_resource.upgrade().expect("Dangling!"); + // Need to convert Box to String for Clone for Shared + let result = this.0.core.add(&this, resource, force_run).await; + + result + .map(|(logger, t, did_run)| (logger, As::from_artifact(t), did_run)) + .map_err(|(logger, e)| (logger, e.to_string())) + }) + .shared(); + let future_clone = future.clone(); + resources.insert( + storable_resource, + (Box::pin(async move { + let result = future_clone.await; + if result.is_err() { + // Step back to give the initial caller time to handle the error before unwrapping + sleep(Duration::from_millis(0)).await; + } + result.map(|(_, t, did_run)| (t, did_run)).unwrap() + }) as Pin>>) + .shared(), + ); + drop(resources); + let result = future.await; + result.map_err(|(logger, e)| (logger, e.into())) + }; + result.map(|(logger, t, did_run)| (logger, t.into_artifact(), did_run)) + } +} + +#[derive(Debug)] +pub struct SetupFacade< + SR, + LOG, + L = DefaultLocator, + B = DefaultBuilder, + Rs = DefaultResources<'static, &'static str>, + As = DefaultArtifacts<'static, &'static str>, +>(LOG, Setup); + +impl SetupFacade { + pub fn new(symbol_runner: SR, logger: LOG) -> Self { + Self::new_with(symbol_runner, logger) + } +} + +impl SetupFacade { + pub fn new_with(symbol_runner: SR, logger: LOG) -> Self { + Self( + logger, + Setup(Rc::new(SetupInner { + core: RegularSetupCore::new(symbol_runner), + resources: RefCell::default(), + })), + ) + } +} + +impl< + SR: 'static, + LOG: 'static + Logger, + L: 'static, + B: 'static, + Rs: 'static + Eq + Hash, + As: 'static, + > SetupFacade +{ + pub async fn add_force(&self, resource: R, force_run: bool) -> AddResult + where + Rs: FromResource, + As: FromArtifact + Clone, + R::Artifact: Clone, + RegularSetupCore: SetupCore>, + { + let result = self.1.add(resource, force_run).await; + match result { + Ok((logger, t, did_run)) => { + if self + .0 + .put(logger.release().into_iter().filter(|e| e.0 <= 3)) + == 0 + { + self.0.write(3, "."); + } + Ok((t, did_run)) + } + Err((logger, e)) => { + self.0.put(logger.release()); + Err(e) + } + } + } + pub async fn add(&self, resource: R) -> AddResult + where + RegularSetupCore: SetupCore>, + Rs: FromResource, + As: FromArtifact + Clone, + R::Artifact: Clone, + SR: SymbolRunner, + { + self.add_force(resource, false).await + } + + pub async fn run_symbol( + &self, + symbol: S, + force: bool, + ) -> Result> + where + RegularSetupCore: SymbolRunner, + { + symbol.run(&(self.1).0.core, &self.0, force).await + } +} + +#[cfg(test)] +mod test { + use super::SymbolRunner; + use crate::async_utils::run; + use crate::loggers::{Logger, StoringLogger}; + use crate::resources::{FromArtifact, FromResource, Resource}; + use crate::symbols::Symbol; + use crate::to_artifact::ToArtifact; + use crate::{ImplementationBuilder, ResourceLocator, Setup}; + use async_trait::async_trait; + use std::cell::RefCell; + use std::error::Error; + use std::fmt::Debug; + use std::rc::{Rc, Weak}; + + struct TestSymbolRunner { + count: Rc>, + } + + #[async_trait(?Send)] + impl SymbolRunner for TestSymbolRunner { + async fn run_symbol( + &self, + symbol: &S, + _logger: &L, + force: bool, + ) -> Result> { + let run = force || !symbol.target_reached().await?; + if run { + *self.count.borrow_mut() += 1; + } + Ok(run) + } + } + + #[derive(Debug, PartialEq, Eq, Hash)] + struct TestResource(&'static str, T); + impl Resource for TestResource { + type Artifact = (); + } + + #[derive(Debug, Hash, PartialEq, Eq)] + enum Resources { + A(Rc>), + B(Rc>), + } + impl FromResource> for Resources { + fn from_resource(from: TestResource<&'static str>) -> (Self, Weak>) { + let inner = Rc::new(from); + (Self::A(Rc::clone(&inner)), Rc::downgrade(&inner)) + } + } + impl FromResource> for Resources { + fn from_resource(from: TestResource<()>) -> (Self, Weak>) { + let inner = Rc::new(from); + (Self::B(Rc::clone(&inner)), Rc::downgrade(&inner)) + } + } + + #[derive(Clone)] + struct Artifacts; + impl FromArtifact> for Artifacts { + fn from_artifact(_from: ()) -> Self { + Self + } + #[allow(clippy::unused_unit)] + fn into_artifact(self) -> () { + #[allow(clippy::unused_unit)] + () + } + } + + struct TestResourceLocator; + impl ResourceLocator> for TestResourceLocator { + type Prerequisites = (); + fn locate(_resource: &TestResource) -> ( as ToArtifact>::Artifact, ()) { + ((), ()) + } + } + + struct TestImplementationBuilder; + impl ImplementationBuilder> for TestImplementationBuilder { + type Implementation = TestSymbol; + type Prerequisites = TestResource<()>; + + fn prerequisites(resource: &TestResource<&'static str>) -> Self::Prerequisites { + TestResource(resource.1, ()) + } + fn create( + resource: &TestResource<&'static str>, + (): &(), + _inputs: ::Artifact, + ) -> Self::Implementation { + TestSymbol { + reached: resource.0.chars().next().unwrap().is_uppercase(), + } + } + } + impl ImplementationBuilder> for TestImplementationBuilder { + type Implementation = TestSymbol; + type Prerequisites = (); + + fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {} + fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Implementation { + TestSymbol { + reached: resource.0.chars().next().unwrap().is_uppercase(), + } + } + } + + #[derive(Debug)] + struct TestSymbol { + reached: bool, + } + + #[async_trait(?Send)] + impl Symbol for TestSymbol { + async fn target_reached(&self) -> Result> { + Ok(self.reached) + } + async fn execute(&self) -> Result<(), Box> { + Ok(()) + } + } + + fn get_setup() -> ( + Rc>, + Setup< + TestSymbolRunner, + StoringLogger, + TestResourceLocator, + TestImplementationBuilder, + Resources, + Artifacts, + >, + ) { + let count = Rc::new(RefCell::new(0)); + let runner = TestSymbolRunner { + count: Rc::clone(&count), + }; + (count, Setup::new_with(runner, StoringLogger::new())) + } + + #[test] + fn correctly_uses_force() { + run(async { + let (count, setup) = get_setup(); + setup.add(TestResource("A", "b")).await.unwrap(); + assert_eq!(*count.borrow(), 2); + setup.add(TestResource("A", "b")).await.unwrap(); + assert_eq!(*count.borrow(), 2); + + let (count, setup) = get_setup(); + setup.add(TestResource("A", "B")).await.unwrap(); + assert_eq!(*count.borrow(), 0); + }); + } +} diff --git a/src/setup/symbol_runner.rs b/src/setup/symbol_runner.rs index ea628a3..e9b3b10 100644 --- a/src/setup/symbol_runner.rs +++ b/src/setup/symbol_runner.rs @@ -1,7 +1,7 @@ use crate::async_utils::sleep; +use crate::loggers::Logger; use crate::symbols::Symbol; use async_trait::async_trait; -use slog::{debug, info, o, trace, Logger}; use std::error::Error; use std::fmt; use std::fmt::Debug; @@ -9,10 +9,10 @@ use std::time::Duration; #[async_trait(?Send)] pub trait SymbolRunner { - async fn run_symbol( + async fn run_symbol( &self, symbol: &S, - logger: &Logger, + logger: &L, force: bool, ) -> Result>; } @@ -37,19 +37,18 @@ impl InitializingSymbolRunner { Self } - async fn exec_symbol( + async fn exec_symbol( &self, symbol: &S, - logger: &Logger, + logger: &L, ) -> Result<(), Box> { - info!(logger, "Executing {:?}", symbol); + logger.info(format!("Executing {:?}", symbol)); symbol.execute().await?; let target_reached = symbol.target_reached().await?; - trace!( - logger, + logger.trace(format!( "Symbol reports target_reached: {:?} (should be true)", target_reached - ); + )); if target_reached { Ok(()) } else { @@ -60,26 +59,25 @@ impl InitializingSymbolRunner { #[async_trait(?Send)] impl SymbolRunner for InitializingSymbolRunner { - async fn run_symbol( + async fn run_symbol( &self, symbol: &S, - logger: &Logger, + logger: &L, force: bool, ) -> Result> { let executed = if force { - debug!(logger, "Forcing symbol execution"); + logger.debug("Forcing symbol execution"); self.exec_symbol(symbol, logger).await?; true } else { let target_reached = symbol.target_reached().await?; if target_reached { - debug!(logger, "{:?} already reached", symbol); + logger.debug(format!("{:?} already reached", symbol)); } else { - trace!( - logger, + logger.trace(format!( "Symbol reports target_reached: {:?}", target_reached - ); + )); self.exec_symbol(symbol, logger).await?; } !target_reached @@ -108,10 +106,10 @@ where clippy::cast_possible_truncation, clippy::cast_precision_loss )] - async fn run_symbol( + async fn run_symbol( &self, symbol: &S, - logger: &Logger, + logger: &L, force: bool, ) -> Result> { sleep(Duration::from_millis( @@ -141,23 +139,23 @@ impl DrySymbolRunner { #[async_trait(?Send)] impl SymbolRunner for DrySymbolRunner { - async fn run_symbol( + async fn run_symbol( &self, symbol: &S, - logger: &Logger, + logger: &L, force: bool, ) -> Result> { let would_execute = if force { - info!(logger, "Would force-execute"); + logger.info(format!("Would force-execute {:?}", symbol)); true } else { let target_reached = symbol.target_reached().await?; - debug!( - logger, - "Symbol reports target_reached: {:?}", target_reached - ); + logger.debug(format!( + "Symbol reports target_reached: {:?}", + target_reached + )); if !target_reached { - info!(logger, "Would execute"); + logger.info(format!("Would execute {:?}", symbol)); } !target_reached }; @@ -180,19 +178,18 @@ impl SymbolRunner for ReportingSymbolRunner where R: SymbolRunner, { - async fn run_symbol( + async fn run_symbol( &self, symbol: &S, - logger: &Logger, + logger: &L, force: bool, ) -> Result> { - let logger = logger.new(o!("symbol" => format!("{symbol:?}"))); - debug!(logger, "Running ..."); - let res = self.0.run_symbol(symbol, &logger, force).await; + logger.debug(format!("Running symbol {:?}", symbol)); + let res = self.0.run_symbol(symbol, logger, force).await; if let Err(ref e) = res { - info!(logger, "failed with {}", e); + logger.info(format!("Failed on {:?} with {}, aborting.", symbol, e)); } else { - debug!(logger, "Successfully finished"); + logger.debug(format!("Successfully finished {:?}", symbol)); } res } @@ -203,9 +200,9 @@ mod test { use super::{DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner, SymbolRunner}; use crate::async_utils::sleep; use crate::async_utils::{run, try_join}; + use crate::loggers::StoringLogger; use crate::symbols::Symbol; use async_trait::async_trait; - use slog::{o, Discard, Logger}; use std::cell::RefCell; use std::error::Error; use std::fmt; @@ -266,7 +263,7 @@ mod test { } fn run_symbol(s: S) -> Result> { - run(InitializingSymbolRunner::new().run_symbol(&s, &Logger::root(Discard, o!()), false)) + run(InitializingSymbolRunner::new().run_symbol(&s, &StoringLogger::new(), false)) } #[test] @@ -319,8 +316,8 @@ mod test { let s1 = SleeperSymbol; let s2 = DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]); - let l1 = Logger::root(Discard, o!()); - let l2 = Logger::root(Discard, o!()); + let l1 = StoringLogger::new(); + let l2 = StoringLogger::new(); let runner1 = InitializingSymbolRunner::new(); let result = try_join!( runner1.run_symbol(&s1, &l1, false), @@ -330,6 +327,8 @@ mod test { assert_eq!(result, (false, true)); let s2 = DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]); + let l1 = StoringLogger::new(); + let l2 = StoringLogger::new(); let runner2 = DrySymbolRunner::new(); let result = try_join!( runner2.run_symbol(&s1, &l1, false), @@ -339,6 +338,8 @@ mod test { assert_eq!(result, (false, true)); let s2 = DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]); + let l1 = StoringLogger::new(); + let l2 = StoringLogger::new(); let runner3 = ReportingSymbolRunner::new(runner1); let result = try_join!( runner3.run_symbol(&s1, &l1, false), diff --git a/src/setup/util.rs b/src/setup/util.rs index 6951165..542482f 100644 --- a/src/setup/util.rs +++ b/src/setup/util.rs @@ -1,131 +1,12 @@ -use crate::async_utils::join; +use crate::loggers::StoringLogger; use crate::resources::Resource; use crate::to_artifact::ToArtifact; -use async_trait::async_trait; -use slog::{Drain, Filter, Logger, OwnedKVList, Record}; -use slog_async::AsyncRecord; -use std::cell::RefCell; use std::error::Error; use std::fmt::Debug; -use std::io::{self, Write}; -use std::rc::Rc; -use std::sync::{Arc, Mutex}; pub trait AddableResource: 'static + Resource + Debug {} impl AddableResource for R where R: 'static + Resource + Debug {} pub type AddResult = Result<(::Artifact, bool), Box>; - -#[async_trait(?Send)] -pub trait Add { - async fn add(&self, logger: &Rc, resource: Rc, force_run: bool) -> AddResult; -} - -#[async_trait(?Send)] -pub trait AddGeneric { - async fn add_generic(&self, logger: &Rc, x: X, force_run: bool) -> AddResult; -} - -macro_rules! add_generic { - ( $($name:ident)* ) => ( - #[async_trait(?Send)] - #[allow(non_snake_case)] - impl<_S, $($name: AddableResource,)*> - AddGeneric<($($name,)*)> for _S - where - $( - _S: AddGeneric<$name> - ),* - { - #[allow(unused, clippy::shadow_unrelated)] - async fn add_generic(&self, logger: &Rc, ($($name,)*): ($($name,)*), force_run: bool) -> AddResult<($($name,)*)> - { - let ($($name,)*) = join!($(self.add_generic(logger, $name, force_run),)*); - let mut did_run_any = false; - $( - let (artifact, did_run) = $name?; - did_run_any = did_run_any || did_run; - let $name = artifact; - )* - Ok((($($name,)*), did_run_any)) - } - } - ); -} - -for_each_tuple!(add_generic); - -#[async_trait(?Send)] -impl> AddGeneric for S { - async fn add_generic(&self, logger: &Rc, r: R, force_run: bool) -> AddResult { - self.add(logger, Rc::new(r), force_run).await - } -} - -// From https://users.rust-lang.org/t/how-to-send-a-writer-into-a-thread/4965/10 -#[derive(Clone)] -struct Output(Rc>); - -impl Output { - pub fn new(w: W) -> Self { - Self(Rc::new(RefCell::new(w))) - } -} - -impl Write for Output { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.borrow_mut().write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.0.borrow_mut().flush() - } -} - -#[derive(Clone, Default)] -pub struct Recorder(Arc>>); - -impl Drain for Recorder { - type Ok = (); - type Err = slog::Never; - - fn log(&self, record: &Record<'_>, logger_values: &OwnedKVList) -> Result { - self - .0 - .lock() - .unwrap() - .push(AsyncRecord::from(record, logger_values)); - Ok(()) - } -} - -impl Recorder { - pub fn into_string(self, filter_level: slog::Level) -> String { - let output = Output::new(vec![]); - { - let decorator = slog_term::PlainDecorator::new(output.clone()); - let drain = Filter::new( - slog_term::CompactFormat::new(decorator).build(), - move |record| record.level().is_at_least(filter_level), - ); - let Ok(mutex) = Arc::try_unwrap(self.0) else { panic!("cannot unwrap Arc") }; // AsyncRecord does not implement Debug, so we cannot unwrap - for record in mutex.into_inner().unwrap() { - record.log_to(&drain).unwrap(); - } - } - String::from_utf8(Rc::try_unwrap(output.0).unwrap().into_inner()) - .expect("Record output should be valid UTF-8") - } -} - -#[cfg(test)] -mod test { - use super::Recorder; - use slog::Level; - - #[test] - fn records_no_output() { - let recorder = Recorder::default(); - assert_eq!(recorder.into_string(Level::Trace), ""); - } -} +pub type InternalAddResult = + Result<(StoringLogger, ::Artifact, bool), (StoringLogger, Box)>; diff --git a/src/symbols/git/checkout.rs b/src/symbols/git/checkout.rs index 43be14b..f5399c7 100644 --- a/src/symbols/git/checkout.rs +++ b/src/symbols/git/checkout.rs @@ -17,7 +17,7 @@ pub struct Checkout<_C, C, P, S, B> { phantom: PhantomData<_C>, } -impl<_C, C, P, S, B> Checkout<_C, C, P, S, B> { +impl Checkout<_C, C, P, S, B> { pub fn new(target: P, source: S, branch: B, command_runner: C) -> Self { Self { target, @@ -29,7 +29,7 @@ impl<_C, C, P, S, B> Checkout<_C, C, P, S, B> { } } -impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S, B> Checkout<_C, C, P, S, B> { +impl, P: AsRef, S, B> Checkout { async fn run_git(&self, args: &[impl AsRef]) -> Result, Box> { let mut new_args = Vec::with_capacity(args.len() + 2); new_args.extend_from_slice(args!["-C", self.target.as_ref()]); @@ -43,8 +43,8 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S, B> Checkout<_C, C, P, } #[async_trait(?Send)] -impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S: AsRef, B: AsRef> Symbol - for Checkout<_C, C, P, S, B> +impl, P: AsRef, S: AsRef, B: AsRef> Symbol + for Checkout { async fn target_reached(&self) -> Result> { if !self.target.as_ref().exists() { @@ -132,7 +132,7 @@ mod test { args: RefCell::new(vec![]), }; let checkout: Checkout = - Checkout::new(".", "source", "branch", &c); + Checkout::new("target", "source", "branch", &c); let start = Instant::now(); assert!(run(checkout.target_reached()).unwrap()); let end = Instant::now(); @@ -142,11 +142,12 @@ mod test { assert_eq!( first_two_args, [ - ["-C", ".", "fetch", "source", "branch"], - ["-C", ".", "rev-list", "-1", "HEAD"], + ["-C", "target", "fetch", "source", "branch"], + ["-C", "target", "rev-list", "-1", "HEAD"], ] ); - assert_eq!(args[2], ["-C", ".", "rev-list", "-1", "FETCH_HEAD"]); + drop(first_two_args); + assert_eq!(args[2], ["-C", "target", "rev-list", "-1", "FETCH_HEAD"]); assert!((end - start).as_millis() >= 100); assert!((end - start).as_millis() < 150); diff --git a/src/symbols/mariadb/database.rs b/src/symbols/mariadb/database.rs index 95ae693..caace4e 100644 --- a/src/symbols/mariadb/database.rs +++ b/src/symbols/mariadb/database.rs @@ -12,7 +12,7 @@ pub struct Database<'a, D, S, C> { } impl<'a, D, S, C: CommandRunner> Database<'a, D, S, C> { - pub const fn new(db_name: D, seed_file: S, command_runner: &'a C) -> Self { + pub fn new(db_name: D, seed_file: S, command_runner: &'a C) -> Self { Self { db_name, seed_file, diff --git a/src/symbols/mariadb/dump.rs b/src/symbols/mariadb/dump.rs index 7503ad1..11accac 100644 --- a/src/symbols/mariadb/dump.rs +++ b/src/symbols/mariadb/dump.rs @@ -13,7 +13,7 @@ pub struct Dump<'a, N, C, S> { } impl<'a, N, C: CommandRunner, S> Dump<'a, N, C, S> { - pub const fn new(db_name: N, storage: S, command_runner: &'a C) -> Self { + pub fn new(db_name: N, storage: S, command_runner: &'a C) -> Self { Self { db_name, storage, diff --git a/src/symbols/mariadb/user.rs b/src/symbols/mariadb/user.rs index 9fb3730..b8eb4b5 100644 --- a/src/symbols/mariadb/user.rs +++ b/src/symbols/mariadb/user.rs @@ -10,7 +10,7 @@ pub struct User<'a, U, C> { } impl<'a, U: AsRef, C: CommandRunner> User<'a, U, C> { - pub const fn new(user_name: U, command_runner: &'a C) -> Self { + pub fn new(user_name: U, command_runner: &'a C) -> Self { Self { user_name, command_runner, diff --git a/src/symbols/npm.rs b/src/symbols/npm.rs index 5275d64..b2a95bc 100644 --- a/src/symbols/npm.rs +++ b/src/symbols/npm.rs @@ -12,7 +12,7 @@ pub struct Install<'a, T: AsRef, C: CommandRunner> { } impl<'a, T: AsRef, C: CommandRunner> Install<'a, T, C> { - pub const fn new(target: T, command_runner: &'a C) -> Self { + pub fn new(target: T, command_runner: &'a C) -> Self { Self { target, command_runner, diff --git a/src/symbols/postgresql/database.rs b/src/symbols/postgresql/database.rs index 3b825fd..bf36fea 100644 --- a/src/symbols/postgresql/database.rs +++ b/src/symbols/postgresql/database.rs @@ -12,7 +12,7 @@ pub struct PostgreSQLDatabase<'a, N: AsRef, S: AsRef, C: CommandRunner } impl<'a, N: AsRef, S: AsRef, C: CommandRunner> PostgreSQLDatabase<'a, N, S, C> { - pub const fn new(name: N, seed_file: S, command_runner: &'a C) -> Self { + pub fn new(name: N, seed_file: S, command_runner: &'a C) -> Self { PostgreSQLDatabase { name, seed_file, @@ -25,7 +25,7 @@ impl<'a, N: AsRef, S: AsRef, C: CommandRunner> PostgreSQLDatabase<'a, .command_runner .get_output( "su", - args!["-", "postgres", "-c", format!("psql -t -c \"{sql}\"")], + args!["-", "postgres", "-c", format!("psql -t -c \"{}\"", sql)], ) .await?; Ok(String::from_utf8(b)?) diff --git a/src/symbols/saved_directory.rs b/src/symbols/saved_directory.rs index 6e45c89..d52091a 100644 --- a/src/symbols/saved_directory.rs +++ b/src/symbols/saved_directory.rs @@ -10,7 +10,7 @@ use std::marker::PhantomData; use std::path::Path; use std::str::FromStr; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq)] pub enum StorageDirection { Load, Store, diff --git a/src/symbols/systemd/user_service.rs b/src/symbols/systemd/user_service.rs index 67ed3b2..f795786 100644 --- a/src/symbols/systemd/user_service.rs +++ b/src/symbols/systemd/user_service.rs @@ -8,23 +8,28 @@ use std::path::Path; use std::time::Duration; #[derive(Debug)] -pub struct UserService<'a, S: AsRef, U: AsRef> { +pub struct UserService<'a, S: AsRef, U: AsRef, R: CommandRunner> { socket_path: S, service_name: &'a str, - command_runner: SetuidCommandRunner, + command_runner: SetuidCommandRunner<'a, U, R>, } -impl, U: AsRef> UserService<'static, S, U> { - pub const fn new(socket_path: S, user_name: U, service_name: &'static str) -> Self { +impl, U: AsRef, R: CommandRunner> UserService<'static, S, U, R> { + pub fn new( + socket_path: S, + user_name: U, + service_name: &'static str, + command_runner: &'static R, + ) -> Self { Self { socket_path, service_name, - command_runner: SetuidCommandRunner::new(user_name), + command_runner: SetuidCommandRunner::new(user_name, command_runner), } } } -impl, U: AsRef> UserService<'_, S, U> { +impl, U: AsRef, R: CommandRunner> UserService<'_, S, U, R> { async fn systemctl_wait_for_dbus(&self, args: &[&OsStr]) -> Result> { let mut tries = 5; loop { @@ -80,7 +85,7 @@ impl, U: AsRef> UserService<'_, S, U> { } #[async_trait(?Send)] -impl, U: AsRef> Symbol for UserService<'_, S, U> { +impl, U: AsRef, R: CommandRunner> Symbol for UserService<'_, S, U, R> { async fn target_reached(&self) -> Result> { self.check_if_service().await } diff --git a/src/symbols/tls/key.rs b/src/symbols/tls/key.rs index be2ea9f..720d7ab 100644 --- a/src/symbols/tls/key.rs +++ b/src/symbols/tls/key.rs @@ -8,7 +8,7 @@ use std::path::Path; pub struct Key { file_path: P, command_runner: C, - bits: u32, + bytes: u32, } impl Key { @@ -16,7 +16,7 @@ impl Key { Self { file_path, command_runner, - bits: 4096, + bytes: 4096, } } } @@ -42,10 +42,8 @@ impl> Symbol for Key { ], ) .await?; - Ok( - stdout.ends_with(b"RSA key ok\n") - && stdout.starts_with(format!("RSA Private-Key: ({} bit, 2 primes)\n", self.bits).as_ref()), - ) + // FIXME check bytes + Ok(stdout.ends_with(b"RSA key ok\n")) } async fn execute(&self) -> Result<(), Box> { @@ -57,7 +55,7 @@ impl> Symbol for Key { "genrsa", "-out", self.file_path.as_ref(), - self.bits.to_string(), + self.bytes.to_string(), ], ) .await diff --git a/src/symbols/wordpress/plugin.rs b/src/symbols/wordpress/plugin.rs index 6ac40e2..8c280d8 100644 --- a/src/symbols/wordpress/plugin.rs +++ b/src/symbols/wordpress/plugin.rs @@ -16,7 +16,7 @@ pub struct Plugin<'a, P, N, R> { } impl<'a, P: AsRef, N: AsRef, R: CommandRunner> Plugin<'a, P, N, R> { - pub const fn new(base: P, name: N, command_runner: &'a R) -> Self { + pub fn new(base: P, name: N, command_runner: &'a R) -> Self { Self { base, name, diff --git a/src/symbols/wordpress/translation.rs b/src/symbols/wordpress/translation.rs index ac16ec3..6643c9e 100644 --- a/src/symbols/wordpress/translation.rs +++ b/src/symbols/wordpress/translation.rs @@ -19,7 +19,7 @@ pub struct Translation<'a, C, D, R> { } impl<'a, D, C: AsRef, R: CommandRunner> Translation<'a, C, D, R> { - pub const fn new(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self { + pub fn new(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self { Self { path, version, @@ -33,7 +33,7 @@ impl, D: AsRef, R: CommandRunner> Translation<'_, C, D, R> { fn get_pairs(&self) -> Vec<(String, PathBuf)> { let version_x = self .version - .trim_end_matches(|c: char| c.is_ascii_digit()) + .trim_end_matches(|c: char| c.is_digit(10)) .to_owned() + "x"; let locale = self.locale.as_ref(); @@ -51,7 +51,7 @@ impl, D: AsRef, R: CommandRunner> Translation<'_, C, D, R> { ] { for format in &["po", "mo"] { res.push(( - format!("https://translate.wordpress.org/projects/wp/{version_x}/{in_slug}{path_locale}/default/export-translations?format={format}"), + format!("https://translate.wordpress.org/projects/wp/{}/{}{}/default/export-translations?format={}", version_x, in_slug, path_locale, format), [self.path.as_ref(), format!("{}{}.{}", out_slug, self.locale.as_ref(), format).as_ref()].iter().collect() )); } diff --git a/src/templates/nginx/mod.rs b/src/templates/nginx/mod.rs index aa2992d..1134378 100644 --- a/src/templates/nginx/mod.rs +++ b/src/templates/nginx/mod.rs @@ -11,55 +11,3 @@ pub fn acme_challenges_snippet>(path: P) -> String { path.as_ref().to_str().unwrap() ) } - -#[cfg(test)] -mod test { - use super::{server_config, uwsgi_snippet}; - - #[test] - fn test_uwsgi() { - assert_eq!( - server_config( - "testdomain", - "/certpath", - "/keypath", - uwsgi_snippet("/uwsgi.sock", "/static"), - "/challenges_snippet.conf" - ), - "server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name testdomain; - include \"/challenges_snippet.conf\"; - - ssl_certificate /certpath; - ssl_certificate_key /keypath; - add_header Strict-Transport-Security \"max-age=31536000\"; - - root /static; - - location / { - try_files $uri @proxy; - } - - location @proxy { - include uwsgi_params; - uwsgi_pass unix:/uwsgi.sock; - } -} - -# Redirect all HTTP links to the matching HTTPS page -server { - listen 80; - listen [::]:80; - server_name testdomain; - include \"/challenges_snippet.conf\"; - - location / { - return 301 https://$host$request_uri; - } -} -" - ); - } -} diff --git a/src/templates/nginx/server.rs b/src/templates/nginx/server.rs index dd28bc4..9f04bbd 100644 --- a/src/templates/nginx/server.rs +++ b/src/templates/nginx/server.rs @@ -78,26 +78,21 @@ pub fn php_snippet, STATIC: AsRef>( pub fn redir_snippet(target: &str) -> String { format!( "location / {{ - return 301 $scheme://{target}$request_uri; - }}" + return 301 $scheme://{}$request_uri; + }}", + target ) } pub trait SocketSpec { - fn to_proxy_pass(&self) -> String; - fn to_uwsgi_pass(&self) -> String; + fn to_nginx(&self) -> String; } impl> SocketSpec for T { #[must_use] - fn to_proxy_pass(&self) -> String { + fn to_nginx(&self) -> String { format!("unix:{}:", self.as_ref().to_str().unwrap()) } - - #[must_use] - fn to_uwsgi_pass(&self) -> String { - format!("unix:{}", self.as_ref().to_str().unwrap()) - } } #[derive(Debug)] @@ -112,12 +107,7 @@ impl LocalTcpSocket { impl SocketSpec for LocalTcpSocket { #[must_use] - fn to_proxy_pass(&self) -> String { - format!("localhost:{}", self.0) - } - - #[must_use] - fn to_uwsgi_pass(&self) -> String { + fn to_nginx(&self) -> String { format!("localhost:{}", self.0) } } @@ -139,33 +129,12 @@ pub fn proxy_snippet>( proxy_redirect off; }}", static_path.as_ref().to_str().unwrap(), - socket_path.to_proxy_pass() + socket_path.to_nginx() ) } #[must_use] -pub fn uwsgi_snippet>( - socket_path: S, - static_path: STATIC, -) -> String { - format!( - "root {}; - - location / {{ - try_files $uri @proxy; - }} - - location @proxy {{ - include uwsgi_params; - uwsgi_pass {}; - }}", - static_path.as_ref().to_str().unwrap(), - socket_path.to_uwsgi_pass() - ) -} - -#[must_use] -pub fn static_snippet(static_path: impl AsRef) -> String { +pub fn static_snippet>(static_path: S) -> String { format!( "root {}; try_files $uri $uri/ $uri.html =404; @@ -175,7 +144,7 @@ pub fn static_snippet(static_path: impl AsRef) -> String { } #[must_use] -pub const fn dokuwiki_snippet() -> &'static str { +pub fn dokuwiki_snippet() -> String { " location ~ /(data/|conf/|bin/|inc/|install.php) { deny all; } @@ -187,11 +156,11 @@ pub const fn dokuwiki_snippet() -> &'static str { rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last; rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last; rewrite ^/(.*) /doku.php?id=$1&$args last; - }" + }".into() } #[must_use] -pub const fn nextcloud_snippet() -> &'static str { +pub fn nextcloud_snippet() -> String { " client_max_body_size 500M; @@ -241,20 +210,5 @@ pub const fn nextcloud_snippet() -> &'static str { access_log off; } " -} - -#[cfg(test)] -mod test { - use super::default_server; - #[test] - fn test_default_server() { - assert_eq!( - default_server("filename"), - r#"server { - listen 80 default_server; - listen [::]:80 default_server; - include "filename"; - }"# - ); - } + .into() } diff --git a/src/templates/php.rs b/src/templates/php.rs index 6e47e28..7bd7695 100644 --- a/src/templates/php.rs +++ b/src/templates/php.rs @@ -56,25 +56,3 @@ env[PATH] = /usr/local/bin:/usr/bin:/bin config ) } - -#[cfg(test)] -mod test { - use super::fpm_pool_config; - #[test] - fn test_fpm_pool_config() { - assert_eq!( - fpm_pool_config("user", "socket", &5.into()), - r"[user] - -user = user -group = www-data -listen = socket -listen.owner = www-data -pm = ondemand -catch_workers_output = yes -env[PATH] = /usr/local/bin:/usr/bin:/bin -pm.max_children = 5 -" - ); - } -} diff --git a/src/to_artifact.rs b/src/to_artifact.rs index e158f07..e45cda9 100644 --- a/src/to_artifact.rs +++ b/src/to_artifact.rs @@ -17,6 +17,8 @@ macro_rules! to_artifact { for_each_tuple!(to_artifact); impl ToArtifact for Option { + // FIXME: https://github.com/rust-lang/rust-clippy/issues/2843 + #![allow(clippy::use_self)] type Artifact = Option; } diff --git a/tests/file.rs b/tests/file.rs index 3043cdc..b74c8d9 100644 --- a/tests/file.rs +++ b/tests/file.rs @@ -98,7 +98,7 @@ fn may_not_read_file() { #[test] fn may_not_create_file() { - let symbol = get_symbol(Path::new("/proc/somefile")); + let symbol = get_symbol(&Path::new("/proc/somefile")); run(async { // Could also return an error @@ -109,7 +109,7 @@ fn may_not_create_file() { #[test] fn directory_missing() { - let symbol = get_symbol(Path::new("/nonexisting")); + let symbol = get_symbol(&Path::new("/nonexisting")); run(async { // Could also return an error diff --git a/tests/setup.rs b/tests/setup.rs index e30520f..077c4fc 100644 --- a/tests/setup.rs +++ b/tests/setup.rs @@ -1,149 +1,100 @@ use async_trait::async_trait; -use regex::Regex; use schematics::async_utils::{run, sleep}; -use schematics::loggers::{Entry, StoringLogger}; +use schematics::loggers::{Logger, StoringLogger}; use schematics::resources::{AcmeUser, Cert, Csr, GitCheckout}; use schematics::symbols::Symbol; use schematics::Setup; use schematics::SymbolRunner; -use slog::{info, Logger as SlogLogger}; +use std::cell::RefCell; use std::error::Error; use std::fmt::Debug; +use std::rc::Rc; use std::time::Duration; #[derive(Clone, Debug)] struct TestSymbolRunner { - run: bool, -} - -impl TestSymbolRunner { - fn new(run: bool) -> Self { - Self { run } - } + count: Rc>, } #[async_trait(?Send)] impl SymbolRunner for TestSymbolRunner { - async fn run_symbol( + async fn run_symbol( &self, _symbol: &S, - logger: &SlogLogger, + _logger: &L, _force: bool, ) -> Result> { - info!(logger, "run_symbol"); + *self.count.borrow_mut() += 1; sleep(Duration::from_millis(0)).await; - Ok(self.run) + Ok(false) } } -fn test( - count: usize, - body: fn(setup: Setup) -> (), -) -> Vec> { - let runner = TestSymbolRunner::new(false); - let logger = StoringLogger::new(); - { - let setup = Setup::new(runner, logger.clone()); - body(setup); - } - assert_eq!(logger.release(), vec![Entry(3, ".".repeat(count))]); - - let runner = TestSymbolRunner::new(true); - let logger = StoringLogger::new(); - { - let setup = Setup::new(runner, logger.clone()); - body(setup); - } - logger.release() -} - #[test] fn can_create_an_acme_user() { - let mut result = test(1, |setup| { - assert_eq!((run(setup.add(AcmeUser)).unwrap().0).0, "acme"); - }); - let entry = result - .pop() - .expect("log is empty but should contain one entry"); - assert_eq!(result.len(), 0, "log has more than one entry"); - assert_eq!(entry.0, 3, "log entry has wrong level"); - let re = - Regex::new(r"^resource: AcmeUser\n \w+ \d{1,2} \d{2}:\d{2}:\d{2}.\d{3} INFO run_symbol\n$") - .unwrap(); - assert!( - re.is_match(&entry.1), - "log output {} does not match {}", - entry.1, - re - ); + let count = Rc::new(RefCell::new(0)); + let runner = TestSymbolRunner { + count: Rc::clone(&count), + }; + let setup = Setup::new(runner, StoringLogger::new()); + assert_eq!((run(setup.add(AcmeUser)).unwrap().0).0, "acme"); } #[test] fn runs_only_once() { - let mut result = test(2, |setup| { - run(async { - assert_eq!( - (setup.add(Csr("somehost")).await.unwrap().0) - .as_ref() - .to_str() - .unwrap(), - "/etc/ssl/local_certs/somehost.csr", - ); - assert_eq!( - (setup.add(Csr("somehost")).await.unwrap().0) - .as_ref() - .to_str() - .unwrap(), - "/etc/ssl/local_certs/somehost.csr", - ); - }); + run(async { + let count = Rc::new(RefCell::new(0)); + let runner = TestSymbolRunner { + count: Rc::clone(&count), + }; + let setup = Setup::new(runner, StoringLogger::new()); + assert_eq!( + (setup.add(Csr("somehost")).await.unwrap().0) + .as_ref() + .to_str() + .unwrap(), + "/etc/ssl/local_certs/somehost.csr", + ); + assert_eq!( + (setup.add(Csr("somehost")).await.unwrap().0) + .as_ref() + .to_str() + .unwrap(), + "/etc/ssl/local_certs/somehost.csr", + ); + assert_eq!(*count.borrow(), 2 + 5); // Key and CSR + 5 dirs }); - let entry = result - .pop() - .expect("log is empty but should contain entries"); - assert_eq!(entry.0, 3, "log entry has wrong level"); - assert_eq!(entry.1, ".", "log entry has wrong content"); - let entry = result - .pop() - .expect("log is empty but should contain entries"); - assert_eq!(entry.0, 3, "log entry has wrong level"); - assert_eq!(entry.1.matches("run_symbol").count(), 7); // Key and CSR + 5 dirs - assert_eq!(result.len(), 0, "log has more than one entry"); } #[test] fn can_create_an_acme_cert() { - let mut result = test(1, |setup| { - assert_eq!( - (run(setup.add(Cert("somehost"))).unwrap().0) - .as_ref() - .to_str() - .unwrap(), - "/etc/ssl/local_certs/somehost.crt", - ); - }); - let entry = result - .pop() - .expect("log is empty but should contain one entry"); - assert_eq!(entry.0, 3, "log entry has wrong level"); - assert_eq!(entry.1.matches("run_symbol").count(), 19); - assert_eq!(result.len(), 0, "log has more than one entry"); + let count = Rc::new(RefCell::new(0)); + let runner = TestSymbolRunner { + count: Rc::clone(&count), + }; + let setup = Setup::new(runner, StoringLogger::new()); + assert_eq!( + (run(setup.add(Cert("somehost"))).unwrap().0) + .as_ref() + .to_str() + .unwrap(), + "/etc/ssl/local_certs/somehost.crt", + ); + assert_eq!(*count.borrow(), 19); } #[test] fn can_create_a_git_checkout() { - let mut result = test(1, |setup| { - run(setup.add(GitCheckout( - "/tmp/somepath".into(), - "/tmp/some_src_repo", - "master", - ))) - .unwrap(); - }); - let entry = result - .pop() - .expect("log is empty but should contain one entry"); - assert_eq!(entry.0, 3, "log entry has wrong level"); - assert_eq!(entry.1.matches("run_symbol").count(), 3); - assert_eq!(result.len(), 0, "log has more than one entry"); + let count = Rc::new(RefCell::new(0)); + let runner = TestSymbolRunner { + count: Rc::clone(&count), + }; + let setup = Setup::new(runner, StoringLogger::new()); + run(setup.add(GitCheckout( + "/tmp/somepath".into(), + "/tmp/some_src_repo", + "master", + ))) + .unwrap(); + assert_eq!(*count.borrow(), 3); }