Browse Source

Experiment with futures

master
Adrian Heine 2 years ago
parent
commit
2d3e3688fa
  1. 5
      Cargo.toml
  2. 87
      src/async_utils.rs
  3. 200
      src/builder.rs
  4. 124
      src/command_runner.rs
  5. 19
      src/lib.rs
  6. 2
      src/locator.rs
  7. 125
      src/loggers.rs
  8. 43
      src/resources/mod.rs
  9. 274
      src/schema.rs
  10. 396
      src/setup.rs
  11. 191
      src/setup/core.rs
  12. 10
      src/setup/mod.rs
  13. 193
      src/setup/runnable.rs
  14. 300
      src/setup/setup.rs
  15. 318
      src/setup/symbol_runner.rs
  16. 9
      src/setup/util.rs
  17. 61
      src/symbols/acme/cert.rs
  18. 6
      src/symbols/concat.rs
  19. 27
      src/symbols/cron.rs
  20. 6
      src/symbols/dir.rs
  21. 6
      src/symbols/file.rs
  22. 124
      src/symbols/git/checkout.rs
  23. 2
      src/symbols/git/mod.rs
  24. 43
      src/symbols/git/submodules.rs
  25. 43
      src/symbols/mariadb/database.rs
  26. 43
      src/symbols/mariadb/dump.rs
  27. 24
      src/symbols/mariadb/user.rs
  28. 6
      src/symbols/mod.rs
  29. 53
      src/symbols/npm.rs
  30. 18
      src/symbols/owner.rs
  31. 100
      src/symbols/postgresql/database.rs
  32. 69
      src/symbols/saved_directory.rs
  33. 18
      src/symbols/systemd/reload.rs
  34. 61
      src/symbols/systemd/user_service.rs
  35. 7
      src/symbols/systemd/user_session.rs
  36. 49
      src/symbols/tls/csr.rs
  37. 52
      src/symbols/tls/key.rs
  38. 34
      src/symbols/user.rs
  39. 57
      src/symbols/wordpress/plugin.rs
  40. 28
      src/symbols/wordpress/translation.rs
  41. 1
      src/templates/nginx/server.rs
  42. 2
      src/to_artifact.rs
  43. 57
      tests/file.rs
  44. 30
      tests/setup.rs

5
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"

87
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<F: Future>(future: F) -> F::Output {
tokio::runtime::Runtime::new().unwrap().block_on(future)
}
pub use tokio::try_join;
#[derive(Debug)]
pub struct TimerFuture {
state: Arc<Mutex<State>>,
}
#[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<Self::Output> {
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<Output = ()> {
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,
}
}
})
}
}

200
src/builder.rs

