From 2d3e3688faddaad303d8f861e8e6aa2ce727fcae Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Sun, 16 Aug 2020 11:08:22 +0200 Subject: [PATCH] Experiment with futures --- Cargo.toml | 5 +- src/async_utils.rs | 87 ++++++ src/builder.rs | 200 +++++++------- src/command_runner.rs | 124 +++++---- src/lib.rs | 19 +- src/locator.rs | 2 + src/loggers.rs | 125 +++++++-- src/resources/mod.rs | 43 ++- src/schema.rs | 274 ------------------ src/setup.rs | 396 --------------------------- src/setup/core.rs | 191 +++++++++++++ src/setup/mod.rs | 10 + src/setup/runnable.rs | 193 +++++++++++++ src/setup/setup.rs | 300 ++++++++++++++++++++ src/setup/symbol_runner.rs | 318 +++++++++++++++++++++ src/setup/util.rs | 9 + src/symbols/acme/cert.rs | 61 +++-- src/symbols/concat.rs | 6 +- src/symbols/cron.rs | 27 +- src/symbols/dir.rs | 6 +- src/symbols/file.rs | 6 +- src/symbols/git/checkout.rs | 124 +++++++-- src/symbols/git/mod.rs | 2 +- src/symbols/git/submodules.rs | 43 ++- src/symbols/mariadb/database.rs | 43 +-- src/symbols/mariadb/dump.rs | 43 +-- src/symbols/mariadb/user.rs | 24 +- src/symbols/mod.rs | 6 +- src/symbols/npm.rs | 53 ++-- src/symbols/owner.rs | 18 +- src/symbols/postgresql/database.rs | 100 ++++--- src/symbols/saved_directory.rs | 69 +++-- src/symbols/systemd/reload.rs | 18 +- src/symbols/systemd/user_service.rs | 61 +++-- src/symbols/systemd/user_session.rs | 7 +- src/symbols/tls/csr.rs | 49 ++-- src/symbols/tls/key.rs | 52 ++-- src/symbols/user.rs | 34 ++- src/symbols/wordpress/plugin.rs | 57 ++-- src/symbols/wordpress/translation.rs | 28 +- src/templates/nginx/server.rs | 1 + src/to_artifact.rs | 2 +- tests/file.rs | 57 ++-- tests/setup.rs | 30 +- 44 files changed, 2081 insertions(+), 1242 deletions(-) create mode 100644 src/async_utils.rs delete mode 100644 src/schema.rs delete mode 100644 src/setup.rs create mode 100644 src/setup/core.rs create mode 100644 src/setup/mod.rs create mode 100644 src/setup/runnable.rs create mode 100644 src/setup/setup.rs create mode 100644 src/setup/symbol_runner.rs create mode 100644 src/setup/util.rs diff --git a/Cargo.toml b/Cargo.toml index 8000d82..0ebcb92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,11 @@ edition = "2018" build = "src/build.rs" [dependencies] -users = "0.7.0" +users = "0.10.0" regex = "1.0.1" +futures = "0.3" +async-trait = "0.1" +tokio = { version = "0.2", features = ["process", "io-util", "rt-core", "macros"] } [dev-dependencies] tempdir = "0.3" diff --git a/src/async_utils.rs b/src/async_utils.rs new file mode 100644 index 0000000..2c1196a --- /dev/null +++ b/src/async_utils.rs @@ -0,0 +1,87 @@ +use std::{ + future::Future, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Waker}, + thread, + time::Duration, +}; + +pub use async_trait::async_trait; + +pub fn run(future: F) -> F::Output { + tokio::runtime::Runtime::new().unwrap().block_on(future) +} +pub use tokio::try_join; + +#[derive(Debug)] +pub struct TimerFuture { + state: Arc>, +} + +#[derive(Debug)] +enum State { + NotStarted(Duration), + Running(Waker), + Completed, +} + +impl Future for TimerFuture { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut state = self.state.lock().unwrap(); + 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 + } +} + +pub fn sleep(duration: Duration) -> impl Future { + TimerFuture { + state: Arc::new(Mutex::new(State::NotStarted(duration))), + } +} + +#[cfg(test)] +mod test { + use crate::async_utils::{run, sleep}; + use futures::future::FutureExt; + use std::time::{Duration, Instant}; + + #[test] + fn test_sleep() { + run(async { + let start = Instant::now(); + let sleep = sleep(Duration::from_millis(100)).fuse(); + let ok = async {}.fuse(); + futures::pin_mut!(sleep, ok); + loop { + futures::select! { + _ = sleep => {}, + _ = ok => assert!((Instant::now() - start).as_millis() < 100), + complete => break, + } + } + }) + } +} diff --git a/src/builder.rs b/src/builder.rs index 6bea5c4..325c29b 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -39,48 +39,49 @@ use crate::to_artifact::ToArtifact; use std::fmt::Display; use std::path::{Path, PathBuf}; -pub trait SymbolBuilder { +pub trait ImplementationBuilder { type Prerequisites: ToArtifact; fn prerequisites(resource: &R) -> Self::Prerequisites; - type Symbol; + type Implementation; fn create( resource: &R, target: &R::Artifact, inputs: ::Artifact, - ) -> Self::Symbol + ) -> Self::Implementation where R: Resource; } +#[derive(Debug)] pub struct DefaultBuilder; -impl SymbolBuilder> for DefaultBuilder { +impl ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &Key) -> Self::Prerequisites {} - type Symbol = KeySymbol; + type Implementation = KeySymbol; fn create( _resource: &Key, target: & as Resource>::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { KeySymbol::new(StdCommandRunner, target.clone().into()) } } -impl SymbolBuilder> for DefaultBuilder { +impl ImplementationBuilder> for DefaultBuilder { type Prerequisites = Key; fn prerequisites(resource: &Csr) -> Self::Prerequisites { Key(resource.0.clone()) } - type Symbol = CsrSymbol; + type Implementation = CsrSymbol; fn create( resource: &Csr, target: & as Resource>::Artifact, key: ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { CsrSymbol::new( StdCommandRunner, resource.0.clone(), @@ -90,7 +91,7 @@ impl SymbolBuilder> for DefaultBuilder { } } -impl SymbolBuilder> for DefaultBuilder { +impl ImplementationBuilder> for DefaultBuilder { type Prerequisites = ( Csr, AcmeRootCert, @@ -110,7 +111,7 @@ impl SymbolBuilder> for DefaultBuilder { ) } - type Symbol = CertSymbol< + type Implementation = CertSymbol< SetuidCommandRunner<'static, String, StdCommandRunner>, SetuidCommandRunner<'static, String, StdCommandRunner>, D, @@ -120,7 +121,7 @@ impl SymbolBuilder> for DefaultBuilder { resource: &Cert, target: & as Resource>::Artifact, (csr, root_cert, account_key, challenges_dir, user_name, _): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { CertSymbol::new( resource.0.clone(), SetuidCommandRunner::new(user_name.0, &StdCommandRunner), @@ -133,73 +134,73 @@ impl SymbolBuilder> for DefaultBuilder { } } -impl SymbolBuilder> for DefaultBuilder { +impl ImplementationBuilder> for DefaultBuilder { type Prerequisites = (Cert, AcmeRootCert); fn prerequisites(resource: &CertChain) -> Self::Prerequisites { (Cert(resource.0.clone()), AcmeRootCert) } - type Symbol = ConcatSymbol<[PathBuf; 2], PathBuf, PathBuf>; + type Implementation = ConcatSymbol<[PathBuf; 2], PathBuf, PathBuf>; fn create( _resource: &CertChain, target: & as Resource>::Artifact, (cert, root_cert): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { ConcatSymbol::new([cert.into(), root_cert.into()], target.clone().into()) } } -impl SymbolBuilder> for DefaultBuilder { +impl ImplementationBuilder> for DefaultBuilder { type Prerequisites = (CertChain, Key); fn prerequisites(resource: &KeyAndCertBundle) -> Self::Prerequisites { (CertChain(resource.0.clone()), Key(resource.0.clone())) } - type Symbol = ConcatSymbol<[PathBuf; 2], PathBuf, PathBuf>; + type Implementation = ConcatSymbol<[PathBuf; 2], PathBuf, PathBuf>; fn create( _resource: &KeyAndCertBundle, target: & as Resource>::Artifact, (cert_chain, key): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { ConcatSymbol::new([key.into(), cert_chain.into()], target.clone().into()) } } -impl + Clone> SymbolBuilder> for DefaultBuilder { +impl + Clone> ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &File

) -> Self::Prerequisites {} - type Symbol = FileSymbol; + type Implementation = FileSymbol; fn create( resource: &File

, _target: & as Resource>::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { FileSymbol::new(resource.0.clone(), resource.1.clone()) } } -impl<'a, P: AsRef + Clone> SymbolBuilder> for DefaultBuilder { +impl<'a, P: AsRef + Clone> ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &GitCheckout<'a, P>) -> Self::Prerequisites {} - type Symbol = GitCheckoutSymbol; + type Implementation = GitCheckoutSymbol; fn create( resource: &GitCheckout<'a, P>, _target: & as Resource>::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { GitCheckoutSymbol::new(resource.0.clone(), resource.1, resource.2, StdCommandRunner) } } -impl SymbolBuilder for DefaultBuilder { +impl ImplementationBuilder for DefaultBuilder { type Prerequisites = AcmeChallengesNginxSnippet; fn prerequisites(_resource: &DefaultServer) -> Self::Prerequisites { AcmeChallengesNginxSnippet } - type Symbol = ( + type Implementation = ( FileSymbol, ReloadServiceSymbol, ); @@ -207,7 +208,7 @@ impl SymbolBuilder for DefaultBuilder { _resource: &DefaultServer, target: &::Artifact, challenges_snippet_path: ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { ( FileSymbol::new( target.clone().into(), @@ -218,7 +219,7 @@ impl SymbolBuilder for DefaultBuilder { } } -impl + Clone + Display> SymbolBuilder> for DefaultBuilder { +impl + Clone + Display> ImplementationBuilder> for DefaultBuilder { type Prerequisites = (CertChain, Key, AcmeChallengesNginxSnippet); fn prerequisites(resource: &ServeCustom) -> Self::Prerequisites { ( @@ -228,7 +229,7 @@ impl + Clone + Display> SymbolBuilder> for DefaultB ) } - type Symbol = ( + type Implementation = ( FileSymbol, ReloadServiceSymbol, ); @@ -236,7 +237,7 @@ impl + Clone + Display> SymbolBuilder> for DefaultB resource: &ServeCustom, target: & as Resource>::Artifact, (cert, key, challenges_snippet_path): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { ( FileSymbol::new( target.clone().into(), @@ -247,7 +248,7 @@ impl + Clone + Display> SymbolBuilder> for DefaultB } } -impl> SymbolBuilder> for DefaultBuilder { +impl> ImplementationBuilder> for DefaultBuilder { type Prerequisites = ( PhpFpmPool, CertChain, @@ -263,7 +264,7 @@ impl> SymbolBuilder> for Defau ) } - type Symbol = ( + type Implementation = ( FileSymbol, ReloadServiceSymbol, ); @@ -271,7 +272,7 @@ impl> SymbolBuilder> for Defau resource: &ServePhp, target: & as Resource>::Artifact, (pool, cert, key, challenges_snippet_path): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { ( FileSymbol::new( target.clone().into(), @@ -288,7 +289,7 @@ impl> SymbolBuilder> for Defau } } -impl> SymbolBuilder> +impl> ImplementationBuilder> for DefaultBuilder { type Prerequisites = ( @@ -312,7 +313,7 @@ impl> SymbolBuilder, ReloadServiceSymbol, ); @@ -320,7 +321,7 @@ impl> SymbolBuilder, target: & as Resource>::Artifact, (socket, cert, key, challenges_snippet_path): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { ( FileSymbol::new( target.clone().into(), @@ -337,7 +338,7 @@ impl> SymbolBuilder + Clone + Display> SymbolBuilder> for DefaultBuilder { +impl + Clone + Display> ImplementationBuilder> for DefaultBuilder { type Prerequisites = (CertChain, Key, AcmeChallengesNginxSnippet); fn prerequisites(resource: &ServeRedir) -> Self::Prerequisites { ( @@ -347,7 +348,7 @@ impl + Clone + Display> SymbolBuilder> for DefaultBu ) } - type Symbol = ( + type Implementation = ( FileSymbol, ReloadServiceSymbol, ); @@ -355,7 +356,7 @@ impl + Clone + Display> SymbolBuilder> for DefaultBu resource: &ServeRedir, target: & as Resource>::Artifact, (cert, key, challenges_snippet_path): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { ( FileSymbol::new( target.clone().into(), @@ -372,7 +373,7 @@ impl + Clone + Display> SymbolBuilder> for DefaultBu } } -impl + Clone + Display, P: AsRef> SymbolBuilder> +impl + Clone + Display, P: AsRef> ImplementationBuilder> for DefaultBuilder { type Prerequisites = (CertChain, Key, AcmeChallengesNginxSnippet); @@ -384,7 +385,7 @@ impl + Clone + Display, P: AsRef> SymbolBuilder, ReloadServiceSymbol, ); @@ -392,7 +393,7 @@ impl + Clone + Display, P: AsRef> SymbolBuilder, target: & as Resource>::Artifact, (cert, key, challenges_snippet_path): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { ( FileSymbol::new( target.clone().into(), @@ -409,11 +410,11 @@ impl + Clone + Display, P: AsRef> SymbolBuilder SymbolBuilder> for DefaultBuilder { +impl ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &PhpFpmPool) -> Self::Prerequisites {} - type Symbol = ( + type Implementation = ( FileSymbol, ReloadServiceSymbol, ); @@ -421,7 +422,7 @@ impl SymbolBuilder> for DefaultBuilder { resource: &PhpFpmPool, (socket_path, conf_path, user_name, service_name): & as Resource>::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { ( FileSymbol::new( conf_path.clone().into(), @@ -432,11 +433,11 @@ impl SymbolBuilder> for DefaultBuilder { } } -impl> SymbolBuilder> for DefaultBuilder { +impl> ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &SystemdSocketService) -> Self::Prerequisites {} - type Symbol = ( + type Implementation = ( FileSymbol, SystemdUserSessionSymbol<'static, String, StdCommandRunner>, UserServiceSymbol<'static, PathBuf, String, StdCommandRunner>, @@ -445,7 +446,7 @@ impl> SymbolBuilder> for DefaultBui resource: &SystemdSocketService, (socket_path, conf_path, user_name): & as Resource>::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { ( FileSymbol::new( conf_path.clone().into(), @@ -471,44 +472,44 @@ impl> SymbolBuilder> for DefaultBui } } -impl SymbolBuilder> for DefaultBuilder { +impl ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &Dir

) -> Self::Prerequisites {} - type Symbol = DirSymbol

; + type Implementation = DirSymbol

; fn create( resource: &Dir

, _target: & as Resource>::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { DirSymbol::new(resource.0.clone()) } } -impl> SymbolBuilder> for DefaultBuilder { +impl> ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &NpmInstall

) -> Self::Prerequisites {} - type Symbol = NpmInstallSymbol<'static, P, StdCommandRunner>; + type Implementation = NpmInstallSymbol<'static, P, StdCommandRunner>; fn create( resource: &NpmInstall

, _target: & as Resource>::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { NpmInstallSymbol::new(resource.0.clone(), &StdCommandRunner) } } -impl> SymbolBuilder> for DefaultBuilder { +impl> ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &StoredDirectory

) -> Self::Prerequisites {} - type Symbol = SavedDirectorySymbol; + type Implementation = SavedDirectorySymbol; fn create( resource: &StoredDirectory

, target: & as Resource>::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { SavedDirectorySymbol::new( resource.1.clone(), SimpleStorage::new(target.clone().into()), @@ -518,16 +519,16 @@ impl> SymbolBuilder> for DefaultBuilde } } -impl> SymbolBuilder> for DefaultBuilder { +impl> ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &LoadedDirectory

) -> Self::Prerequisites {} - type Symbol = SavedDirectorySymbol; + type Implementation = SavedDirectorySymbol; fn create( resource: &LoadedDirectory

, target: & as Resource>::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { SavedDirectorySymbol::new( resource.1.clone(), SimpleStorage::new(target.clone().into()), @@ -537,69 +538,69 @@ impl> SymbolBuilder> for DefaultBuilde } } -impl SymbolBuilder> for DefaultBuilder { +impl ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &UserForDomain) -> Self::Prerequisites {} - type Symbol = UserSymbol; + type Implementation = UserSymbol; fn create( _resource: &UserForDomain, (user_name, _home_path): & as Resource>::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { UserSymbol::new(user_name.0.clone(), StdCommandRunner) } } -impl SymbolBuilder for DefaultBuilder { +impl ImplementationBuilder for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &User) -> Self::Prerequisites {} - type Symbol = UserSymbol; + type Implementation = UserSymbol; fn create( resource: &User, (): &::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { UserSymbol::new(resource.0.clone(), StdCommandRunner) } } -impl + Clone> SymbolBuilder> for DefaultBuilder { +impl + Clone> ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &Owner

) -> Self::Prerequisites {} - type Symbol = OwnerSymbol; + type Implementation = OwnerSymbol; fn create( resource: &Owner

, (): & as Resource>::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { OwnerSymbol::new(resource.1.clone(), resource.0.clone(), StdCommandRunner) } } -impl SymbolBuilder for DefaultBuilder { +impl ImplementationBuilder for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &AcmeUser) -> Self::Prerequisites {} - type Symbol = UserSymbol; + type Implementation = UserSymbol; fn create( _resource: &AcmeUser, user_name: &::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { UserSymbol::new(user_name.0.clone(), StdCommandRunner) } } -impl SymbolBuilder for DefaultBuilder { +impl ImplementationBuilder for DefaultBuilder { type Prerequisites = AcmeUser; fn prerequisites(_resource: &AcmeChallengesDir) -> Self::Prerequisites { AcmeUser } - type Symbol = ( + type Implementation = ( DirSymbol, OwnerSymbol, ); @@ -607,7 +608,7 @@ impl SymbolBuilder for DefaultBuilder { _resource: &AcmeChallengesDir, target: &::Artifact, user_name: ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { ( DirSymbol::new(target.clone().into()), OwnerSymbol::new(target.clone().into(), user_name.0, StdCommandRunner), @@ -615,18 +616,18 @@ impl SymbolBuilder for DefaultBuilder { } } -impl SymbolBuilder for DefaultBuilder { +impl ImplementationBuilder for DefaultBuilder { type Prerequisites = AcmeChallengesDir; fn prerequisites(_resource: &AcmeChallengesNginxSnippet) -> Self::Prerequisites { AcmeChallengesDir } - type Symbol = FileSymbol; + type Implementation = FileSymbol; fn create( _resource: &AcmeChallengesNginxSnippet, target: &::Artifact, challenges_dir: ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { FileSymbol::new( target.clone().into(), nginx::acme_challenges_snippet(challenges_dir), @@ -634,13 +635,13 @@ impl SymbolBuilder for DefaultBuilder { } } -impl SymbolBuilder for DefaultBuilder { +impl ImplementationBuilder for DefaultBuilder { type Prerequisites = AcmeUser; fn prerequisites(_resource: &AcmeAccountKey) -> Self::Prerequisites { AcmeUser } - type Symbol = ( + type Implementation = ( KeySymbol, OwnerSymbol, ); @@ -648,7 +649,7 @@ impl SymbolBuilder for DefaultBuilder { _resource: &AcmeAccountKey, target: &::Artifact, user_name: ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { ( KeySymbol::new(StdCommandRunner, target.clone().into()), OwnerSymbol::new(target.clone().into(), user_name.0, StdCommandRunner), @@ -656,41 +657,41 @@ impl SymbolBuilder for DefaultBuilder { } } -impl SymbolBuilder for DefaultBuilder { +impl ImplementationBuilder for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &AcmeRootCert) -> Self::Prerequisites {} - type Symbol = FileSymbol; + type Implementation = FileSymbol; fn create( _resource: &AcmeRootCert, target: &::Artifact, (): ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { FileSymbol::new(target.clone().into(), LETS_ENCRYPT_X3_CROSS_SIGNED) } } -impl SymbolBuilder> for DefaultBuilder { +impl ImplementationBuilder> for DefaultBuilder { type Prerequisites = (); fn prerequisites(_resource: &MariaDbUser) -> Self::Prerequisites {} - type Symbol = MariaDbUserSymbol<'static, String, StdCommandRunner>; + type Implementation = MariaDbUserSymbol<'static, String, StdCommandRunner>; fn create( _resource: &MariaDbUser, user_name: & as Resource>::Artifact, _: ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { MariaDbUserSymbol::new(user_name.0.clone(), &StdCommandRunner) } } -impl SymbolBuilder> for DefaultBuilder { +impl ImplementationBuilder> for DefaultBuilder { type Prerequisites = MariaDbUser; fn prerequisites(resource: &MariaDbDatabase) -> Self::Prerequisites { MariaDbUser(resource.0.clone()) } - type Symbol = ( + type Implementation = ( MariaDbDatabaseSymbol<'static, String, SimpleStorage, StdCommandRunner>, MariaDbDumpSymbol<'static, String, StdCommandRunner, SimpleStorage>, ); @@ -698,7 +699,7 @@ impl SymbolBuilder> for DefaultBuilder { _resource: &MariaDbDatabase, (db_name, _, data_path): & as Resource>::Artifact, _: ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { let db_dump = SimpleStorage::new(data_path.clone().into()); ( MariaDbDatabaseSymbol::new(db_name.0.clone(), db_dump.clone(), &StdCommandRunner), @@ -707,34 +708,35 @@ impl SymbolBuilder> for DefaultBuilder { } } -impl> SymbolBuilder> for DefaultBuilder { +impl> ImplementationBuilder> for DefaultBuilder { type Prerequisites = Dir; fn prerequisites(resource: &WordpressPlugin

) -> Self::Prerequisites { Dir(resource.0.as_ref().join("wp-content/plugins")) } - type Symbol = WordpressPluginSymbol<'static, P, &'static str, StdCommandRunner>; + type Implementation = WordpressPluginSymbol<'static, P, &'static str, StdCommandRunner>; fn create( resource: &WordpressPlugin

, (): & as Resource>::Artifact, _: ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { WordpressPluginSymbol::new(resource.0.clone(), resource.1, &StdCommandRunner) } } -impl> SymbolBuilder> for DefaultBuilder { +impl> ImplementationBuilder> for DefaultBuilder { type Prerequisites = Dir; fn prerequisites(resource: &WordpressTranslation

) -> Self::Prerequisites { Dir(resource.0.as_ref().join("wp-content/languages")) } - type Symbol = WordpressTranslationSymbol<'static, &'static str, PathBuf, StdCommandRunner>; + type Implementation = + WordpressTranslationSymbol<'static, &'static str, PathBuf, StdCommandRunner>; fn create( resource: &WordpressTranslation

, (): & as Resource>::Artifact, _: ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { WordpressTranslationSymbol::new( resource.0.as_ref().join("wp-content/languages"), resource.1, @@ -744,18 +746,18 @@ impl> SymbolBuilder> for DefaultB } } -impl SymbolBuilder> for DefaultBuilder { +impl ImplementationBuilder> for DefaultBuilder { type Prerequisites = UserForDomain; fn prerequisites(resource: &Cron) -> Self::Prerequisites { UserForDomain(resource.0.clone()) } - type Symbol = CronSymbol<'static, String, String, StdCommandRunner>; + type Implementation = CronSymbol<'static, String, String, StdCommandRunner>; fn create( resource: &Cron, (): & as Resource>::Artifact, user_name: ::Artifact, - ) -> Self::Symbol { + ) -> Self::Implementation { CronSymbol::new((user_name.0).0, resource.1.clone(), &StdCommandRunner) } } diff --git a/src/command_runner.rs b/src/command_runner.rs index b4e0956..5d8fd2f 100644 --- a/src/command_runner.rs +++ b/src/command_runner.rs @@ -1,9 +1,11 @@ +use async_trait::async_trait; use std::error::Error; use std::ffi::OsStr; -use std::io::{Result as IoResult, Write}; -use std::process::Command; +use std::io::Result as IoResult; use std::process::Output; use std::process::Stdio; +use tokio::io::AsyncWriteExt; +use tokio::process::Command; #[macro_export] macro_rules! args { @@ -13,46 +15,48 @@ macro_rules! args { ($($x:expr,)*) => (args![$($x),*]) // handle trailing commas } +fn check_success(output: Output) -> Result> { + if output.status.success() { + Ok(output) + } else { + Err(String::from_utf8(output.stderr)?.into()) + } +} + +pub fn is_success(res: Result) -> Result> { + check_success(res?) +} + +pub fn get_output(output: Output) -> Result, Box> { + Ok(check_success(output)?.stdout) +} + +#[async_trait(?Send)] pub trait CommandRunner { - fn run_with_args_and_stdin( - &self, - program: &str, - args: &[&OsStr], - stdin: &str, - ) -> IoResult; - fn run_with_args(&self, program: &str, args: &[&OsStr]) -> IoResult { - self.run_with_args_and_stdin(program, args, "") + async fn run(&self, program: &str, args: &[&OsStr], stdin: &str) -> IoResult; + + async fn run_with_args(&self, program: &str, args: &[&OsStr]) -> IoResult { + self.run(program, args, "").await } - fn get_output(&self, program: &str, args: &[&OsStr]) -> Result, Box> { - let output = self.run_with_args(program, args)?; - if !output.status.success() { - return Err(String::from_utf8(output.stderr)?.into()); - } - Ok(output.stdout) + async fn get_output(&self, program: &str, args: &[&OsStr]) -> Result, Box> { + let output = self.run_with_args(program, args).await?; + get_output(output) } - fn get_stderr(&self, program: &str, args: &[&OsStr]) -> Result, Box> { - let output = self.run_with_args(program, args)?; - if !output.status.success() { - return Err(String::from_utf8(output.stderr)?.into()); - } - Ok(output.stderr) + async fn run_successfully(&self, program: &str, args: &[&OsStr]) -> Result<(), Box> { + is_success(self.run(program, args, "").await)?; + Ok(()) } - fn run_successfully(&self, program: &str, args: &[&OsStr]) -> Result<(), Box> { - self.get_output(program, args).map(|_| ()) + async fn get_stderr(&self, program: &str, args: &[&OsStr]) -> Result, Box> { + Ok(is_success(self.run_with_args(program, args).await)?.stderr) } } #[derive(Debug)] pub struct StdCommandRunner; +#[async_trait(?Send)] impl CommandRunner for StdCommandRunner { - fn run_with_args_and_stdin( - &self, - program: &str, - args: &[&OsStr], - input: &str, - ) -> IoResult { - // FIXME: logger + async fn run(&self, program: &str, args: &[&OsStr], input: &str) -> IoResult { //println!("{} {:?}", program, args); let mut child = Command::new(program) .args(args) @@ -64,9 +68,10 @@ impl CommandRunner for StdCommandRunner { let stdin = child.stdin.as_mut().expect("Failed to open stdin"); stdin .write_all(input.as_bytes()) + .await .expect("Failed to write to stdin"); - let res = child.wait_with_output(); - println!("{:?}", res); + let res = child.wait_with_output().await; + //println!("{:?}", res); res } } @@ -87,7 +92,6 @@ impl<'a, U: AsRef, C: CommandRunner> SetuidCommandRunner<'a, U, C> { } use std::env; -use std::os::unix::process::CommandExt; use users::get_user_by_name; struct TempSetEnv<'a> { @@ -115,13 +119,9 @@ impl Drop for TempSetEnv<'_> { } } +#[async_trait(?Send)] impl, C: CommandRunner> CommandRunner for SetuidCommandRunner<'_, U, C> { - fn run_with_args_and_stdin( - &self, - program: &str, - args: &[&OsStr], - input: &str, - ) -> IoResult { + async fn run(&self, program: &str, args: &[&OsStr], input: &str) -> IoResult { let uid = get_user_by_name(self.user_name.as_ref()) .expect("User does not exist") .uid(); @@ -140,9 +140,10 @@ impl, C: CommandRunner> CommandRunner for SetuidCommandRunner<'_, let stdin = child.stdin.as_mut().expect("Failed to open stdin"); stdin .write_all(input.as_bytes()) + .await .expect("Failed to write to stdin"); - let res = child.wait_with_output(); - println!("{:?}", res); + let res = child.wait_with_output().await; + //println!("{:?}", res); res } } @@ -170,24 +171,43 @@ where // 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, { - fn run_with_args_and_stdin( - &self, - program: &str, - args: &[&OsStr], - input: &str, - ) -> IoResult { + async fn run(&self, program: &str, args: &[&OsStr], input: &str) -> IoResult { let raw_new_args = [self.user_name, "-s", "/usr/bin/env", "--", program]; let mut new_args: Vec<&OsStr> = raw_new_args.iter().map(|s| s.as_ref()).collect(); new_args.extend_from_slice(args); - self - .command_runner - .run_with_args_and_stdin("su", &new_args, input) + self.command_runner.run("su", &new_args, input).await } } #[cfg(test)] -mod test {} +mod test { + use crate::args; + use crate::async_utils::run; + use crate::command_runner::{CommandRunner, StdCommandRunner}; + use futures::future::FutureExt; + use std::time::Instant; + + #[test] + fn test() { + let c = StdCommandRunner; + run(async { + let args = args!["1"]; + let start = Instant::now(); + let res = c.run("sleep", args, "").fuse(); + let ps = c.run("ps", args![], "").fuse(); + futures::pin_mut!(res, ps); + loop { + futures::select! { + _ = res => {}, + _ = ps => assert!((Instant::now() - start).as_millis() < 1000), + complete => break, + } + } + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 7a09805..70496ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,10 @@ #![warn( macro_use_extern_crate, meta_variable_misuse, + missing_debug_implementations, non_ascii_idents, trivial_numeric_casts, unused, - unreachable_pub, unsafe_code, unstable_features, variant_size_differences, @@ -15,26 +15,27 @@ clippy::pedantic )] #![allow( + unreachable_pub, single_use_lifetimes, trivial_casts, - clippy::module_name_repetitions, clippy::cargo_common_metadata, + clippy::future_not_send, + clippy::missing_errors_doc, + clippy::module_name_repetitions, rustdoc, missing_docs, - missing_copy_implementations, - missing_debug_implementations + missing_copy_implementations )] #[macro_use] mod for_each_tuple; - +pub mod async_utils; pub mod bin; pub mod build; #[macro_use] pub mod command_runner; pub mod loggers; pub mod resources; -pub mod schema; pub mod storage; pub mod symbols; pub mod templates; @@ -46,6 +47,8 @@ mod setup; pub mod static_files; mod to_artifact; -pub use builder::{DefaultBuilder, SymbolBuilder}; +pub use builder::{DefaultBuilder, ImplementationBuilder}; pub use locator::{DefaultLocator, DefaultPolicy, Policy, ResourceLocator}; -pub use setup::Setup; +pub use setup::{ + DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner, Setup, SymbolRunner, +}; diff --git a/src/locator.rs b/src/locator.rs index eaaddeb..2eee3f4 100644 --- a/src/locator.rs +++ b/src/locator.rs @@ -35,6 +35,7 @@ pub trait Policy { } } +#[derive(Debug)] pub struct DefaultPolicy; impl Policy for DefaultPolicy {} @@ -46,6 +47,7 @@ pub trait ResourceLocator { R: Resource; } +#[derive(Debug)] pub struct DefaultLocator

{ phantom: PhantomData

, } diff --git a/src/loggers.rs b/src/loggers.rs index 71b928f..8c42e1c 100644 --- a/src/loggers.rs +++ b/src/loggers.rs @@ -1,36 +1,129 @@ +use std::cell::RefCell; +use std::cmp::min; 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(Debug)] +pub struct Entry(pub Level, pub S); + pub trait Logger { - fn write(&mut self, msg: &str); - fn debug(&mut 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 + Into>(&self, msg: S) + where + Self: Sized, + { + self.writeln(4, msg) + } + fn trace + Into>(&self, msg: S) + where + Self: Sized, + { + self.writeln(5, msg) + } + 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); + c += 1; + } + c + } } -pub struct StdErrLogger; +#[derive(Debug, Default)] +pub struct StdErrLogger { + line_started: RefCell, +} impl Logger for StdErrLogger { - fn debug(&mut self, str: &str) { - writeln!(&mut stderr(), "{}", str).unwrap(); + fn write + Into>(&self, _level: Level, msg: S) { + *self.line_started.borrow_mut() = true; + write!(&mut stderr(), "{}", msg.as_ref()).unwrap(); + } + fn writeln + Into>(&self, _level: Level, msg: S) { + if self.line_started.replace(false) { + writeln!(&mut stderr()).unwrap(); + } + writeln!(&mut stderr(), "{}", msg.as_ref()).unwrap(); } - fn write(&mut self, str: &str) { - writeln!(&mut stderr(), "{}", str).unwrap(); +} +impl Drop for StdErrLogger { + fn drop(&mut self) { + if *self.line_started.borrow() == true { + writeln!(&mut stderr()).unwrap(); + } } } -pub struct FilteringLogger<'a> { - logger: &'a mut dyn Logger, +#[derive(Debug)] +pub struct FilteringLogger<'a, L> { + logger: &'a L, + max_level: Level, } -impl<'a> FilteringLogger<'a> { - pub fn new(logger: &'a mut dyn Logger) -> Self { - FilteringLogger { logger } +impl<'a, L> FilteringLogger<'a, L> { + pub fn new(logger: &'a L, max_level: Level) -> Self { + Self { logger, max_level } } } -impl Logger for FilteringLogger<'_> { - fn debug(&mut self, _str: &str) {} - fn write(&mut self, str: &str) { - self.logger.write(str) +impl<'a, L: Logger> Logger for FilteringLogger<'a, L> { + fn write + Into>(&self, level: Level, str: S) { + if level <= self.max_level { + self.logger.write(level, str) + } + } + fn writeln + Into>(&self, level: Level, str: S) { + if level <= self.max_level { + self.logger.writeln(level, str) + } + } +} + +#[derive(Debug, Default)] +pub struct StoringLogger { + log: RefCell>>, +} + +impl StoringLogger { + pub fn new() -> Self { + Self::default() + } + + pub fn release(self) -> Vec> { + self.log.into_inner() + } +} + +impl Logger for StoringLogger { + fn write + Into>(&self, level: Level, line: S) { + let mut log = self.log.borrow_mut(); + let entry = log + .pop() + .map(|e| Entry(min(e.0, level), e.1 + line.as_ref())) + .unwrap_or_else(|| Entry(level, line.into())); + log.push(entry); + } + fn writeln + Into>(&self, level: Level, line: S) { + self.log.borrow_mut().push(Entry(level, line.into())); } } diff --git a/src/resources/mod.rs b/src/resources/mod.rs index 4e717af..a590c47 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -218,28 +218,51 @@ impl Resource for Cron { type Artifact = (); } -pub trait BorrowResource { - fn borrow_resource(&self) -> Option<&R>; +use std::rc::{Rc, Weak}; + +pub trait FromResource { + fn from_resource(from: R) -> (Self, Weak) + where + Self: Sized; +} + +pub trait FromArtifact { + fn from_artifact(from: R::Artifact) -> Self + where + Self: Sized; + fn into_artifact(self) -> R::Artifact + where + Self: Sized; } macro_rules! default_resources { ( $($name:ident: $type:ty,)* ) => { #[derive(Debug, PartialEq, Eq, Hash)] pub enum DefaultResources<'a, D> { - $( $name($type) ),* + $( $name(Rc<$type>) ),* } - $(impl<'a, D> From<$type> for DefaultResources<'a, D> { - fn from(from: $type) -> Self { - Self::$name(from) + $(impl<'a, D> FromResource<$type> for DefaultResources<'a, D> { + fn from_resource(from: $type) -> (Self, Weak<$type>) { + let inner = Rc::new(from); + (Self::$name(Rc::clone(&inner)), Rc::downgrade(&inner)) } })* - $(impl<'a, D> BorrowResource<$type> for DefaultResources<'a, D> { - fn borrow_resource(&self) -> Option<&$type> { + #[derive(Clone, Debug)] + pub enum DefaultArtifacts<'a, D> { + $( $name(<$type as Resource>::Artifact) ),* + } + + $(impl<'a, D> FromArtifact<$type> for DefaultArtifacts<'a, D> { + fn from_artifact(from: <$type as Resource>::Artifact) -> Self { + Self::$name(from) + } + + fn into_artifact(self) -> <$type as Resource>::Artifact { match self { - Self::$name(v) => Some(v), - _ => None + Self::$name(inner) => inner, + _ => panic!() } } })* diff --git a/src/schema.rs b/src/schema.rs deleted file mode 100644 index dab238d..0000000 --- a/src/schema.rs +++ /dev/null @@ -1,274 +0,0 @@ -use crate::loggers::Logger; -use crate::symbols::Symbol; -use std::cell::RefCell; -use std::error::Error; -use std::fmt; -use std::fmt::Debug; - -pub trait SymbolRunner { - fn run_symbol(&self, symbol: &S, force: bool) -> Result>; -} - -impl SymbolRunner for Box { - fn run_symbol(&self, symbol: &S, force: bool) -> Result> { - (**self).run_symbol(symbol, force) - } -} - -#[derive(Debug)] -pub enum SymbolRunError { - Symbol(Box), - ExecuteDidNotReach(()), -} - -impl Error for SymbolRunError { - fn cause(&self) -> Option<&dyn Error> { - match self { - Self::Symbol(ref e) => Some(&**e), - Self::ExecuteDidNotReach(_) => None, - } - } -} - -impl fmt::Display for SymbolRunError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Symbol(ref e) => write!(f, "{}", e), - Self::ExecuteDidNotReach(_) => write!(f, "Target not reached after executing symbol"), - } - } -} - -pub struct InitializingSymbolRunner { - logger: RefCell, -} - -impl InitializingSymbolRunner { - pub fn new(logger: L) -> Self { - Self { - logger: RefCell::new(logger), - } - } - - fn exec_symbol(&self, symbol: &S) -> Result<(), Box> { - let mut logger = self.logger.borrow_mut(); - logger.write(format!("Executing {:?}", symbol).as_str()); - symbol.execute()?; - let target_reached = symbol.target_reached()?; - logger.debug( - format!( - "Symbol reports target_reached: {:?} (should be true)", - target_reached - ) - .as_str(), - ); - if target_reached { - Ok(()) - } else { - Err(Box::new(SymbolRunError::ExecuteDidNotReach(()))) - } - } -} - -impl SymbolRunner for InitializingSymbolRunner { - fn run_symbol(&self, symbol: &S, force: bool) -> Result> { - let mut logger = self.logger.borrow_mut(); - let executed = if force { - logger.debug("Forcing symbol execution"); - drop(logger); - self.exec_symbol(symbol)?; - true - } else { - let target_reached = symbol.target_reached()?; - if target_reached { - logger.write(format!("{:?} already reached", symbol).as_str()); - } else { - logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str()); - drop(logger); - self.exec_symbol(symbol)?; - } - !target_reached - }; - Ok(executed) - } -} - -pub struct DrySymbolRunner { - logger: RefCell, -} - -impl DrySymbolRunner { - pub fn new(logger: L) -> Self { - Self { - logger: RefCell::new(logger), - } - } -} - -impl SymbolRunner for DrySymbolRunner { - fn run_symbol(&self, symbol: &S, force: bool) -> Result> { - let mut logger = self.logger.borrow_mut(); - let would_execute = if force { - logger.write(format!("Would force-execute {:?}", symbol).as_str()); - true - } else { - let target_reached = symbol.target_reached()?; - logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str()); - if !target_reached { - logger.write(format!("Would execute {:?}", symbol).as_str()); - } - !target_reached - }; - Ok(would_execute) - } -} - -pub struct ReportingSymbolRunner<'a, R, L>(&'a R, RefCell); - -impl<'a, R, L> ReportingSymbolRunner<'a, R, L> { - pub fn new(symbol_runner: &'a R, logger: L) -> Self { - ReportingSymbolRunner(symbol_runner, RefCell::new(logger)) - } -} - -impl<'a, R, L> SymbolRunner for ReportingSymbolRunner<'a, R, L> -where - R: SymbolRunner, - L: Logger, -{ - fn run_symbol(&self, symbol: &S, force: bool) -> Result> { - let mut logger = self.1.borrow_mut(); - logger.debug(format!("Running symbol {:?}", symbol).as_str()); - let res = self.0.run_symbol(symbol, force); - if let Err(ref e) = res { - logger.write(format!("Failed on {:?} with {}, aborting.", symbol, e).as_str()) - } else { - logger.debug(format!("Successfully finished {:?}", symbol).as_str()) - } - res - } -} - -#[cfg(test)] -mod test { - use std::cell::RefCell; - use std::error::Error; - use std::fmt; - - use crate::loggers::Logger; - use crate::schema::InitializingSymbolRunner; - use crate::schema::SymbolRunner; - use crate::symbols::Symbol; - - #[derive(Debug, PartialEq, Clone)] - enum DummySymbolError { - Error(()), - } - - impl Error for DummySymbolError {} - - impl fmt::Display for DummySymbolError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Dummy symbol error") - } - } - - #[derive(Debug)] - struct DummySymbol { - _target_reached: RefCell, - _execute: RefCell, - } - - impl< - E: Iterator>>, - T: Iterator>>, - > Symbol for DummySymbol - { - fn target_reached(&self) -> Result> { - self._target_reached.borrow_mut().next().unwrap() - } - fn execute(&self) -> Result<(), Box> { - self._execute.borrow_mut().next().unwrap() - } - } - - impl< - E: Iterator>>, - T: Iterator>>, - > DummySymbol - { - fn new< - IE: IntoIterator>>, - IT: IntoIterator>>, - >( - target_reached: IT, - execute: IE, - ) -> Self { - Self { - _target_reached: RefCell::new(target_reached.into_iter()), - _execute: RefCell::new(execute.into_iter()), - } - } - } - - struct DummyLogger { - log: Vec, - } - - impl DummyLogger { - fn new() -> DummyLogger { - DummyLogger { log: Vec::new() } - } - } - - impl Logger for DummyLogger { - fn write(&mut self, line: &str) { - self.log.push(line.into()); - } - fn debug(&mut self, line: &str) { - self.log.push(line.into()); - } - } - - #[test] - fn nothing_needed_to_be_done() { - let result = InitializingSymbolRunner::new(DummyLogger::new()) - .run_symbol(&DummySymbol::new(vec![Ok(true)], vec![Ok(())]), false); - assert!(result.is_ok()); - } - - #[test] - fn everything_is_ok() { - let result = InitializingSymbolRunner::new(DummyLogger::new()).run_symbol( - &DummySymbol::new(vec![Ok(true), Ok(false)], vec![Ok(())]), - false, - ); - assert!(result.is_ok()); - } - - #[test] - fn executing_did_not_change_state() { - let result = InitializingSymbolRunner::new(DummyLogger::new()).run_symbol( - &DummySymbol::new(vec![Ok(false), Ok(false)], vec![Ok(())]), - false, - ); - assert_eq!( - result.unwrap_err().to_string(), - "Target not reached after executing symbol" - ); - } - - #[test] - fn executing_did_not_work() { - let err = InitializingSymbolRunner::new(DummyLogger::new()) - .run_symbol( - &DummySymbol::new( - vec![Ok(false)], - vec![Err(Box::new(DummySymbolError::Error(())) as Box)], - ), - false, - ) - .unwrap_err(); - assert_eq!(err.to_string(), "Dummy symbol error"); - } -} diff --git a/src/setup.rs b/src/setup.rs deleted file mode 100644 index 5e03f80..0000000 --- a/src/setup.rs +++ /dev/null @@ -1,396 +0,0 @@ -use crate::resources::{BorrowResource, DefaultResources, Resource}; -use crate::schema::SymbolRunner; -use crate::symbols::Symbol; -use crate::to_artifact::ToArtifact; -use crate::{DefaultBuilder, DefaultLocator, ResourceLocator, SymbolBuilder}; -use std::collections::HashSet; -use std::error::Error; -use std::fmt::Debug; -use std::hash::Hash; -use std::marker::PhantomData; - -pub trait CanHandle { - fn handle(&mut self, x: X) -> Result<(X::Artifact, bool), Box>; -} - -macro_rules! can_handle { - ( $($name:ident)* ) => ( - #[allow(non_snake_case)] - impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, $($name: Resource,)*> - CanHandle<($($name,)*)> - for Setup<_SR, _L, _R, _B> - where - $( - _B: SymbolBuilder<$name>, - <_B as SymbolBuilder<$name>>::Symbol: Runnable + Debug, - _L: ResourceLocator<$name>, - _R: From<$name> + BorrowResource<$name>, - Self: CanHandle<<_B as SymbolBuilder<$name>>::Prerequisites> + - CanHandle<<_L as ResourceLocator<$name>>::Prerequisites> - ),* - { - fn handle(&mut self, ($($name,)*): ($($name,)*)) -> Result<(($($name::Artifact,)*), bool), Box> - { - $(let $name = self.add($name)?;)* - Ok((($($name.0,)*), false $(|| $name.1)*)) - } - } - ); -} - -for_each_tuple!(can_handle); - -// This is for self-referential T -// FIXME: Wait for specialization -impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, T: Resource> CanHandle> - for Setup<_SR, _L, _R, _B> -where - _B: SymbolBuilder, - <_B as SymbolBuilder>::Symbol: Runnable + Debug, - _L: ResourceLocator>, - _R: From + BorrowResource, - Self: CanHandle<<_B as SymbolBuilder>::Prerequisites>, -{ - fn handle( - &mut self, - r: Option, - ) -> Result<( as ToArtifact>::Artifact, bool), Box> { - Ok(match r { - Some(r) => { - let (result, did_run) = self.add(r)?; - (Some(result), did_run) - } - None => (None, false), - }) - } -} - -impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, T: Resource> CanHandle for Setup<_SR, _L, _R, _B> -where - _B: SymbolBuilder, - <_B as SymbolBuilder>::Symbol: Runnable + Debug, - _L: ResourceLocator, - _R: From + Debug + BorrowResource, - Self: CanHandle<<_B as SymbolBuilder>::Prerequisites> - + CanHandle<<_L as ResourceLocator>::Prerequisites>, -{ - fn handle(&mut self, r: T) -> Result<(::Artifact, bool), Box> { - self.add(r) - } -} - -pub struct Setup< - SR, - L = DefaultLocator, - R = DefaultResources<'static, &'static str>, - B = DefaultBuilder, -> { - symbol_runner: SR, - resources: HashSet, - phantom: PhantomData<(L, B)>, -} - -// https://github.com/rust-lang/rust/issues/27336 -impl Setup { - pub fn new(symbol_runner: SR) -> Self { - Self { - symbol_runner, - resources: Default::default(), - phantom: Default::default(), - } - } -} - -impl Setup { - pub fn new_with(symbol_runner: SR) -> Self { - Self { - symbol_runner, - resources: Default::default(), - phantom: Default::default(), - } - } -} - -pub trait Runnable { - fn run(&self, runner: &R, force: bool) -> Result>; -} - -impl Runnable for S { - fn run(&self, runner: &R, force: bool) -> Result> { - runner.run_symbol(self, force) - } -} - -macro_rules! runnable_for_tuple { - ( $($name:ident)* ) => ( - #[allow(non_snake_case)] - impl<$($name: Symbol + Debug,)*> Runnable for ($($name,)*) { - #[allow(unused)] - fn run<_R: SymbolRunner>(&self, runner: &_R, force: bool) -> Result> { - let ($($name,)*) = self; - let mut result = false; - $(result = runner.run_symbol($name, force || result)? || result;)* - Ok(result) - } - } - ); -} - -for_each_tuple!(runnable_for_tuple); - -impl Setup { - pub fn add(&mut self, resource: R) -> Result<(R::Artifact, bool), Box> - where - B: SymbolBuilder, - >::Symbol: Runnable + Debug, - L: ResourceLocator, - Rs: From + BorrowResource, - Self: CanHandle + CanHandle<>::Prerequisites>, - { - self.add_force(resource, false) - } - - pub fn add_force( - &mut self, - resource: R, - force_run: bool, - ) -> Result<(R::Artifact, bool), Box> - where - B: SymbolBuilder, - >::Symbol: Runnable + Debug, - L: ResourceLocator, - Rs: From + BorrowResource, - Self: CanHandle + CanHandle<>::Prerequisites>, - { - let (target, target_prereqs) = L::locate(&resource); - let storable_resource = Rs::from(resource); - let did_run = if self.resources.get(&storable_resource).is_some() { - assert!( - !force_run, - "Forcing to run an already-added resource is a logical error" - ); - false - } else { - let (_, target_prereqs_did_run) = self.handle(target_prereqs)?; - let (symbol, prereqs_did_run) = - self.get_symbol(storable_resource.borrow_resource().unwrap(), &target)?; - self.resources.insert(storable_resource); - self.run_symbol( - symbol, - force_run || target_prereqs_did_run || prereqs_did_run, - )? - }; - Ok((target, did_run)) - } - - fn get_symbol( - &mut self, - resource: &R, - target: &R::Artifact, - ) -> Result<(>::Symbol, bool), Box> - where - B: SymbolBuilder, - Self: CanHandle, - { - let (prereqs, prereqs_did_run) = self.handle(B::prerequisites(resource))?; - Ok((B::create(resource, target, prereqs), prereqs_did_run)) - } - - pub fn run_symbol(&self, symbol: S, force: bool) -> Result> { - symbol.run(&self.symbol_runner, force) - } -} - -#[cfg(test)] -mod test { - use crate::resources::{BorrowResource, Resource}; - use crate::schema::SymbolRunner; - use crate::symbols::Symbol; - use crate::to_artifact::ToArtifact; - use crate::{ResourceLocator, Setup, SymbolBuilder}; - use std::cell::RefCell; - use std::error::Error; - use std::fmt::Debug; - use std::rc::Rc; - - struct TestSymbolRunner { - count: Rc>, - } - - impl SymbolRunner for TestSymbolRunner { - fn run_symbol( - &self, - symbol: &S, - force: bool, - ) -> Result> { - let run = force || !symbol.target_reached()?; - 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(TestResource<&'static str>), - B(TestResource<()>), - } - impl From> for Resources { - fn from(from: TestResource<&'static str>) -> Self { - Self::A(from) - } - } - impl From> for Resources { - fn from(from: TestResource<()>) -> Self { - Self::B(from) - } - } - - impl BorrowResource> for Resources { - fn borrow_resource(&self) -> Option<&TestResource<&'static str>> { - match self { - Self::A(a) => Some(a), - _ => None, - } - } - } - impl BorrowResource> for Resources { - fn borrow_resource(&self) -> Option<&TestResource<()>> { - match self { - Self::B(b) => Some(b), - _ => None, - } - } - } - - struct TestResourceLocator; - impl ResourceLocator> for TestResourceLocator { - type Prerequisites = (); - fn locate(_resource: &TestResource) -> ( as ToArtifact>::Artifact, ()) { - ((), ()) - } - } - - struct TestSymbolBuilder; - impl SymbolBuilder> for TestSymbolBuilder { - type Symbol = TestSymbol; - type Prerequisites = TestResource<()>; - - fn prerequisites(resource: &TestResource<&'static str>) -> Self::Prerequisites { - TestResource(resource.1, ()) - } - fn create( - resource: &TestResource<&'static str>, - (): &(), - _inputs: ::Artifact, - ) -> Self::Symbol { - TestSymbol { - reached: resource.0.chars().next().unwrap().is_uppercase(), - } - } - } - impl SymbolBuilder> for TestSymbolBuilder { - type Symbol = TestSymbol; - type Prerequisites = (); - - fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {} - fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Symbol { - TestSymbol { - reached: resource.0.chars().next().unwrap().is_uppercase(), - } - } - } - - #[derive(Debug)] - struct TestSymbol { - reached: bool, - } - impl Symbol for TestSymbol { - fn target_reached(&self) -> Result> { - Ok(self.reached) - } - fn execute(&self) -> Result<(), Box> { - Ok(()) - } - } - - fn get_setup() -> ( - Rc>, - Setup, - ) { - let count = Rc::new(RefCell::new(0)); - let runner = TestSymbolRunner { - count: Rc::clone(&count), - }; - (count, Setup::new_with(runner)) - } - - #[test] - fn correctly_uses_force() { - let (count, mut setup) = get_setup(); - setup.add(TestResource("A", "b")).unwrap(); - assert_eq!(*count.borrow(), 2); - setup.add(TestResource("A", "b")).unwrap(); - assert_eq!(*count.borrow(), 2); - - let (count, mut setup) = get_setup(); - setup.add(TestResource("A", "B")).unwrap(); - assert_eq!(*count.borrow(), 0); - } - - #[test] - fn correctly_handles_symbol_tuples() { - let (count, setup) = get_setup(); - setup - .run_symbol( - (TestSymbol { reached: false }, TestSymbol { reached: false }), - false, - ) - .unwrap(); - assert_eq!(*count.borrow(), 2); - - let (count, setup) = get_setup(); - setup - .run_symbol( - (TestSymbol { reached: true }, TestSymbol { reached: false }), - false, - ) - .unwrap(); - assert_eq!(*count.borrow(), 1); - - // An unreached symbol forces all further symbols - let (count, setup) = get_setup(); - setup - .run_symbol( - (TestSymbol { reached: false }, TestSymbol { reached: true }), - false, - ) - .unwrap(); - assert_eq!(*count.borrow(), 2); - - let (count, setup) = get_setup(); - setup - .run_symbol( - (TestSymbol { reached: true }, TestSymbol { reached: true }), - false, - ) - .unwrap(); - assert_eq!(*count.borrow(), 0); - - let (count, setup) = get_setup(); - setup - .run_symbol( - (TestSymbol { reached: true }, TestSymbol { reached: true }), - true, - ) - .unwrap(); - assert_eq!(*count.borrow(), 2); - } -} diff --git a/src/setup/core.rs b/src/setup/core.rs new file mode 100644 index 0000000..7570cb3 --- /dev/null +++ b/src/setup/core.rs @@ -0,0 +1,191 @@ +use super::runnable::Runnable; +use super::util::{AddResult, AddableResource}; +use super::Setup; +use super::SymbolRunner; +use crate::async_utils::try_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) -> AddResult; +} + +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)] + async fn add_generic(&self, ($($name,)*): ($($name,)*)) -> Result<(($($name::Artifact,)*), bool), Box> + { + let x: Result<_, Box> = try_join!($(self.add_async($name, false),)*); + let ($($name,)*) = x?; + Ok((($($name.0,)*), false $(|| $name.1)*)) + } + } + ); +} + +for_each_tuple!(add_generic); + +// This is for self-referential T +// FIXME: Wait for specialization +#[async_trait(?Send)] +impl< + SR: 'static + SymbolRunner, + LOG: 'static + Logger, + 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) -> AddResult> { + Ok(match r { + Some(r) => { + let (result, did_run) = self.add_async(r, false).await?; + (Some(result), did_run) + } + None => (None, false), + }) + } +} + +#[async_trait(?Send)] +impl< + LOG: 'static + Logger, + 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) -> AddResult { + self.add_async(r, false).await + } +} + +#[async_trait(?Send)] +pub trait SetupCore { + async fn add>( + &self, + setup: &S, + parent_logger: &LOG, + resource: RR, + force_run: bool, + ) -> AddResult; +} + +#[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, + parent_logger: &LOG, + resource: RR, + force_run: bool, + ) -> AddResult { + let resource = resource.as_ref(); + let logger = StoringLogger::new(); + logger.write(4, format!("Adding {:?} ... ", resource)); + let result = { + logger.trace(format!(" (force_run is {})", force_run)); + let (location, location_prereqs) = L::locate(resource); + logger.trace(format!("Adding location prereqs for {:?}", resource)); + let (_, location_prereqs_did_run) = setup.add_generic(location_prereqs).await?; + logger.trace(format!( + "Location prereqs for {:?} did_run: {}", + resource, location_prereqs_did_run + )); + logger.trace(format!("Adding implementation prereqs for {:?}", resource)); + let (prereqs, prereqs_did_run) = setup.add_generic(B::prerequisites(resource)).await?; + 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 = implementation + .run( + &self.symbol_runner, + &logger, + force_run || location_prereqs_did_run || prereqs_did_run, + ) + .await?; + Ok((location, did_run)) + }; + logger.write(4, "done."); + let max_level = if result.is_err() { 5 } else { 3 }; + if parent_logger.put(logger.release().into_iter().filter(|e| e.0 <= max_level)) == 0 { + parent_logger.write(3, "."); + } + result + } +} + +#[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.debug(format!("Directly running {:?} ...", symbol)); + let result = self.symbol_runner.run_symbol(symbol, &logger, force).await; + logger.debug("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 new file mode 100644 index 0000000..f414fc8 --- /dev/null +++ b/src/setup/mod.rs @@ -0,0 +1,10 @@ +mod core; +mod util; +pub use util::{AddResult, AddableResource}; +mod symbol_runner; +pub use symbol_runner::{ + DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner, SymbolRunner, +}; +mod runnable; +mod setup; +pub use setup::Setup; diff --git a/src/setup/runnable.rs b/src/setup/runnable.rs new file mode 100644 index 0000000..5c3a1c3 --- /dev/null +++ b/src/setup/runnable.rs @@ -0,0 +1,193 @@ +use super::SymbolRunner; +use crate::loggers::Logger; +use crate::symbols::Symbol; +use async_trait::async_trait; +use std::error::Error; +use std::fmt::Debug; + +#[async_trait(?Send)] +pub trait Runnable { + async fn run( + &self, + runner: &R, + logger: &L, + force: bool, + ) -> Result>; +} + +#[async_trait(?Send)] +impl Runnable for S +where + Self: Symbol + Debug, +{ + async fn run( + &self, + runner: &R, + logger: &L, + force: bool, + ) -> Result> { + runner.run_symbol(self, logger, force).await + } +} + +macro_rules! runnable_for_tuple { + ( $($name:ident)* ) => ( + #[async_trait(?Send)] + #[allow(non_snake_case)] + impl<$($name: Symbol + Debug,)*> Runnable for ($($name,)*) { + #[allow(unused)] + 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;)* + Ok(result) + } + } + ); +} + +for_each_tuple!(runnable_for_tuple); + +#[cfg(test)] +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 std::cell::RefCell; + use std::error::Error; + use std::fmt::Debug; + use std::rc::Rc; + + #[derive(Debug)] + struct DummySymbol { + _target_reached: RefCell, + _execute: RefCell, + } + + #[async_trait(?Send)] + impl< + E: Iterator>>, + T: Iterator>>, + > Symbol for DummySymbol + { + async fn target_reached(&self) -> Result> { + self._target_reached.borrow_mut().next().unwrap() + } + async fn execute(&self) -> Result<(), Box> { + self._execute.borrow_mut().next().unwrap() + } + } + + impl< + E: Iterator>>, + T: Iterator>>, + > DummySymbol + { + fn new< + IE: IntoIterator>>, + IT: IntoIterator>>, + >( + target_reached: IT, + execute: IE, + ) -> Self { + Self { + _target_reached: RefCell::new(target_reached.into_iter()), + _execute: RefCell::new(execute.into_iter()), + } + } + } + + struct TestSymbolRunner { + count: Rc>, + } + + fn get_runner() -> (Rc>, TestSymbolRunner) { + let count = Rc::new(RefCell::new(0)); + let runner = TestSymbolRunner { + count: Rc::clone(&count), + }; + (count, runner) + } + + #[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) + } + } + + fn run_symbol( + runnable: impl Runnable, + force: bool, + ) -> (Rc>, Result>) { + let (count, runner) = get_runner(); + let res = run(runnable.run(&runner, &StoringLogger::new(), force)); + (count, res) + } + + #[test] + fn correctly_handles_symbol_tuples() { + let (count, res) = run_symbol( + ( + DummySymbol::new(vec![Ok(false)], vec![Ok(())]), + DummySymbol::new(vec![Ok(false)], vec![Ok(())]), + ), + false, + ); + res.unwrap(); + assert_eq!(*count.borrow(), 2); + + let (count, res) = run_symbol( + ( + DummySymbol::new(vec![Ok(true)], vec![Ok(())]), + DummySymbol::new(vec![Ok(false)], vec![Ok(())]), + ), + false, + ); + res.unwrap(); + assert_eq!(*count.borrow(), 1); + + // An unreached symbol forces all further symbols + let (count, res) = run_symbol( + ( + DummySymbol::new(vec![Ok(false)], vec![Ok(())]), + DummySymbol::new(vec![Ok(true)], vec![Ok(())]), + ), + false, + ); + res.unwrap(); + assert_eq!(*count.borrow(), 2); + + let (count, res) = run_symbol( + ( + DummySymbol::new(vec![Ok(true)], vec![Ok(())]), + DummySymbol::new(vec![Ok(true)], vec![Ok(())]), + ), + false, + ); + res.unwrap(); + assert_eq!(*count.borrow(), 0); + + let (count, res) = run_symbol( + ( + DummySymbol::new(vec![Ok(true)], vec![Ok(())]), + DummySymbol::new(vec![Ok(true)], vec![Ok(())]), + ), + true, + ); + res.unwrap(); + assert_eq!(*count.borrow(), 2); + } +} diff --git a/src/setup/setup.rs b/src/setup/setup.rs new file mode 100644 index 0000000..24ebf1f --- /dev/null +++ b/src/setup/setup.rs @@ -0,0 +1,300 @@ +use super::core::{RegularSetupCore, SetupCore}; +use super::runnable::Runnable; +use super::util::{AddResult, AddableResource}; +use super::SymbolRunner; +use crate::async_utils::run; +use crate::loggers::Logger; +use crate::resources::{DefaultArtifacts, DefaultResources, FromArtifact, FromResource}; +use crate::{DefaultBuilder, DefaultLocator}; +use futures::future::FutureExt; +use futures::future::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; + +type Cache = HashMap>>>>; + +#[derive(Debug)] +struct SetupInner { + core: CORE, + logger: LOG, + resources: RefCell>, +} + +#[derive(Debug)] +pub struct Setup< + SR, + LOG, + L = DefaultLocator, + B = DefaultBuilder, + Rs = DefaultResources<'static, &'static str>, + As = DefaultArtifacts<'static, &'static str>, +>(Rc, LOG, Rs, As>>); + +// https://github.com/rust-lang/rust/issues/27336 +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 { + Self(Rc::new(SetupInner { + core: RegularSetupCore::new(symbol_runner), + logger, + resources: RefCell::default(), + })) + } +} + +impl< + L: 'static, + B: 'static, + SR: 'static, + LOG: 'static + Logger, + Rs: Hash + Eq + 'static, + As: 'static, + > Setup +{ + fn borrow_resources(&self) -> RefMut<'_, Cache> { + self.0.resources.borrow_mut() + } + + pub(super) async fn add_async( + &self, + resource: R, + force_run: bool, + ) -> AddResult + 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(); + 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); + Ok(future.await) + } 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 + this + .0 + .core + .add(&this, &this.0.logger, resource, force_run) + .await + .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 { future_clone.await.unwrap() }) + as Pin>>) + .shared(), + ); + drop(resources); + future.await.map_err(|e| e.into()) + } + .map(|(t, did_run)| (t.into_artifact(), did_run)) + } + + // + // Legacy + // + pub fn add(&self, resource: R) -> AddResult + where + RegularSetupCore: SetupCore, + Rs: FromResource, + As: FromArtifact + Clone, + R::Artifact: Clone, + { + run(self.add_async(resource, false)) + } + + pub fn add_force(&self, resource: R, force_run: bool) -> AddResult + where + RegularSetupCore: SetupCore, + Rs: FromResource, + As: FromArtifact + Clone, + R::Artifact: Clone, + { + run(self.add_async(resource, force_run)) + } + + pub fn run_symbol(&self, symbol: S, force: bool) -> Result> + where + RegularSetupCore: SymbolRunner, + { + run(symbol.run(&self.0.core, &self.0.logger, force)) + } +} + +#[cfg(test)] +mod test { + use super::SymbolRunner; + 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 + } + fn into_artifact(self) -> () { + () + } + } + + 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() { + let (count, setup) = get_setup(); + setup.add(TestResource("A", "b")).unwrap(); + assert_eq!(*count.borrow(), 2); + setup.add(TestResource("A", "b")).unwrap(); + assert_eq!(*count.borrow(), 2); + + let (count, setup) = get_setup(); + setup.add(TestResource("A", "B")).unwrap(); + assert_eq!(*count.borrow(), 0); + } +} diff --git a/src/setup/symbol_runner.rs b/src/setup/symbol_runner.rs new file mode 100644 index 0000000..8e6ce83 --- /dev/null +++ b/src/setup/symbol_runner.rs @@ -0,0 +1,318 @@ +use crate::loggers::Logger; +use crate::symbols::Symbol; +use async_trait::async_trait; +use std::error::Error; +use std::fmt; +use std::fmt::Debug; + +#[async_trait(?Send)] +pub trait SymbolRunner { + async fn run_symbol( + &self, + symbol: &S, + logger: &L, + force: bool, + ) -> Result>; +} + +#[derive(Debug)] +pub enum SymbolRunError { + Symbol(Box), + ExecuteDidNotReach(()), +} + +impl Error for SymbolRunError { + fn cause(&self) -> Option<&dyn Error> { + match self { + Self::Symbol(ref e) => Some(&**e), + Self::ExecuteDidNotReach(_) => None, + } + } +} + +impl fmt::Display for SymbolRunError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Symbol(ref e) => write!(f, "{}", e), + Self::ExecuteDidNotReach(_) => write!(f, "Target not reached after executing symbol"), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct InitializingSymbolRunner; + +impl InitializingSymbolRunner { + pub fn new() -> Self { + Self + } + + async fn exec_symbol( + &self, + symbol: &S, + logger: &L, + ) -> Result<(), Box> { + logger.info(format!("Executing {:?}", symbol)); + symbol.execute().await?; + let target_reached = symbol.target_reached().await?; + logger.trace(format!( + "Symbol reports target_reached: {:?} (should be true)", + target_reached + )); + if target_reached { + Ok(()) + } else { + Err(Box::new(SymbolRunError::ExecuteDidNotReach(()))) + } + } +} + +#[async_trait(?Send)] +impl SymbolRunner for InitializingSymbolRunner { + async fn run_symbol( + &self, + symbol: &S, + logger: &L, + force: bool, + ) -> Result> { + let executed = if force { + logger.debug("Forcing symbol execution"); + self.exec_symbol(symbol, logger).await?; + true + } else { + let target_reached = symbol.target_reached().await?; + if target_reached { + logger.debug(format!("{:?} already reached", symbol)); + } else { + logger.trace(format!( + "Symbol reports target_reached: {:?}", + target_reached + )); + self.exec_symbol(symbol, logger).await?; + } + !target_reached + }; + Ok(executed) + } +} + +#[derive(Clone, Debug, Default)] +pub struct DrySymbolRunner; + +impl DrySymbolRunner { + pub fn new() -> Self { + Self + } +} + +#[async_trait(?Send)] +impl SymbolRunner for DrySymbolRunner { + async fn run_symbol( + &self, + symbol: &S, + logger: &L, + force: bool, + ) -> Result> { + let would_execute = if force { + logger.info(format!("Would force-execute {:?}", symbol)); + true + } else { + let target_reached = symbol.target_reached().await?; + logger.debug(format!( + "Symbol reports target_reached: {:?}", + target_reached + )); + if !target_reached { + logger.info(format!("Would execute {:?}", symbol)); + } + !target_reached + }; + Ok(would_execute) + } +} + +#[derive(Clone, Debug)] +pub struct ReportingSymbolRunner(R); + +impl ReportingSymbolRunner { + pub fn new(symbol_runner: R) -> Self { + Self(symbol_runner) + } +} + +#[async_trait(?Send)] +impl SymbolRunner for ReportingSymbolRunner +where + R: SymbolRunner, +{ + async fn run_symbol( + &self, + symbol: &S, + logger: &L, + force: bool, + ) -> Result> { + logger.debug(format!("Running symbol {:?}", symbol)); + let res = self.0.run_symbol(symbol, logger, force).await; + if let Err(ref e) = res { + logger.info(format!("Failed on {:?} with {}, aborting.", symbol, e)) + } else { + logger.debug(format!("Successfully finished {:?}", symbol)) + } + res + } +} + +#[cfg(test)] +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 std::cell::RefCell; + use std::error::Error; + use std::fmt; + use std::fmt::Debug; + use std::time::Duration; + + #[derive(Debug, PartialEq, Clone)] + enum DummySymbolError { + Error(()), + } + + impl Error for DummySymbolError {} + + impl fmt::Display for DummySymbolError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Dummy symbol error") + } + } + + #[derive(Debug)] + struct DummySymbol { + _target_reached: RefCell, + _execute: RefCell, + } + + #[async_trait(?Send)] + impl< + E: Iterator>>, + T: Iterator>>, + > Symbol for DummySymbol + { + async fn target_reached(&self) -> Result> { + self._target_reached.borrow_mut().next().unwrap() + } + async fn execute(&self) -> Result<(), Box> { + self._execute.borrow_mut().next().unwrap() + } + } + + impl< + E: Iterator>>, + T: Iterator>>, + > DummySymbol + { + fn new< + IE: IntoIterator>>, + IT: IntoIterator>>, + >( + target_reached: IT, + execute: IE, + ) -> Self { + Self { + _target_reached: RefCell::new(target_reached.into_iter()), + _execute: RefCell::new(execute.into_iter()), + } + } + } + + fn run_symbol(s: S) -> Result> { + run(InitializingSymbolRunner::new().run_symbol(&s, &StoringLogger::new(), false)) + } + + #[test] + fn nothing_needed_to_be_done() { + let result = run_symbol(DummySymbol::new(vec![Ok(true)], vec![Ok(())])); + assert!(result.is_ok()); + } + + #[test] + fn everything_is_ok() { + let result = run_symbol(DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())])); + assert!(result.is_ok()); + } + + #[test] + fn executing_did_not_change_state() { + let result = run_symbol(DummySymbol::new(vec![Ok(false), Ok(false)], vec![Ok(())])); + assert_eq!( + result.unwrap_err().to_string(), + "Target not reached after executing symbol" + ); + } + + #[test] + fn executing_did_not_work() { + let result = run_symbol(DummySymbol::new( + vec![Ok(false)], + vec![Err(Box::new(DummySymbolError::Error(())) as Box)], + )); + assert_eq!(result.unwrap_err().to_string(), "Dummy symbol error"); + } + + #[derive(Debug)] + struct SleeperSymbol; + + #[async_trait(?Send)] + impl Symbol for SleeperSymbol { + async fn target_reached(&self) -> Result> { + sleep(Duration::from_millis(0)).await; + Ok(true) + } + async fn execute(&self) -> Result<(), Box> { + unimplemented!(); + } + } + + #[test] + fn actually_support_parallel_execution() { + run(async { + let s1 = SleeperSymbol; + let s2 = DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]); + + let l1 = StoringLogger::new(); + let l2 = StoringLogger::new(); + let runner1 = InitializingSymbolRunner::new(); + let result = try_join!( + runner1.run_symbol(&s1, &l1, false), + runner1.run_symbol(&s2, &l2, false), + ) + .unwrap(); + 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), + runner2.run_symbol(&s2, &l2, false), + ) + .unwrap(); + 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), + runner3.run_symbol(&s2, &l2, false), + ) + .unwrap(); + assert_eq!(result, (false, true)); + }); + } +} diff --git a/src/setup/util.rs b/src/setup/util.rs new file mode 100644 index 0000000..ad9dbff --- /dev/null +++ b/src/setup/util.rs @@ -0,0 +1,9 @@ +use crate::resources::Resource; +use crate::to_artifact::ToArtifact; +use std::error::Error; +use std::fmt::Debug; + +pub trait AddableResource: 'static + Resource + Debug {} +impl AddableResource for R where R: 'static + Resource + Debug {} + +pub type AddResult = Result<(::Artifact, bool), Box>; diff --git a/src/symbols/acme/cert.rs b/src/symbols/acme/cert.rs index 9145301..e54f70a 100644 --- a/src/symbols/acme/cert.rs +++ b/src/symbols/acme/cert.rs @@ -1,5 +1,6 @@ use crate::command_runner::CommandRunner; use crate::symbols::Symbol; +use async_trait::async_trait; use std::borrow::Borrow; use std::error::Error; use std::fs::File as FsFile; @@ -44,24 +45,29 @@ impl<_C, C, D, P> Cert<_C, C, D, P> { const DAYS_IN_SECONDS: u32 = 24 * 60 * 60; +#[async_trait(?Send)] impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef, P: AsRef> Symbol for Cert<_C, C, D, P> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { if !self.cert_path.as_ref().exists() { return Ok(false); } - let output = self.command_runner.borrow().run_with_args( - "openssl", - args![ - "x509", - "-in", - self.cert_path.as_ref(), - "-noout", - "-subject", - "-checkend", - (30 * DAYS_IN_SECONDS).to_string(), - ], - )?; + let output = self + .command_runner + .borrow() + .run_with_args( + "openssl", + args![ + "x509", + "-in", + self.cert_path.as_ref(), + "-noout", + "-subject", + "-checkend", + (30 * DAYS_IN_SECONDS).to_string(), + ], + ) + .await?; if output.status.success() && output.stdout == format!( @@ -83,6 +89,7 @@ impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef, P: AsRef> Symbol for self.cert_path.as_ref(), ], ) + .await .is_ok(), ) } else if output.status.code() == Some(1) @@ -99,18 +106,22 @@ impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef, P: AsRef> Symbol for } } - fn execute(&self) -> Result<(), Box> { - let output = self.command_runner.borrow().get_output( - "acme-tiny", - args![ - "--account-key", - self.account_key_path.as_ref(), - "--csr", - self.csr_path.as_ref(), - "--acme-dir", - self.challenges_path.as_ref(), - ], - )?; + async fn execute(&self) -> Result<(), Box> { + let output = self + .command_runner + .borrow() + .get_output( + "acme-tiny", + args![ + "--account-key", + self.account_key_path.as_ref(), + "--csr", + self.csr_path.as_ref(), + "--acme-dir", + self.challenges_path.as_ref(), + ], + ) + .await?; let mut file = FsFile::create(self.cert_path.as_ref())?; file.write_all(&output)?; Ok(()) diff --git a/src/symbols/concat.rs b/src/symbols/concat.rs index cdde300..b226c99 100644 --- a/src/symbols/concat.rs +++ b/src/symbols/concat.rs @@ -1,4 +1,5 @@ use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; use std::fs::{metadata, File}; use std::io::copy; @@ -22,8 +23,9 @@ impl Concat { } } +#[async_trait(?Send)] impl, D: AsRef, I: AsRef> Symbol for Concat { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { let target = self.target.as_ref(); if !target.exists() { return Ok(false); @@ -37,7 +39,7 @@ impl, D: AsRef, I: AsRef> Symbol for Concat { Ok(true) } - fn execute(&self) -> Result<(), Box> { + async fn execute(&self) -> Result<(), Box> { let mut file = File::create(self.target.as_ref())?; for source in self.sources.as_ref() { copy(&mut File::open(source)?, &mut file)?; diff --git a/src/symbols/cron.rs b/src/symbols/cron.rs index e30978d..2a452ce 100644 --- a/src/symbols/cron.rs +++ b/src/symbols/cron.rs @@ -1,5 +1,6 @@ -use crate::command_runner::CommandRunner; +use crate::command_runner::{is_success, CommandRunner}; use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; #[derive(Debug)] @@ -19,23 +20,27 @@ impl<'r, U, R> Cron<'r, String, U, R> { } } +#[async_trait(?Send)] impl, U: AsRef, R: CommandRunner> Symbol for Cron<'_, C, U, R> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { let tab = self .command_runner - .get_output("crontab", args!["-l", "-u", self.user.as_ref()])?; + .get_output("crontab", args!["-l", "-u", self.user.as_ref()]) + .await?; Ok(tab == self.content.as_ref().as_bytes()) } - fn execute(&self) -> Result<(), Box> { - let output = self.command_runner.run_with_args_and_stdin( - "crontab", - args!["-u", self.user.as_ref(), "-",], - self.content.as_ref(), + async fn execute(&self) -> Result<(), Box> { + is_success( + self + .command_runner + .run( + "crontab", + args!["-u", self.user.as_ref(), "-",], + self.content.as_ref(), + ) + .await, )?; - if !output.status.success() { - return Err(String::from_utf8(output.stderr)?.into()); - } Ok(()) } } diff --git a/src/symbols/dir.rs b/src/symbols/dir.rs index c97c12a..f9fdacc 100644 --- a/src/symbols/dir.rs +++ b/src/symbols/dir.rs @@ -1,4 +1,5 @@ use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; use std::fs; use std::io; @@ -15,8 +16,9 @@ impl

Dir

{ } } +#[async_trait(?Send)] impl> Symbol for Dir

{ - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { if !self.path.as_ref().exists() { return Ok(false); } @@ -30,7 +32,7 @@ impl> Symbol for Dir

{ Ok(true) } - fn execute(&self) -> Result<(), Box> { + async fn execute(&self) -> Result<(), Box> { fs::create_dir(self.path.as_ref()).map_err(|e| Box::new(e) as Box) } } diff --git a/src/symbols/file.rs b/src/symbols/file.rs index 25fe3fa..edff275 100644 --- a/src/symbols/file.rs +++ b/src/symbols/file.rs @@ -1,4 +1,5 @@ use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; use std::fs::File as FsFile; use std::io::{Read, Write}; @@ -16,8 +17,9 @@ impl File { } } +#[async_trait(?Send)] impl, C: AsRef> Symbol for File { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { if !self.path.as_ref().exists() { return Ok(false); } @@ -35,7 +37,7 @@ impl, C: AsRef> Symbol for File { } } - fn execute(&self) -> Result<(), Box> { + async fn execute(&self) -> Result<(), Box> { let mut file = FsFile::create(self.path.as_ref())?; file.write_all(self.content.as_ref().as_bytes())?; Ok(()) diff --git a/src/symbols/git/checkout.rs b/src/symbols/git/checkout.rs index 09a5a5a..e2ad4a5 100644 --- a/src/symbols/git/checkout.rs +++ b/src/symbols/git/checkout.rs @@ -1,5 +1,7 @@ +use crate::async_utils::try_join; use crate::command_runner::CommandRunner; use crate::symbols::Symbol; +use async_trait::async_trait; use std::borrow::Borrow; use std::error::Error; use std::ffi::OsStr; @@ -28,47 +30,121 @@ impl Checkout<_C, C, P, S, B> { } impl, P: AsRef, S, B> Checkout { - fn _run_in_target_repo(&self, args: &[impl AsRef]) -> Result, Box> { - let mut new_args = vec![OsStr::new("-C"), self.target.as_ref().as_ref()]; + 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()]); new_args.extend(args.iter().map(AsRef::as_ref)); - self.command_runner.borrow().get_output("git", &new_args) + self + .command_runner + .borrow() + .get_output("git", &new_args) + .await } } +#[async_trait(?Send)] impl, P: AsRef, S: AsRef, B: AsRef> Symbol for Checkout { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { if !self.target.as_ref().exists() { return Ok(false); } - self._run_in_target_repo(&["fetch", self.source.as_ref(), self.branch.as_ref()])?; - // git rev-list resolves tag objects - let fetch_head = self._run_in_target_repo(&["rev-list", "-1", "FETCH_HEAD"])?; - let head = self._run_in_target_repo(&["rev-list", "-1", "HEAD"])?; + let fetch_head_f = async { + self + .run_git(args!["fetch", self.source, self.branch]) + .await?; + // git rev-list resolves tag objects + self.run_git(&["rev-list", "-1", "FETCH_HEAD"]).await + }; + let head_f = self.run_git(&["rev-list", "-1", "HEAD"]); + let (fetch_head, head) = try_join!(fetch_head_f, head_f)?; Ok(fetch_head == head) } - fn execute(&self) -> Result<(), Box> { + async fn execute(&self) -> Result<(), Box> { if !self.target.as_ref().exists() { - return self.command_runner.borrow().run_successfully( - "git", - args![ - "clone", - "--depth", - "1", - "-b", - self.branch.as_ref(), - self.source.as_ref(), - self.target.as_ref(), - ], - ); + self + .command_runner + .borrow() + .run_successfully( + "git", + args![ + "clone", + "--depth", + "1", + "-b", + self.branch.as_ref(), + self.source.as_ref(), + self.target.as_ref(), + ], + ) + .await?; + } else { + self + .run_git(&["fetch", self.source.as_ref(), self.branch.as_ref()]) + .await?; + self.run_git(&["merge", "FETCH_HEAD"]).await?; } - self._run_in_target_repo(&["fetch", self.source.as_ref(), self.branch.as_ref()])?; - self._run_in_target_repo(&["merge", "FETCH_HEAD"])?; Ok(()) } } #[cfg(test)] -mod test {} +mod test { + use super::Checkout; + use crate::async_utils::run; + use crate::async_utils::sleep; + use crate::command_runner::CommandRunner; + use crate::symbols::Symbol; + use async_trait::async_trait; + use std::cell::RefCell; + use std::ffi::{OsStr, OsString}; + use std::io::Result as IoResult; + use std::os::unix::process::ExitStatusExt; + use std::process::{ExitStatus, Output}; + use std::time::{Duration, Instant}; + + struct DummyCommandRunner { + pub args: RefCell>>, + } + #[async_trait(?Send)] + impl CommandRunner for DummyCommandRunner { + async fn run(&self, program: &str, args: &[&OsStr], stdin: &str) -> IoResult { + assert_eq!(program, "git"); + assert_eq!(stdin, ""); + sleep(Duration::from_millis(50)).await; + self + .args + .borrow_mut() + .push(args.iter().map(|a| a.to_os_string()).collect()); + Ok(Output { + status: ExitStatus::from_raw(0), + stdout: vec![], + stderr: vec![], + }) + } + } + + #[test] + fn test() { + let c = DummyCommandRunner { + args: RefCell::new(vec![]), + }; + let checkout: Checkout = + Checkout::new("target", "source", "branch", &c); + let start = Instant::now(); + assert!(run(checkout.target_reached()).unwrap()); + let end = Instant::now(); + assert_eq!( + c.args.into_inner(), + [ + ["-C", "target", "fetch", "source", "branch"], + ["-C", "target", "rev-list", "-1", "HEAD"], + ["-C", "target", "rev-list", "-1", "FETCH_HEAD"] + ] + ); + assert!((end - start).as_millis() >= 100); + assert!((end - start).as_millis() < 150); + } +} diff --git a/src/symbols/git/mod.rs b/src/symbols/git/mod.rs index 580afe8..10a23bd 100644 --- a/src/symbols/git/mod.rs +++ b/src/symbols/git/mod.rs @@ -1,4 +1,4 @@ mod checkout; -//pub mod submodules; +pub mod submodules; pub use checkout::Checkout; diff --git a/src/symbols/git/submodules.rs b/src/symbols/git/submodules.rs index 83255d1..e35eabf 100644 --- a/src/symbols/git/submodules.rs +++ b/src/symbols/git/submodules.rs @@ -1,19 +1,20 @@ +use crate::command_runner::CommandRunner; +use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; use std::ffi::OsStr; use std::fmt; use std::path::Path; -use crate::command_runner::CommandRunner; -use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; - -pub struct GitSubmodules<'a, P: AsRef, C: CommandRunner> { +#[derive(Debug)] +pub struct GitSubmodules<'a, P, C> { target: P, command_runner: &'a C, } -impl<'a, P: AsRef, C: CommandRunner> GitSubmodules<'a, P, C> { +impl<'a, P, C> GitSubmodules<'a, P, C> { pub fn new(target: P, command_runner: &'a C) -> Self { - GitSubmodules { + Self { target, command_runner, } @@ -27,20 +28,25 @@ impl, C: CommandRunner> fmt::Display for GitSubmodules<'_, P, C> } impl, C: CommandRunner> GitSubmodules<'_, P, C> { - fn _run_in_target_repo(&self, args: &[&OsStr]) -> Result, Box> { + async fn _run_in_target_repo(&self, args: &[&OsStr]) -> Result, Box> { let mut new_args: Vec<&OsStr> = vec![]; new_args.extend_from_slice(args!["-C", self.target.as_ref()]); new_args.extend_from_slice(args); - self.command_runner.get_output("git", &new_args) + self.command_runner.get_output("git", &new_args).await } } +#[async_trait(?Send)] impl, C: CommandRunner> Symbol for GitSubmodules<'_, P, C> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { if !self.target.as_ref().exists() { return Ok(false); } - let output = String::from_utf8(self._run_in_target_repo(args!["submodule", "status"])?)?; + let output = String::from_utf8( + self + ._run_in_target_repo(args!["submodule", "status"]) + .await?, + )?; Ok( output .lines() @@ -48,21 +54,12 @@ impl, C: CommandRunner> Symbol for GitSubmodules<'_, P, C> { ) } - fn execute(&self) -> Result<(), Box> { - self._run_in_target_repo(args!["submodule", "update", "--init"])?; + async fn execute(&self) -> Result<(), Box> { + self + ._run_in_target_repo(args!["submodule", "update", "--init"]) + .await?; Ok(()) } - - fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { - Box::new(SymbolAction::new(runner, self)) - } - - fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box - where - Self: 'b, - { - Box::new(OwnedSymbolAction::new(runner, *self)) - } } #[cfg(test)] diff --git a/src/symbols/mariadb/database.rs b/src/symbols/mariadb/database.rs index 4a35ae2..caace4e 100644 --- a/src/symbols/mariadb/database.rs +++ b/src/symbols/mariadb/database.rs @@ -1,6 +1,7 @@ use crate::command_runner::CommandRunner; use crate::storage::Storage; use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; #[derive(Debug)] @@ -19,37 +20,45 @@ impl<'a, D, S, C: CommandRunner> Database<'a, D, S, C> { } } - fn run_sql(&self, sql: &str) -> Result> { + async fn run_sql(&self, sql: &str) -> Result> { let b = self .command_runner - .get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])?; + .get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql]) + .await?; Ok(String::from_utf8(b)?) } } +#[async_trait(?Send)] impl, S: Storage, C: CommandRunner> Symbol for Database<'_, D, S, C> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { Ok( self - .run_sql(&format!("SHOW DATABASES LIKE '{}'", self.db_name.as_ref()))? + .run_sql(&format!("SHOW DATABASES LIKE '{}'", self.db_name.as_ref())) + .await? .trim_end() == self.db_name.as_ref(), ) } - fn execute(&self) -> Result<(), Box> { - self.run_sql(&format!("CREATE DATABASE {}", self.db_name.as_ref()))?; - self.command_runner.run_successfully( - "sh", - args![ - "-c", - format!( - "mariadb '{}' < {}", - self.db_name.as_ref(), - self.seed_file.read_filename()?.to_str().unwrap() - ), - ], - ) + async fn execute(&self) -> Result<(), Box> { + self + .run_sql(&format!("CREATE DATABASE {}", self.db_name.as_ref())) + .await?; + self + .command_runner + .run_successfully( + "sh", + args![ + "-c", + format!( + "mariadb '{}' < {}", + self.db_name.as_ref(), + self.seed_file.read_filename()?.to_str().unwrap() + ), + ], + ) + .await } } diff --git a/src/symbols/mariadb/dump.rs b/src/symbols/mariadb/dump.rs index e01d9d8..8276532 100644 --- a/src/symbols/mariadb/dump.rs +++ b/src/symbols/mariadb/dump.rs @@ -1,9 +1,9 @@ -use std::error::Error; -use std::str::FromStr; - use crate::command_runner::CommandRunner; use crate::storage::Storage; use crate::symbols::Symbol; +use async_trait::async_trait; +use std::error::Error; +use std::str::FromStr; #[derive(Debug)] pub struct Dump<'a, N, C, S> { @@ -21,34 +21,39 @@ impl<'a, N, C: CommandRunner, S> Dump<'a, N, C, S> { } } - fn run_sql(&self, sql: &str) -> Result> { + async fn run_sql(&self, sql: &str) -> Result> { let b = self .command_runner - .get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])?; + .get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql]) + .await?; Ok(String::from_utf8(b)?) } } +#[async_trait(?Send)] impl, C: CommandRunner, S: Storage> Symbol for Dump<'_, N, C, S> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { let dump_date = self.storage.recent_date()?; - let _modified_date = self.run_sql(&format!("select UNIX_TIMESTAMP(MAX(UPDATE_TIME)) from information_schema.tables WHERE table_schema = '{}'", self.db_name.as_ref()))?; + let _modified_date = self.run_sql(&format!("select UNIX_TIMESTAMP(MAX(UPDATE_TIME)) from information_schema.tables WHERE table_schema = '{}'", self.db_name.as_ref())).await?; let modified_date = _modified_date.trim_end(); Ok(modified_date != "NULL" && u64::from_str(modified_date)? <= dump_date) } - fn execute(&self) -> Result<(), Box> { - self.command_runner.run_successfully( - "sh", - args![ - "-c", - format!( - "mysqldump '{}' > {}", - self.db_name.as_ref(), - self.storage.write_filename().to_str().unwrap() - ), - ], - ) + async fn execute(&self) -> Result<(), Box> { + self + .command_runner + .run_successfully( + "sh", + args![ + "-c", + format!( + "mysqldump '{}' > {}", + self.db_name.as_ref(), + self.storage.write_filename().to_str().unwrap() + ), + ], + ) + .await } } diff --git a/src/symbols/mariadb/user.rs b/src/symbols/mariadb/user.rs index 337f229..b8eb4b5 100644 --- a/src/symbols/mariadb/user.rs +++ b/src/symbols/mariadb/user.rs @@ -1,5 +1,6 @@ use crate::command_runner::CommandRunner; use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; #[derive(Debug)] @@ -16,32 +17,37 @@ impl<'a, U: AsRef, C: CommandRunner> User<'a, U, C> { } } - fn run_sql(&self, sql: &str) -> Result> { + async fn run_sql(&self, sql: &str) -> Result> { let b = self .command_runner - .get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])?; + .get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql]) + .await?; Ok(String::from_utf8(b)?) } } +#[async_trait(?Send)] impl, C: CommandRunner> Symbol for User<'_, U, C> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { Ok( self .run_sql(&format!( "SELECT User FROM mysql.user WHERE User = '{}' AND plugin = 'unix_socket'", self.user_name.as_ref() - ))? + )) + .await? .trim_end() == self.user_name.as_ref(), ) } - fn execute(&self) -> Result<(), Box> { - self.run_sql(&format!( - "GRANT ALL ON {0}.* TO {0} IDENTIFIED VIA unix_socket", - self.user_name.as_ref() - ))?; + async fn execute(&self) -> Result<(), Box> { + self + .run_sql(&format!( + "GRANT ALL ON {0}.* TO {0} IDENTIFIED VIA unix_socket", + self.user_name.as_ref() + )) + .await?; Ok(()) } } diff --git a/src/symbols/mod.rs b/src/symbols/mod.rs index a9932a8..875131e 100644 --- a/src/symbols/mod.rs +++ b/src/symbols/mod.rs @@ -1,9 +1,11 @@ +use async_trait::async_trait; use std::error::Error; // Symbol +#[async_trait(?Send)] pub trait Symbol { - fn target_reached(&self) -> Result>; - fn execute(&self) -> Result<(), Box>; + async fn target_reached(&self) -> Result>; + async fn execute(&self) -> Result<(), Box>; } pub mod acme; diff --git a/src/symbols/npm.rs b/src/symbols/npm.rs index afc42ba..5536c96 100644 --- a/src/symbols/npm.rs +++ b/src/symbols/npm.rs @@ -1,5 +1,6 @@ use crate::command_runner::CommandRunner; use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; use std::fmt; use std::path::Path; @@ -29,37 +30,39 @@ impl, C: CommandRunner> fmt::Display for Install<'_, T, C> { } } +#[async_trait(?Send)] impl, C: CommandRunner> Symbol for Install<'_, T, C> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { if !self.target.as_ref().exists() { return Ok(false); } - let result = self.command_runner.run_with_args( - "sh", - args![ - "-c", - format!("cd '{}' && npm ls", self.target.as_ref().to_str().unwrap()), - ], - )?; - Ok( - result.status.success() - && !String::from_utf8(result.stdout) - .unwrap() - .contains("(empty)"), - ) + let output = self + .command_runner + .get_output( + "sh", + args![ + "-c", + format!("cd '{}' && npm ls", self.target.as_ref().to_str().unwrap()), + ], + ) + .await?; + Ok(!String::from_utf8(output).unwrap().contains("(empty)")) } - fn execute(&self) -> Result<(), Box> { - self.command_runner.run_successfully( - "sh", - args![ - "-c", - format!( - "cd '{}' && npm install --production --unsafe-perm", - self.target.as_ref().to_str().unwrap() - ), - ], - ) + async fn execute(&self) -> Result<(), Box> { + self + .command_runner + .run_successfully( + "sh", + args![ + "-c", + format!( + "cd '{}' && npm install --production --unsafe-perm", + self.target.as_ref().to_str().unwrap() + ), + ], + ) + .await } } diff --git a/src/symbols/owner.rs b/src/symbols/owner.rs index a58c4fc..c7d28a3 100644 --- a/src/symbols/owner.rs +++ b/src/symbols/owner.rs @@ -1,5 +1,6 @@ use crate::command_runner::CommandRunner; use crate::symbols::Symbol; +use async_trait::async_trait; use std::borrow::Borrow; use std::error::Error; use std::fs; @@ -27,10 +28,11 @@ impl<_C, C, P, U> Owner<_C, C, P, U> { } } +#[async_trait(?Send)] impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, U: AsRef> Symbol for Owner<_C, C, P, U> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { if !self.path.as_ref().exists() { return Ok(false); } @@ -39,10 +41,14 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, U: AsRef> Symbol Ok(actual_uid == target_uid) } - fn execute(&self) -> Result<(), Box> { - self.command_runner.borrow().run_successfully( - "chown", - args!["-R", self.user_name.as_ref(), self.path.as_ref()], - ) + async fn execute(&self) -> Result<(), Box> { + self + .command_runner + .borrow() + .run_successfully( + "chown", + args!["-R", self.user_name.as_ref(), self.path.as_ref()], + ) + .await } } diff --git a/src/symbols/postgresql/database.rs b/src/symbols/postgresql/database.rs index 7bc6c88..bf36fea 100644 --- a/src/symbols/postgresql/database.rs +++ b/src/symbols/postgresql/database.rs @@ -1,8 +1,10 @@ use crate::command_runner::CommandRunner; use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; use std::fmt; +#[derive(Debug)] pub struct PostgreSQLDatabase<'a, N: AsRef, S: AsRef, C: CommandRunner> { name: N, seed_file: S, @@ -18,11 +20,14 @@ impl<'a, N: AsRef, S: AsRef, C: CommandRunner> PostgreSQLDatabase<'a, } } - fn run_sql(&self, sql: &str) -> Result> { - let b = self.command_runner.get_output( - "su", - args!["-", "postgres", "-c", format!("psql -t -c \"{}\"", sql)], - )?; + async fn run_sql(&self, sql: &str) -> Result> { + let b = self + .command_runner + .get_output( + "su", + args!["-", "postgres", "-c", format!("psql -t -c \"{}\"", sql)], + ) + .await?; Ok(String::from_utf8(b)?) } } @@ -35,54 +40,65 @@ impl, S: AsRef, C: CommandRunner> fmt::Display } } +#[async_trait(?Send)] impl, S: AsRef, C: CommandRunner> Symbol for PostgreSQLDatabase<'_, N, S, C> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { Ok( self .run_sql(&format!( "SELECT datname FROM pg_database WHERE datname LIKE '{}'", self.name.as_ref() - ))? + )) + .await? .trim() == self.name.as_ref(), ) } - fn execute(&self) -> Result<(), Box> { - self.command_runner.run_successfully( - "su", - args![ - "-", - "postgres", - "-c", - format!("createuser {}", self.name.as_ref()) - ], - )?; - self.command_runner.run_successfully( - "su", - args![ - "-", - "postgres", - "-c", - format!( - "createdb -E UTF8 -T template0 -O {} {0}", - self.name.as_ref() - ), - ], - )?; - self.command_runner.run_successfully( - "su", - args![ - "-", - "postgres", - "-c", - format!( - "psql '{}' < {}", - self.name.as_ref(), - self.seed_file.as_ref() - ), - ], - ) + async fn execute(&self) -> Result<(), Box> { + self + .command_runner + .run_successfully( + "su", + args![ + "-", + "postgres", + "-c", + format!("createuser {}", self.name.as_ref()) + ], + ) + .await?; + self + .command_runner + .run_successfully( + "su", + args![ + "-", + "postgres", + "-c", + format!( + "createdb -E UTF8 -T template0 -O {} {0}", + self.name.as_ref() + ), + ], + ) + .await?; + self + .command_runner + .run_successfully( + "su", + args![ + "-", + "postgres", + "-c", + format!( + "psql '{}' < {}", + self.name.as_ref(), + self.seed_file.as_ref() + ), + ], + ) + .await } } diff --git a/src/symbols/saved_directory.rs b/src/symbols/saved_directory.rs index 81772bc..d52091a 100644 --- a/src/symbols/saved_directory.rs +++ b/src/symbols/saved_directory.rs @@ -1,6 +1,7 @@ use crate::command_runner::CommandRunner; use crate::storage::Storage; use crate::symbols::Symbol; +use async_trait::async_trait; use std::borrow::Borrow; use std::error::Error; use std::fs; @@ -36,10 +37,11 @@ impl<_C, C, P, S> SavedDirectory<_C, C, P, S> { } } +#[async_trait(?Send)] impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S: Storage> Symbol for SavedDirectory<_C, C, P, S> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { let metadata = fs::metadata(self.path.as_ref()); // Check if dir exists if let Err(e) = metadata { @@ -57,26 +59,34 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S: Storage> Symbol } let dump_date = self.storage.recent_date()?; - let output = self.command_runner.borrow().get_output( - "sh", - args![ - "-c", - format!( - "find {} -printf '%T@\\n' | sort -r | head -n1 | grep '^[0-9]\\+' -o", - self.path.as_ref().to_str().unwrap() - ), - ], - )?; + let output = self + .command_runner + .borrow() + .get_output( + "sh", + args![ + "-c", + format!( + "find {} -printf '%T@\\n' | sort -r | head -n1 | grep '^[0-9]\\+' -o", + self.path.as_ref().to_str().unwrap() + ), + ], + ) + .await?; let modified_date = u64::from_str(String::from_utf8(output)?.trim_end())?; if if self.dir == StorageDirection::Store { modified_date > dump_date } else { dump_date > modified_date } { - let output = self.command_runner.borrow().run_with_args( - "diff", - args!["-rq", self.storage.read_filename()?, self.path.as_ref()], - )?; + let output = self + .command_runner + .borrow() + .run_with_args( + "diff", + args!["-rq", self.storage.read_filename()?, self.path.as_ref()], + ) + .await?; match output.status.code() { Some(0) => Ok(true), Some(1) => Ok(false), @@ -87,21 +97,30 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S: Storage> Symbol } } - fn execute(&self) -> Result<(), Box> { + async fn execute(&self) -> Result<(), Box> { if self.dir == StorageDirection::Load { self .command_runner .borrow() - .run_successfully("rm", args!["-rf", self.path.as_ref()])?; - self.command_runner.borrow().run_successfully( - "cp", - args!["-a", self.storage.read_filename()?, self.path.as_ref()], - ) + .run_successfully("rm", args!["-rf", self.path.as_ref()]) + .await?; + self + .command_runner + .borrow() + .run_successfully( + "cp", + args!["-a", self.storage.read_filename()?, self.path.as_ref()], + ) + .await } else { - self.command_runner.borrow().run_successfully( - "cp", - args!["-a", self.path.as_ref(), self.storage.write_filename()], - ) + self + .command_runner + .borrow() + .run_successfully( + "cp", + args!["-a", self.path.as_ref(), self.storage.write_filename()], + ) + .await } } } diff --git a/src/symbols/systemd/reload.rs b/src/symbols/systemd/reload.rs index 36ea5ac..ee35965 100644 --- a/src/symbols/systemd/reload.rs +++ b/src/symbols/systemd/reload.rs @@ -1,5 +1,6 @@ use crate::command_runner::CommandRunner; use crate::symbols::Symbol; +use async_trait::async_trait; use std::borrow::Borrow; use std::error::Error; use std::marker::PhantomData; @@ -21,15 +22,20 @@ impl<_C, C, S> ReloadService<_C, C, S> { } } +#[async_trait(?Send)] impl, _C: CommandRunner, C: Borrow<_C>> Symbol for ReloadService<_C, C, S> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { Ok(true) } - fn execute(&self) -> Result<(), Box> { - self.command_runner.borrow().run_successfully( - "systemctl", - args!["reload-or-restart", self.service.as_ref()], - ) + async fn execute(&self) -> Result<(), Box> { + self + .command_runner + .borrow() + .run_successfully( + "systemctl", + args!["reload-or-restart", self.service.as_ref()], + ) + .await } } diff --git a/src/symbols/systemd/user_service.rs b/src/symbols/systemd/user_service.rs index 5f2b31b..9947f98 100644 --- a/src/symbols/systemd/user_service.rs +++ b/src/symbols/systemd/user_service.rs @@ -1,9 +1,10 @@ +use crate::async_utils::sleep; use crate::command_runner::{CommandRunner, SetuidCommandRunner}; use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; use std::ffi::OsStr; use std::path::Path; -use std::thread::sleep; use std::time::Duration; #[derive(Debug)] @@ -29,10 +30,10 @@ impl, U: AsRef, R: CommandRunner> UserService<'static, S, U, } impl, U: AsRef, R: CommandRunner> UserService<'_, S, U, R> { - fn systemctl_wait_for_dbus(&self, args: &[&OsStr]) -> Result> { + async fn systemctl_wait_for_dbus(&self, args: &[&OsStr]) -> Result> { let mut tries = 5; loop { - let result = self.command_runner.run_with_args("systemctl", args)?; + let result = self.command_runner.run_with_args("systemctl", args).await?; if result.status.success() { return Ok(String::from_utf8(result.stdout)?.trim_end().to_string()); } else { @@ -46,28 +47,35 @@ impl, U: AsRef, R: CommandRunner> UserService<'_, S, U, R> { if tries == 0 { return Err("Gave up waiting for dbus to appear".to_string().into()); } - sleep(Duration::from_millis(500)); + sleep(Duration::from_millis(500)).await; } } - fn check_if_service(&self) -> Result> { + async fn check_if_service(&self) -> Result> { loop { - let active_state = self.systemctl_wait_for_dbus(args![ - "--user", - "show", - "--property", - "ActiveState", - self.service_name, - ])?; + let active_state = self + .systemctl_wait_for_dbus(args![ + "--user", + "show", + "--property", + "ActiveState", + self.service_name, + ]) + .await?; match active_state.as_ref() { - "ActiveState=activating" => sleep(Duration::from_millis(500)), + "ActiveState=activating" => sleep(Duration::from_millis(500)).await, "ActiveState=active" => return Ok(true), "ActiveState=failed" => { return Err( - String::from_utf8(self.command_runner.get_output( - "journalctl", - args!["--user", format!("--user-unit={}", self.service_name)], - )?)? + String::from_utf8( + self + .command_runner + .get_output( + "journalctl", + args!["--user", format!("--user-unit={}", self.service_name)], + ) + .await?, + )? .into(), ) } @@ -77,24 +85,29 @@ impl, U: AsRef, R: CommandRunner> UserService<'_, S, U, R> { } } +#[async_trait(?Send)] impl, U: AsRef, R: CommandRunner> Symbol for UserService<'_, S, U, R> { - fn target_reached(&self) -> Result> { - self.check_if_service() + async fn target_reached(&self) -> Result> { + self.check_if_service().await } - fn execute(&self) -> Result<(), Box> { - self.systemctl_wait_for_dbus(args!["--user", "enable", self.service_name])?; - self.systemctl_wait_for_dbus(args!["--user", "restart", self.service_name])?; + async fn execute(&self) -> Result<(), Box> { + self + .systemctl_wait_for_dbus(args!["--user", "enable", self.service_name]) + .await?; + self + .systemctl_wait_for_dbus(args!["--user", "restart", self.service_name]) + .await?; loop { - if !(self.check_if_service()?) { + if !(self.check_if_service().await?) { return Err("Generic error".into()); } if self.socket_path.as_ref().exists() { return Ok(()); } - sleep(Duration::from_millis(500)); + sleep(Duration::from_millis(500)).await; } } } diff --git a/src/symbols/systemd/user_session.rs b/src/symbols/systemd/user_session.rs index c186ee9..5de3342 100644 --- a/src/symbols/systemd/user_session.rs +++ b/src/symbols/systemd/user_session.rs @@ -1,5 +1,6 @@ use crate::command_runner::CommandRunner; use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; use std::path::Path; @@ -18,16 +19,18 @@ impl<'a, U, C> UserSession<'a, U, C> { } } +#[async_trait(?Send)] impl, C: CommandRunner> Symbol for UserSession<'_, U, C> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { let path = Path::new("/var/lib/systemd/linger").join(self.user_name.as_ref()); Ok(path.exists()) // Could also do `loginctl show-user ${self.user_name} | grep -F 'Linger=yes` } - fn execute(&self) -> Result<(), Box> { + async fn execute(&self) -> Result<(), Box> { self .command_runner .run_successfully("loginctl", args!["enable-linger", self.user_name.as_ref()]) + .await } } diff --git a/src/symbols/tls/csr.rs b/src/symbols/tls/csr.rs index e460ea0..3b35681 100644 --- a/src/symbols/tls/csr.rs +++ b/src/symbols/tls/csr.rs @@ -1,5 +1,6 @@ use crate::command_runner::CommandRunner; use crate::symbols::Symbol; +use async_trait::async_trait; use std::borrow::Borrow; use std::error::Error; use std::path::Path; @@ -23,37 +24,43 @@ impl Csr { } } +#[async_trait(?Send)] impl, K: Borrow, P: Borrow> Symbol for Csr { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { if !self.csr_path.borrow().exists() { return Ok(false); } - let output = self.command_runner.get_stderr( - "openssl", - args!["req", "-in", self.csr_path.borrow(), "-noout", "-verify",], - )?; + let output = self + .command_runner + .get_stderr( + "openssl", + args!["req", "-in", self.csr_path.borrow(), "-noout", "-verify",], + ) + .await?; Ok(output == b"verify OK\n") } - fn execute(&self) -> Result<(), Box> { - self.command_runner.run_successfully( - "openssl", - args![ - "req", - "-new", - "-sha256", - "-key", - self.key_path.borrow(), - "-out", - self.csr_path.borrow(), - "-subj", - format!("/CN={}", self.domain.borrow()), - ], - )?; - Ok(()) + async fn execute(&self) -> Result<(), Box> { + self + .command_runner + .run_successfully( + "openssl", + args![ + "req", + "-new", + "-sha256", + "-key", + self.key_path.borrow(), + "-out", + self.csr_path.borrow(), + "-subj", + format!("/CN={}", self.domain.borrow()), + ], + ) + .await } } diff --git a/src/symbols/tls/key.rs b/src/symbols/tls/key.rs index f1cb847..c5287c0 100644 --- a/src/symbols/tls/key.rs +++ b/src/symbols/tls/key.rs @@ -1,5 +1,6 @@ use crate::command_runner::CommandRunner; use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; use std::path::Path; @@ -22,37 +23,44 @@ impl Key { } } +#[async_trait(?Send)] impl> Symbol for Key { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { if !self.file_path.as_ref().exists() { return Ok(false); } - let stdout = self.command_runner.get_output( - "openssl", - args![ - "rsa", - "-in", - self.file_path.as_ref(), - "-noout", - "-check", - "-text", - ], - )?; + let stdout = self + .command_runner + .get_output( + "openssl", + args![ + "rsa", + "-in", + self.file_path.as_ref(), + "-noout", + "-check", + "-text", + ], + ) + .await?; // FIXME check bytes Ok(stdout.ends_with(b"RSA key ok\n")) } - fn execute(&self) -> Result<(), Box> { - self.command_runner.run_successfully( - "openssl", - args![ - "genrsa", - "-out", - self.file_path.as_ref(), - self.get_bytes().to_string(), - ], - ) + async fn execute(&self) -> Result<(), Box> { + self + .command_runner + .run_successfully( + "openssl", + args![ + "genrsa", + "-out", + self.file_path.as_ref(), + self.get_bytes().to_string(), + ], + ) + .await } } diff --git a/src/symbols/user.rs b/src/symbols/user.rs index a4d7ddd..16dd1b6 100644 --- a/src/symbols/user.rs +++ b/src/symbols/user.rs @@ -1,5 +1,6 @@ use crate::command_runner::CommandRunner; use crate::symbols::Symbol; +use async_trait::async_trait; use std::error::Error; #[derive(Debug)] @@ -17,11 +18,13 @@ impl User { } } +#[async_trait(?Send)] impl, C: CommandRunner> Symbol for User { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { let output = self .command_runner - .run_with_args("getent", args!["passwd", self.user_name.as_ref()])?; + .run_with_args("getent", args!["passwd", self.user_name.as_ref()]) + .await?; match output.status.code() { Some(2) => Ok(false), Some(0) => Ok(true), @@ -29,21 +32,24 @@ impl, C: CommandRunner> Symbol for User { } } - fn execute(&self) -> Result<(), Box> { - self.command_runner.run_successfully( - "adduser", - args![ - // "-m", // Necessary for Fedora, not accepted in Debian - "--system", - self.user_name.as_ref(), - ], - )?; - Ok(()) + async fn execute(&self) -> Result<(), Box> { + self + .command_runner + .run_successfully( + "adduser", + args![ + // "-m", // Necessary for Fedora, not accepted in Debian + "--system", + self.user_name.as_ref(), + ], + ) + .await } } #[cfg(test)] mod test { + use crate::async_utils::run; use crate::command_runner::StdCommandRunner; use crate::symbols::user::User; use crate::symbols::Symbol; @@ -54,7 +60,7 @@ mod test { user_name: "nonexisting", command_runner: StdCommandRunner, }; - assert_eq!(symbol.target_reached().unwrap(), false); + assert_eq!(run(symbol.target_reached()).unwrap(), false); } #[test] @@ -63,6 +69,6 @@ mod test { user_name: "root", command_runner: StdCommandRunner, }; - assert_eq!(symbol.target_reached().unwrap(), true); + assert_eq!(run(symbol.target_reached()).unwrap(), true); } } diff --git a/src/symbols/wordpress/plugin.rs b/src/symbols/wordpress/plugin.rs index 123346c..8c280d8 100644 --- a/src/symbols/wordpress/plugin.rs +++ b/src/symbols/wordpress/plugin.rs @@ -1,3 +1,6 @@ +use crate::command_runner::CommandRunner; +use crate::symbols::Symbol; +use async_trait::async_trait; use regex::Regex; use std::error::Error; use std::fs::File as FsFile; @@ -5,9 +8,6 @@ use std::io; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; -use crate::command_runner::CommandRunner; -use crate::symbols::Symbol; - #[derive(Debug)] pub struct Plugin<'a, P, N, R> { base: P, @@ -33,8 +33,9 @@ impl<'a, P: AsRef, N: AsRef, R: CommandRunner> Plugin<'a, P, N, R> { } } +#[async_trait(?Send)] impl, N: AsRef, R: CommandRunner> Symbol for Plugin<'_, P, N, R> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { let base_path = self.get_path(); if !base_path.exists() { return Ok(false); @@ -65,23 +66,26 @@ impl, N: AsRef, R: CommandRunner> Symbol for Plugin<'_, P, N } } } - let upstream = self.command_runner.get_output( - "curl", - args![ - "--form", - format!( - r###"plugins={{"plugins":{{"{0}/{0}.php":{{"Version":"{1}", "PluginURI":"{2}"}}}}}}"###, - self.name.as_ref(), - version, - plugin_uri - ), - "https://api.wordpress.org/plugins/update-check/1.1/", - ], - )?; + let upstream = self + .command_runner + .get_output( + "curl", + args![ + "--form", + format!( + r###"plugins={{"plugins":{{"{0}/{0}.php":{{"Version":"{1}", "PluginURI":"{2}"}}}}}}"###, + self.name.as_ref(), + version, + plugin_uri + ), + "https://api.wordpress.org/plugins/update-check/1.1/", + ], + ) + .await?; Ok(String::from_utf8(upstream)?.contains(r###""plugins":[]"###)) } - fn execute(&self) -> Result<(), Box> { + async fn execute(&self) -> Result<(), Box> { let source = format!( "https://downloads.wordpress.org/plugin/{}.zip", self.name.as_ref() @@ -89,13 +93,18 @@ impl, N: AsRef, R: CommandRunner> Symbol for Plugin<'_, P, N let zip = format!("/tmp/{}.zip", self.name.as_ref()); self .command_runner - .run_successfully("curl", args![source, "-o", zip])?; + .run_successfully("curl", args![source, "-o", zip]) + .await?; + self + .command_runner + .run_successfully("rm", args!["-rf", self.get_path()]) + .await?; self .command_runner - .run_successfully("rm", args!["-rf", self.get_path()])?; - self.command_runner.run_successfully( - "unzip", - args![zip, "-d", self.base.as_ref().join("wp-content/plugins")], - ) + .run_successfully( + "unzip", + args![zip, "-d", self.base.as_ref().join("wp-content/plugins")], + ) + .await } } diff --git a/src/symbols/wordpress/translation.rs b/src/symbols/wordpress/translation.rs index 15dacd2..93b7344 100644 --- a/src/symbols/wordpress/translation.rs +++ b/src/symbols/wordpress/translation.rs @@ -1,5 +1,6 @@ use crate::command_runner::CommandRunner; use crate::symbols::Symbol; +use async_trait::async_trait; use regex::Regex; use std::cmp::max; use std::error::Error; @@ -59,8 +60,9 @@ impl, D: AsRef, R: CommandRunner> Translation<'_, C, D, R> { } } +#[async_trait(?Send)] impl, D: AsRef, R: CommandRunner> Symbol for Translation<'_, C, D, R> { - fn target_reached(&self) -> Result> { + async fn target_reached(&self) -> Result> { let mut newest = String::new(); let match_date = Regex::new("(?m)^\"PO-Revision-Date: (.+)\\+0000\\\\n\"$").unwrap(); for (_, target) in self.get_pairs() { @@ -86,14 +88,17 @@ impl, D: AsRef, R: CommandRunner> Symbol for Translation<'_, } } } - let upstream = self.command_runner.get_output( - "curl", - args![format!( - "https://api.wordpress.org/core/version-check/1.7/?version={}&locale={}", - self.version, - self.locale.as_ref() - )], - )?; + let upstream = self + .command_runner + .get_output( + "curl", + args![format!( + "https://api.wordpress.org/core/version-check/1.7/?version={}&locale={}", + self.version, + self.locale.as_ref() + )], + ) + .await?; Ok(String::from_utf8(upstream)?.contains(&format!( r###"language":"{}","version":"{}","updated":"{}"###, self.locale.as_ref(), @@ -102,11 +107,12 @@ impl, D: AsRef, R: CommandRunner> Symbol for Translation<'_, ))) } - fn execute(&self) -> Result<(), Box> { + async fn execute(&self) -> Result<(), Box> { for (source, target) in self.get_pairs() { self .command_runner - .run_successfully("curl", args!["--compressed", "-o", target, source,])?; + .run_successfully("curl", args!["--compressed", "-o", target, source,]) + .await?; } Ok(()) } diff --git a/src/templates/nginx/server.rs b/src/templates/nginx/server.rs index 51178c4..fe7aa60 100644 --- a/src/templates/nginx/server.rs +++ b/src/templates/nginx/server.rs @@ -90,6 +90,7 @@ impl> SocketSpec for T { } } +#[derive(Debug)] pub struct LocalTcpSocket(usize); impl LocalTcpSocket { diff --git a/src/to_artifact.rs b/src/to_artifact.rs index 2d20807..e158f07 100644 --- a/src/to_artifact.rs +++ b/src/to_artifact.rs @@ -21,5 +21,5 @@ impl ToArtifact for Option { } impl ToArtifact for T { - type Artifact = T::Artifact; + type Artifact = ::Artifact; } diff --git a/tests/file.rs b/tests/file.rs index 443eb2c..6cce6c5 100644 --- a/tests/file.rs +++ b/tests/file.rs @@ -1,3 +1,4 @@ +use schematics::async_utils::run; use schematics::symbols::file::File as FileSymbol; use schematics::symbols::Symbol; use std::fs::File; @@ -30,7 +31,9 @@ fn already_reached() { let dir = get_dir(Some("target content")); let symbol = get_symbol(dir.path()); - assert_eq!(symbol.target_reached().unwrap(), true); + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), true); + }) } // Bad @@ -39,9 +42,11 @@ fn wrong_prefix() { let dir = get_dir(Some("not target content")); let symbol = get_symbol(dir.path()); - assert_eq!(symbol.target_reached().unwrap(), false); - symbol.execute().unwrap(); - assert_eq!(symbol.target_reached().unwrap(), true); + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + symbol.execute().await.unwrap(); + assert_eq!(symbol.target_reached().await.unwrap(), true); + }) } #[test] @@ -49,9 +54,11 @@ fn wrong_postfix() { let dir = get_dir(Some("target content not")); let symbol = get_symbol(dir.path()); - assert_eq!(symbol.target_reached().unwrap(), false); - symbol.execute().unwrap(); - assert_eq!(symbol.target_reached().unwrap(), true); + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + symbol.execute().await.unwrap(); + assert_eq!(symbol.target_reached().await.unwrap(), true); + }) } #[test] @@ -59,9 +66,11 @@ fn empty_file() { let dir = get_dir(Some("")); let symbol = get_symbol(dir.path()); - assert_eq!(symbol.target_reached().unwrap(), false); - symbol.execute().unwrap(); - assert_eq!(symbol.target_reached().unwrap(), true); + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + symbol.execute().await.unwrap(); + assert_eq!(symbol.target_reached().await.unwrap(), true); + }) } #[test] @@ -69,9 +78,11 @@ fn no_file() { let dir = get_dir(None); let symbol = get_symbol(dir.path()); - assert_eq!(symbol.target_reached().unwrap(), false); - symbol.execute().unwrap(); - assert_eq!(symbol.target_reached().unwrap(), true); + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + symbol.execute().await.unwrap(); + assert_eq!(symbol.target_reached().await.unwrap(), true); + }) } // Exceptional cases @@ -80,23 +91,29 @@ fn no_file() { fn may_not_read_file() { let symbol = FileSymbol::new(Path::new("/etc/shadow"), ""); - assert_eq!(symbol.target_reached().is_err(), true); + run(async { + assert_eq!(symbol.target_reached().await.is_err(), true); + }) } #[test] fn may_not_create_file() { let symbol = get_symbol(&Path::new("/proc/somefile")); - // Could also return an error - assert_eq!(symbol.target_reached().unwrap(), false); - assert!(symbol.execute().is_err()); + run(async { + // Could also return an error + assert_eq!(symbol.target_reached().await.unwrap(), false); + assert!(symbol.execute().await.is_err()); + }) } #[test] fn directory_missing() { let symbol = get_symbol(&Path::new("/nonexisting")); - // Could also return an error - assert_eq!(symbol.target_reached().unwrap(), false); - assert!(symbol.execute().is_err()); + run(async { + // Could also return an error + assert_eq!(symbol.target_reached().await.unwrap(), false); + assert!(symbol.execute().await.is_err()); + }) } diff --git a/tests/setup.rs b/tests/setup.rs index 09be8c7..89be1e7 100644 --- a/tests/setup.rs +++ b/tests/setup.rs @@ -1,34 +1,52 @@ -use schematics::resources::{Cert, Csr, GitCheckout}; -use schematics::schema::SymbolRunner; +use async_trait::async_trait; +use schematics::async_utils::sleep; +use schematics::loggers::{Logger, StoringLogger}; +use schematics::resources::{AcmeUser, Cert, Csr, GitCheckout}; use schematics::symbols::Symbol; use schematics::Setup; +use schematics::SymbolRunner; 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 { count: Rc>, } +#[async_trait(?Send)] impl SymbolRunner for TestSymbolRunner { - fn run_symbol( + async fn run_symbol( &self, _symbol: &S, + _logger: &L, _force: bool, ) -> Result> { *self.count.borrow_mut() += 1; + sleep(Duration::from_millis(0)).await; Ok(false) } } +#[test] +fn can_create_an_acme_user() { + 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(AcmeUser).unwrap().0).0, "acme"); +} + #[test] fn runs_only_once() { let count = Rc::new(RefCell::new(0)); let runner = TestSymbolRunner { count: Rc::clone(&count), }; - let mut setup = Setup::new(runner); + let setup = Setup::new(runner, StoringLogger::new()); assert_eq!( (setup.add(Csr("somehost")).unwrap().0) .as_ref() @@ -52,7 +70,7 @@ fn can_create_an_acme_cert() { let runner = TestSymbolRunner { count: Rc::clone(&count), }; - let mut setup = Setup::new(runner); + let setup = Setup::new(runner, StoringLogger::new()); assert_eq!( (setup.add(Cert("somehost")).unwrap().0) .as_ref() @@ -69,7 +87,7 @@ fn can_create_a_git_checkout() { let runner = TestSymbolRunner { count: Rc::clone(&count), }; - let mut setup = Setup::new(runner); + let setup = Setup::new(runner, StoringLogger::new()); setup .add(GitCheckout( "/tmp/somepath".into(),