@ -39,48 +39,49 @@ use crate::to_artifact::ToArtifact;
use std::fmt::Display;
use std::path::{Path, PathBuf};
pub trait SymbolBuilder<R> {
pub trait ImplementationBuilder<R> {
type Prerequisites: ToArtifact;
fn prerequisites(resource: &R) -> Self::Prerequisites;
type Symbol;
type Implementation;
fn create(
resource: &R,
target: &R::Artifact,
inputs: <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol
) -> Self::Implementation
where
R: Resource;
}
#[derive(Debug)]
pub struct DefaultBuilder;
impl<D> SymbolBuilder<Key<D>> for DefaultBuilder {
impl<D> ImplementationBuilder<Key<D>> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &Key<D>) -> Self::Prerequisites {}
type Symbol = KeySymbol<StdCommandRunner, PathBuf>;
type Implementation = KeySymbol<StdCommandRunner, PathBuf>;
fn create(
_resource: &Key<D>,
target: &<Key<D> as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
KeySymbol::new(StdCommandRunner, target.clone().into())
}
}
impl<D: Clone> SymbolBuilder<Csr<D>> for DefaultBuilder {
impl<D: Clone> ImplementationBuilder<Csr<D>> for DefaultBuilder {
type Prerequisites = Key<D>;
fn prerequisites(resource: &Csr<D>) -> Self::Prerequisites {
Key(resource.0.clone())
}
type Symbol = CsrSymbol<StdCommandRunner, D, PathBuf, PathBuf>;
type Implementation = CsrSymbol<StdCommandRunner, D, PathBuf, PathBuf>;
fn create(
resource: &Csr<D>,
target: &<Csr<D> as Resource>::Artifact,
key: <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
CsrSymbol::new(
StdCommandRunner,
resource.0.clone(),
@ -90,7 +91,7 @@ impl<D: Clone> SymbolBuilder<Csr<D>> for DefaultBuilder {
}
}
impl<D: Clone> SymbolBuilder<Cert<D>> for DefaultBuilder {
impl<D: Clone> ImplementationBuilder<Cert<D>> for DefaultBuilder {
type Prerequisites = (
Csr<D>,
AcmeRootCert,
@ -110,7 +111,7 @@ impl<D: Clone> SymbolBuilder<Cert<D>> for DefaultBuilder {
)
}
type Symbol = CertSymbol<
type Implementation = CertSymbol<
SetuidCommandRunner<'static, String, StdCommandRunner>,
SetuidCommandRunner<'static, String, StdCommandRunner>,
D,
@ -120,7 +121,7 @@ impl<D: Clone> SymbolBuilder<Cert<D>> for DefaultBuilder {
resource: &Cert<D>,
target: &<Cert<D> as Resource>::Artifact,
(csr, root_cert, account_key, challenges_dir, user_name, _): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
CertSymbol::new(
resource.0.clone(),
SetuidCommandRunner::new(user_name.0, &StdCommandRunner),
@ -133,73 +134,73 @@ impl<D: Clone> SymbolBuilder<Cert<D>> for DefaultBuilder {
}
}
impl<D: Clone> SymbolBuilder<CertChain<D>> for DefaultBuilder {
impl<D: Clone> ImplementationBuilder<CertChain<D>> for DefaultBuilder {
type Prerequisites = (Cert<D>, AcmeRootCert);
fn prerequisites(resource: &CertChain<D>) -> 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<D>,
target: &<CertChain<D> as Resource>::Artifact,
(cert, root_cert): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
ConcatSymbol::new([cert.into(), root_cert.into()], target.clone().into())
}
}
impl<D: Clone> SymbolBuilder<KeyAndCertBundle<D>> for DefaultBuilder {
impl<D: Clone> ImplementationBuilder<KeyAndCertBundle<D>> for DefaultBuilder {
type Prerequisites = (CertChain<D>, Key<D>);
fn prerequisites(resource: &KeyAndCertBundle<D>) -> 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<D>,
target: &<KeyAndCertBundle<D> as Resource>::Artifact,
(cert_chain, key): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
ConcatSymbol::new([key.into(), cert_chain.into()], target.clone().into())
}
}
impl<P: AsRef<Path> + Clone> SymbolBuilder<File<P>> for DefaultBuilder {
impl<P: AsRef<Path> + Clone> ImplementationBuilder<File<P>> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &File<P>) -> Self::Prerequisites {}
type Symbol = FileSymbol<P, String>;
type Implementation = FileSymbol<P, String>;
fn create(
resource: &File<P>,
_target: &<File<P> as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
FileSymbol::new(resource.0.clone(), resource.1.clone())
}
}
impl<'a, P: AsRef<Path> + Clone> SymbolBuilder<GitCheckout<'a, P>> for DefaultBuilder {
impl<'a, P: AsRef<Path> + Clone> ImplementationBuilder<GitCheckout<'a, P>> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &GitCheckout<'a, P>) -> Self::Prerequisites {}
type Symbol = GitCheckoutSymbol<StdCommandRunner, StdCommandRunner, P, &'a str, &'a str>;
type Implementation = GitCheckoutSymbol<StdCommandRunner, StdCommandRunner, P, &'a str, &'a str>;
fn create(
resource: &GitCheckout<'a, P>,
_target: &<GitCheckout<'a, P> as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
GitCheckoutSymbol::new(resource.0.clone(), resource.1, resource.2, StdCommandRunner)
}
}
impl SymbolBuilder<DefaultServer> for DefaultBuilder {
impl ImplementationBuilder<DefaultServer> for DefaultBuilder {
type Prerequisites = AcmeChallengesNginxSnippet;
fn prerequisites(_resource: &DefaultServer) -> Self::Prerequisites {
AcmeChallengesNginxSnippet
}
type Symbol = (
type Implementation = (
FileSymbol<PathBuf, String>,
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, &'static str>,
);
@ -207,7 +208,7 @@ impl SymbolBuilder<DefaultServer> for DefaultBuilder {
_resource: &DefaultServer,
target: &<DefaultServer as Resource>::Artifact,
challenges_snippet_path: <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
(
FileSymbol::new(
target.clone().into(),
@ -218,7 +219,7 @@ impl SymbolBuilder<DefaultServer> for DefaultBuilder {
}
}
impl<D: AsRef<str> + Clone + Display> SymbolBuilder<ServeCustom<D>> for DefaultBuilder {
impl<D: AsRef<str> + Clone + Display> ImplementationBuilder<ServeCustom<D>> for DefaultBuilder {
type Prerequisites = (CertChain<D>, Key<D>, AcmeChallengesNginxSnippet);
fn prerequisites(resource: &ServeCustom<D>) -> Self::Prerequisites {
(
@ -228,7 +229,7 @@ impl<D: AsRef<str> + Clone + Display> SymbolBuilder<ServeCustom<D>> for DefaultB
)
}
type Symbol = (
type Implementation = (
FileSymbol<PathBuf, String>,
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, &'static str>,
);
@ -236,7 +237,7 @@ impl<D: AsRef<str> + Clone + Display> SymbolBuilder<ServeCustom<D>> for DefaultB
resource: &ServeCustom<D>,
target: &<ServeCustom<D> as Resource>::Artifact,
(cert, key, challenges_snippet_path): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
(
FileSymbol::new(
target.clone().into(),
@ -247,7 +248,7 @@ impl<D: AsRef<str> + Clone + Display> SymbolBuilder<ServeCustom<D>> for DefaultB
}
}
impl<D: Clone + Display, P: AsRef<Path>> SymbolBuilder<ServePhp<D, P>> for DefaultBuilder {
impl<D: Clone + Display, P: AsRef<Path>> ImplementationBuilder<ServePhp<D, P>> for DefaultBuilder {
type Prerequisites = (
PhpFpmPool<D>,
CertChain<D>,
@ -263,7 +264,7 @@ impl<D: Clone + Display, P: AsRef<Path>> SymbolBuilder<ServePhp<D, P>> for Defau
)
}
type Symbol = (
type Implementation = (
FileSymbol<PathBuf, String>,
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, &'static str>,
);
@ -271,7 +272,7 @@ impl<D: Clone + Display, P: AsRef<Path>> SymbolBuilder<ServePhp<D, P>> for Defau
resource: &ServePhp<D, P>,
target: &<ServePhp<D, P> as Resource>::Artifact,
(pool, cert, key, challenges_snippet_path): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
(
FileSymbol::new(
target.clone().into(),
@ -288,7 +289,7 @@ impl<D: Clone + Display, P: AsRef<Path>> SymbolBuilder<ServePhp<D, P>> for Defau
}
}
impl<D: Clone + Display, P: Clone + AsRef<Path>> SymbolBuilder<ServeService<D, P>>
impl<D: Clone + Display, P: Clone + AsRef<Path>> ImplementationBuilder<ServeService<D, P>>
for DefaultBuilder
{
type Prerequisites = (
@ -312,7 +313,7 @@ impl<D: Clone + Display, P: Clone + AsRef<Path>> SymbolBuilder<ServeService<D, P
)
}
type Symbol = (
type Implementation = (
FileSymbol<PathBuf, String>,
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, &'static str>,
);
@ -320,7 +321,7 @@ impl<D: Clone + Display, P: Clone + AsRef<Path>> SymbolBuilder<ServeService<D, P
resource: &ServeService<D, P>,
target: &<ServeService<D, P> as Resource>::Artifact,
(socket, cert, key, challenges_snippet_path): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
(
FileSymbol::new(
target.clone().into(),
@ -337,7 +338,7 @@ impl<D: Clone + Display, P: Clone + AsRef<Path>> SymbolBuilder<ServeService<D, P
}
}
impl<D: AsRef<str> + Clone + Display> SymbolBuilder<ServeRedir<D>> for DefaultBuilder {
impl<D: AsRef<str> + Clone + Display> ImplementationBuilder<ServeRedir<D>> for DefaultBuilder {
type Prerequisites = (CertChain<D>, Key<D>, AcmeChallengesNginxSnippet);
fn prerequisites(resource: &ServeRedir<D>) -> Self::Prerequisites {
(
@ -347,7 +348,7 @@ impl<D: AsRef<str> + Clone + Display> SymbolBuilder<ServeRedir<D>> for DefaultBu
)
}
type Symbol = (
type Implementation = (
FileSymbol<PathBuf, String>,
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, &'static str>,
);
@ -355,7 +356,7 @@ impl<D: AsRef<str> + Clone + Display> SymbolBuilder<ServeRedir<D>> for DefaultBu
resource: &ServeRedir<D>,
target: &<ServeRedir<D> as Resource>::Artifact,
(cert, key, challenges_snippet_path): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
(
FileSymbol::new(
target.clone().into(),
@ -372,7 +373,7 @@ impl<D: AsRef<str> + Clone + Display> SymbolBuilder<ServeRedir<D>> for DefaultBu
}
}
impl<D: AsRef<str> + Clone + Display, P: AsRef<Path>> SymbolBuilder<ServeStatic<D, P>>
impl<D: AsRef<str> + Clone + Display, P: AsRef<Path>> ImplementationBuilder<ServeStatic<D, P>>
for DefaultBuilder
{
type Prerequisites = (CertChain<D>, Key<D>, AcmeChallengesNginxSnippet);
@ -384,7 +385,7 @@ impl<D: AsRef<str> + Clone + Display, P: AsRef<Path>> SymbolBuilder<ServeStatic<
)
}
type Symbol = (
type Implementation = (
FileSymbol<PathBuf, String>,
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, &'static str>,
);
@ -392,7 +393,7 @@ impl<D: AsRef<str> + Clone + Display, P: AsRef<Path>> SymbolBuilder<ServeStatic<
resource: &ServeStatic<D, P>,
target: &<ServeStatic<D, P> as Resource>::Artifact,
(cert, key, challenges_snippet_path): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
(
FileSymbol::new(
target.clone().into(),
@ -409,11 +410,11 @@ impl<D: AsRef<str> + Clone + Display, P: AsRef<Path>> SymbolBuilder<ServeStatic<
}
}
impl<D: Clone> SymbolBuilder<PhpFpmPool<D>> for DefaultBuilder {
impl<D: Clone> ImplementationBuilder<PhpFpmPool<D>> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &PhpFpmPool<D>) -> Self::Prerequisites {}
type Symbol = (
type Implementation = (
FileSymbol<PathBuf, String>,
ReloadServiceSymbol<StdCommandRunner, StdCommandRunner, String>,
);
@ -421,7 +422,7 @@ impl<D: Clone> SymbolBuilder<PhpFpmPool<D>> for DefaultBuilder {
resource: &PhpFpmPool<D>,
(socket_path, conf_path, user_name, service_name): &<PhpFpmPool<D> as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
(
FileSymbol::new(
conf_path.clone().into(),
@ -432,11 +433,11 @@ impl<D: Clone> SymbolBuilder<PhpFpmPool<D>> for DefaultBuilder {
}
}
impl<D, P: AsRef<Path>> SymbolBuilder<SystemdSocketService<D, P>> for DefaultBuilder {
impl<D, P: AsRef<Path>> ImplementationBuilder<SystemdSocketService<D, P>> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &SystemdSocketService<D, P>) -> Self::Prerequisites {}
type Symbol = (
type Implementation = (
FileSymbol<PathBuf, String>,
SystemdUserSessionSymbol<'static, String, StdCommandRunner>,
UserServiceSymbol<'static, PathBuf, String, StdCommandRunner>,
@ -445,7 +446,7 @@ impl<D, P: AsRef<Path>> SymbolBuilder<SystemdSocketService<D, P>> for DefaultBui
resource: &SystemdSocketService<D, P>,
(socket_path, conf_path, user_name): &<SystemdSocketService<D, P> as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
(
FileSymbol::new(
conf_path.clone().into(),
@ -471,44 +472,44 @@ impl<D, P: AsRef<Path>> SymbolBuilder<SystemdSocketService<D, P>> for DefaultBui
}
}
impl<P: Clone> SymbolBuilder<Dir<P>> for DefaultBuilder {
impl<P: Clone> ImplementationBuilder<Dir<P>> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &Dir<P>) -> Self::Prerequisites {}
type Symbol = DirSymbol<P>;
type Implementation = DirSymbol<P>;
fn create(
resource: &Dir<P>,
_target: &<Dir<P> as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
DirSymbol::new(resource.0.clone())
}
}
impl<P: Clone + AsRef<Path>> SymbolBuilder<NpmInstall<P>> for DefaultBuilder {
impl<P: Clone + AsRef<Path>> ImplementationBuilder<NpmInstall<P>> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &NpmInstall<P>) -> Self::Prerequisites {}
type Symbol = NpmInstallSymbol<'static, P, StdCommandRunner>;
type Implementation = NpmInstallSymbol<'static, P, StdCommandRunner>;
fn create(
resource: &NpmInstall<P>,
_target: &<NpmInstall<P> as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
NpmInstallSymbol::new(resource.0.clone(), &StdCommandRunner)
}
}
impl<P: Clone + AsRef<Path>> SymbolBuilder<StoredDirectory<P>> for DefaultBuilder {
impl<P: Clone + AsRef<Path>> ImplementationBuilder<StoredDirectory<P>> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &StoredDirectory<P>) -> Self::Prerequisites {}
type Symbol = SavedDirectorySymbol<StdCommandRunner, StdCommandRunner, P, SimpleStorage>;
type Implementation = SavedDirectorySymbol<StdCommandRunner, StdCommandRunner, P, SimpleStorage>;
fn create(
resource: &StoredDirectory<P>,
target: &<StoredDirectory<P> as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
SavedDirectorySymbol::new(
resource.1.clone(),
SimpleStorage::new(target.clone().into()),
@ -518,16 +519,16 @@ impl<P: Clone + AsRef<Path>> SymbolBuilder<StoredDirectory<P>> for DefaultBuilde
}
}
impl<P: Clone + AsRef<Path>> SymbolBuilder<LoadedDirectory<P>> for DefaultBuilder {
impl<P: Clone + AsRef<Path>> ImplementationBuilder<LoadedDirectory<P>> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &LoadedDirectory<P>) -> Self::Prerequisites {}
type Symbol = SavedDirectorySymbol<StdCommandRunner, StdCommandRunner, P, SimpleStorage>;
type Implementation = SavedDirectorySymbol<StdCommandRunner, StdCommandRunner, P, SimpleStorage>;
fn create(
resource: &LoadedDirectory<P>,
target: &<LoadedDirectory<P> as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
SavedDirectorySymbol::new(
resource.1.clone(),
SimpleStorage::new(target.clone().into()),
@ -537,69 +538,69 @@ impl<P: Clone + AsRef<Path>> SymbolBuilder<LoadedDirectory<P>> for DefaultBuilde
}
}
impl<D: Clone> SymbolBuilder<UserForDomain<D>> for DefaultBuilder {
impl<D: Clone> ImplementationBuilder<UserForDomain<D>> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &UserForDomain<D>) -> Self::Prerequisites {}
type Symbol = UserSymbol<String, StdCommandRunner>;
type Implementation = UserSymbol<String, StdCommandRunner>;
fn create(
_resource: &UserForDomain<D>,
(user_name, _home_path): &<UserForDomain<D> as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
UserSymbol::new(user_name.0.clone(), StdCommandRunner)
}
}
impl SymbolBuilder<User> for DefaultBuilder {
impl ImplementationBuilder<User> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &User) -> Self::Prerequisites {}
type Symbol = UserSymbol<String, StdCommandRunner>;
type Implementation = UserSymbol<String, StdCommandRunner>;
fn create(
resource: &User,
(): &<User as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
UserSymbol::new(resource.0.clone(), StdCommandRunner)
}
}
impl<P: AsRef<Path> + Clone> SymbolBuilder<Owner<P>> for DefaultBuilder {
impl<P: AsRef<Path> + Clone> ImplementationBuilder<Owner<P>> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &Owner<P>) -> Self::Prerequisites {}
type Symbol = OwnerSymbol<StdCommandRunner, StdCommandRunner, P, String>;
type Implementation = OwnerSymbol<StdCommandRunner, StdCommandRunner, P, String>;
fn create(
resource: &Owner<P>,
(): &<Owner<P> as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
OwnerSymbol::new(resource.1.clone(), resource.0.clone(), StdCommandRunner)
}
}
impl SymbolBuilder<AcmeUser> for DefaultBuilder {
impl ImplementationBuilder<AcmeUser> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &AcmeUser) -> Self::Prerequisites {}
type Symbol = UserSymbol<String, StdCommandRunner>;
type Implementation = UserSymbol<String, StdCommandRunner>;
fn create(
_resource: &AcmeUser,
user_name: &<AcmeUser as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
UserSymbol::new(user_name.0.clone(), StdCommandRunner)
}
}
impl SymbolBuilder<AcmeChallengesDir> for DefaultBuilder {
impl ImplementationBuilder<AcmeChallengesDir> for DefaultBuilder {
type Prerequisites = AcmeUser;
fn prerequisites(_resource: &AcmeChallengesDir) -> Self::Prerequisites {
AcmeUser
}
type Symbol = (
type Implementation = (
DirSymbol<PathBuf>,
OwnerSymbol<StdCommandRunner, StdCommandRunner, PathBuf, String>,
);
@ -607,7 +608,7 @@ impl SymbolBuilder<AcmeChallengesDir> for DefaultBuilder {
_resource: &AcmeChallengesDir,
target: &<AcmeChallengesDir as Resource>::Artifact,
user_name: <Self::Prerequisites as ToArtifact>::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<AcmeChallengesDir> for DefaultBuilder {
}
}
impl SymbolBuilder<AcmeChallengesNginxSnippet> for DefaultBuilder {
impl ImplementationBuilder<AcmeChallengesNginxSnippet> for DefaultBuilder {
type Prerequisites = AcmeChallengesDir;
fn prerequisites(_resource: &AcmeChallengesNginxSnippet) -> Self::Prerequisites {
AcmeChallengesDir
}
type Symbol = FileSymbol<PathBuf, String>;
type Implementation = FileSymbol<PathBuf, String>;
fn create(
_resource: &AcmeChallengesNginxSnippet,
target: &<AcmeChallengesNginxSnippet as Resource>::Artifact,
challenges_dir: <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
FileSymbol::new(
target.clone().into(),
nginx::acme_challenges_snippet(challenges_dir),
@ -634,13 +635,13 @@ impl SymbolBuilder<AcmeChallengesNginxSnippet> for DefaultBuilder {
}
}
impl SymbolBuilder<AcmeAccountKey> for DefaultBuilder {
impl ImplementationBuilder<AcmeAccountKey> for DefaultBuilder {
type Prerequisites = AcmeUser;
fn prerequisites(_resource: &AcmeAccountKey) -> Self::Prerequisites {
AcmeUser
}
type Symbol = (
type Implementation = (
KeySymbol<StdCommandRunner, PathBuf>,
OwnerSymbol<StdCommandRunner, StdCommandRunner, PathBuf, String>,
);
@ -648,7 +649,7 @@ impl SymbolBuilder<AcmeAccountKey> for DefaultBuilder {
_resource: &AcmeAccountKey,
target: &<AcmeAccountKey as Resource>::Artifact,
user_name: <Self::Prerequisites as ToArtifact>::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<AcmeAccountKey> for DefaultBuilder {
}
}
impl SymbolBuilder<AcmeRootCert> for DefaultBuilder {
impl ImplementationBuilder<AcmeRootCert> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &AcmeRootCert) -> Self::Prerequisites {}
type Symbol = FileSymbol<PathBuf, &'static str>;
type Implementation = FileSymbol<PathBuf, &'static str>;
fn create(
_resource: &AcmeRootCert,
target: &<AcmeRootCert as Resource>::Artifact,
(): <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
FileSymbol::new(target.clone().into(), LETS_ENCRYPT_X3_CROSS_SIGNED)
}
}
impl<D> SymbolBuilder<MariaDbUser<D>> for DefaultBuilder {
impl<D> ImplementationBuilder<MariaDbUser<D>> for DefaultBuilder {
type Prerequisites = ();
fn prerequisites(_resource: &MariaDbUser<D>) -> Self::Prerequisites {}
type Symbol = MariaDbUserSymbol<'static, String, StdCommandRunner>;
type Implementation = MariaDbUserSymbol<'static, String, StdCommandRunner>;
fn create(
_resource: &MariaDbUser<D>,
user_name: &<MariaDbUser<D> as Resource>::Artifact,
_: <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
MariaDbUserSymbol::new(user_name.0.clone(), &StdCommandRunner)
}
}
impl<D: Clone> SymbolBuilder<MariaDbDatabase<D>> for DefaultBuilder {
impl<D: Clone> ImplementationBuilder<MariaDbDatabase<D>> for DefaultBuilder {
type Prerequisites = MariaDbUser<D>;
fn prerequisites(resource: &MariaDbDatabase<D>) -> 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<D: Clone> SymbolBuilder<MariaDbDatabase<D>> for DefaultBuilder {
_resource: &MariaDbDatabase<D>,
(db_name, _, data_path): &<MariaDbDatabase<D> as Resource>::Artifact,
_: <Self::Prerequisites as ToArtifact>::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<D: Clone> SymbolBuilder<MariaDbDatabase<D>> for DefaultBuilder {
}
}
impl<P: Clone + AsRef<Path>> SymbolBuilder<WordpressPlugin<P>> for DefaultBuilder {
impl<P: Clone + AsRef<Path>> ImplementationBuilder<WordpressPlugin<P>> for DefaultBuilder {
type Prerequisites = Dir<PathBuf>;
fn prerequisites(resource: &WordpressPlugin<P>) -> 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<P>,
(): &<WordpressPlugin<P> as Resource>::Artifact,
_: <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
WordpressPluginSymbol::new(resource.0.clone(), resource.1, &StdCommandRunner)
}
}
impl<P: Clone + AsRef<Path>> SymbolBuilder<WordpressTranslation<P>> for DefaultBuilder {
impl<P: Clone + AsRef<Path>> ImplementationBuilder<WordpressTranslation<P>> for DefaultBuilder {
type Prerequisites = Dir<PathBuf>;
fn prerequisites(resource: &WordpressTranslation<P>) -> 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<P>,
(): &<WordpressTranslation<P> as Resource>::Artifact,
_: <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
WordpressTranslationSymbol::new(
resource.0.as_ref().join("wp-content/languages"),
resource.1,
@ -744,18 +746,18 @@ impl<P: Clone + AsRef<Path>> SymbolBuilder<WordpressTranslation<P>> for DefaultB
}
}
impl<D: Clone> SymbolBuilder<Cron<D>> for DefaultBuilder {
impl<D: Clone> ImplementationBuilder<Cron<D>> for DefaultBuilder {
type Prerequisites = UserForDomain<D>;
fn prerequisites(resource: &Cron<D>) -> Self::Prerequisites {
UserForDomain(resource.0.clone())
}
type Symbol = CronSymbol<'static, String, String, StdCommandRunner>;
type Implementation = CronSymbol<'static, String, String, StdCommandRunner>;
fn create(
resource: &Cron<D>,
(): &<Cron<D> as Resource>::Artifact,
user_name: <Self::Prerequisites as ToArtifact>::Artifact,
) -> Self::Symbol {
) -> Self::Implementation {
CronSymbol::new((user_name.0).0, resource.1.clone(), &StdCommandRunner)
}
}

124
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<Output, Box<dyn Error>> {
if output.status.success() {
Ok(output)
} else {
Err(String::from_utf8(output.stderr)?.into())
}
}
pub fn is_success(res: Result<Output, impl Error + 'static>) -> Result<Output, Box<dyn Error>> {
check_success(res?)
}
pub fn get_output(output: Output) -> Result<Vec<u8>, Box<dyn Error>> {
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<Output>;
fn run_with_args(&self, program: &str, args: &[&OsStr]) -> IoResult<Output> {
self.run_with_args_and_stdin(program, args, "")
async fn run(&self, program: &str, args: &[&OsStr], stdin: &str) -> IoResult<Output>;
async fn run_with_args(&self, program: &str, args: &[&OsStr]) -> IoResult<Output> {
self.run(program, args, "").await
}
fn get_output(&self, program: &str, args: &[&OsStr]) -> Result<Vec<u8>, Box<dyn Error>> {
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<Vec<u8>, Box<dyn Error>> {
let output = self.run_with_args(program, args).await?;
get_output(output)
}
fn get_stderr(&self, program: &str, args: &[&OsStr]) -> Result<Vec<u8>, Box<dyn Error>> {
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<dyn Error>> {
is_success(self.run(program, args, "").await)?;
Ok(())
}
fn run_successfully(&self, program: &str, args: &[&OsStr]) -> Result<(), Box<dyn Error>> {
self.get_output(program, args).map(|_| ())
async fn get_stderr(&self, program: &str, args: &[&OsStr]) -> Result<Vec<u8>, Box<dyn Error>> {
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<Output> {
// FIXME: logger
async fn run(&self, program: &str, args: &[&OsStr], input: &str) -> IoResult<Output> {
//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<str>, 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<U: AsRef<str>, C: CommandRunner> CommandRunner for SetuidCommandRunner<'_, U, C> {
fn run_with_args_and_stdin(
&self,
program: &str,
args: &[&OsStr],
input: &str,
) -> IoResult<Output> {
async fn run(&self, program: &str, args: &[&OsStr], input: &str) -> IoResult<Output> {
let uid = get_user_by_name(self.user_name.as_ref())
.expect("User does not exist")
.uid();
@ -140,9 +140,10 @@ impl<U: AsRef<str>, 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<Output> {
async fn run(&self, program: &str, args: &[&OsStr], input: &str) -> IoResult<Output> {
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,
}
}
})
}
}

19
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,
};

2
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> {
R: Resource;
}
#[derive(Debug)]
pub struct DefaultLocator<P = DefaultPolicy> {
phantom: PhantomData<P>,
}

125
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<S>(pub Level, pub S);
pub trait Logger {
fn write(&mut self, msg: &str);
fn debug(&mut self, msg: &str);
fn write<S: AsRef<str> + Into<String>>(&self, level: Level, msg: S)
where
Self: Sized;
fn writeln<S: AsRef<str> + Into<String>>(&self, level: Level, msg: S)
where
Self: Sized;
fn info<S: AsRef<str> + Into<String>>(&self, msg: S)
where
Self: Sized,
{
self.writeln(3, msg)
}
fn debug<S: AsRef<str> + Into<String>>(&self, msg: S)
where
Self: Sized,
{
self.writeln(4, msg)
}
fn trace<S: AsRef<str> + Into<String>>(&self, msg: S)
where
Self: Sized,
{
self.writeln(5, msg)
}
fn put<S: AsRef<str> + Into<String>>(&self, entries: impl IntoIterator<Item = Entry<S>>) -> 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<bool>,
}
impl Logger for StdErrLogger {
fn debug(&mut self, str: &str) {
writeln!(&mut stderr(), "{}", str).unwrap();
fn write<S: AsRef<str> + Into<String>>(&self, _level: Level, msg: S) {
*self.line_started.borrow_mut() = true;
write!(&mut stderr(), "{}", msg.as_ref()).unwrap();
}
fn writeln<S: AsRef<str> + Into<String>>(&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<S: AsRef<str> + Into<String>>(&self, level: Level, str: S) {
if level <= self.max_level {
self.logger.write(level, str)
}
}
fn writeln<S: AsRef<str> + Into<String>>(&self, level: Level, str: S) {
if level <= self.max_level {
self.logger.writeln(level, str)
}
}
}
#[derive(Debug, Default)]
pub struct StoringLogger {
log: RefCell<Vec<Entry<String>>>,
}
impl StoringLogger {
pub fn new() -> Self {
Self::default()
}
pub fn release(self) -> Vec<Entry<String>> {
self.log.into_inner()
}
}
impl Logger for StoringLogger {
fn write<S: AsRef<str> + Into<String>>(&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<S: AsRef<str> + Into<String>>(&self, level: Level, line: S) {
self.log.borrow_mut().push(Entry(level, line.into()));
}
}

43
src/resources/mod.rs

@ -218,28 +218,51 @@ impl<D> Resource for Cron<D> {
type Artifact = ();
}
pub trait BorrowResource<R> {
fn borrow_resource(&self) -> Option<&R>;
use std::rc::{Rc, Weak};
pub trait FromResource<R> {
fn from_resource(from: R) -> (Self, Weak<R>)
where
Self: Sized;
}
pub trait FromArtifact<R: Resource> {
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