Compare commits
No commits in common. "ac1c06dd319edd94bb7bcbafd1da309fa587be0f" and "4938a51ad72b78a4da4b7c714f9b75ec945bebfa" have entirely different histories.
ac1c06dd31
...
4938a51ad7
34 changed files with 897 additions and 1097 deletions
|
|
@ -9,12 +9,9 @@ build = "src/build.rs"
|
||||||
users = "0.11.0"
|
users = "0.11.0"
|
||||||
regex = "1.0.1"
|
regex = "1.0.1"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
async-trait = "0.1.65"
|
async-trait = "0.1"
|
||||||
tokio = { version = "1.26", features = ["rt", "process", "io-util", "macros", "sync"] }
|
tokio = { version = "1.6.1", features = ["rt", "process", "io-util", "macros", "sync"] }
|
||||||
once_cell = "1.4"
|
once_cell = "1.4"
|
||||||
slog = { version = "2", features = ["max_level_trace", "release_max_level_trace"] }
|
|
||||||
slog-term = "2.5"
|
|
||||||
slog-async = "2.7"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|
|
||||||
|
|
@ -36,20 +36,26 @@ impl Future for TimerFuture {
|
||||||
type Output = ();
|
type Output = ();
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
match *state {
|
if let State::Completed = *state {
|
||||||
State::Completed => return Poll::Ready(()),
|
return Poll::Ready(());
|
||||||
State::NotStarted(duration) => {
|
}
|
||||||
let thread_state = self.state.clone();
|
|
||||||
thread::spawn(move || {
|
if let State::NotStarted(duration) = *state {
|
||||||
thread::sleep(duration);
|
let thread_state = self.state.clone();
|
||||||
let mut state = thread_state.lock().unwrap();
|
thread::spawn(move || {
|
||||||
if let State::Running(waker) = std::mem::replace(&mut *state, State::Completed) {
|
thread::sleep(duration);
|
||||||
waker.wake();
|
let mut state = thread_state.lock().unwrap();
|
||||||
};
|
let waker = if let State::Running(waker) = &*state {
|
||||||
});
|
Some(waker.clone())
|
||||||
}
|
} else {
|
||||||
State::Running(_) => {}
|
None
|
||||||
};
|
};
|
||||||
|
*state = State::Completed;
|
||||||
|
if let Some(w) = waker {
|
||||||
|
w.wake();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
*state = State::Running(cx.waker().clone());
|
*state = State::Running(cx.waker().clone());
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
|
|
@ -78,10 +84,10 @@ mod test {
|
||||||
loop {
|
loop {
|
||||||
futures_util::select! {
|
futures_util::select! {
|
||||||
_ = sleep => {},
|
_ = sleep => {},
|
||||||
_ = ok => assert!(start.elapsed().as_millis() < 100),
|
_ = ok => assert!((Instant::now() - start).as_millis() < 100),
|
||||||
complete => break,
|
complete => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
74
src/build.rs
74
src/build.rs
|
|
@ -1,53 +1,57 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::error::Error;
|
use std::fs::{read_dir, File};
|
||||||
use std::fs::{read as read_file, File};
|
|
||||||
use std::io::ErrorKind::NotFound;
|
use std::io::ErrorKind::NotFound;
|
||||||
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
pub fn create_static_output(
|
fn get_const_name<P: Clone + Into<PathBuf>>(p: &P) -> String {
|
||||||
source_path: &Path,
|
let mut file_name_without_extension = p.clone().into();
|
||||||
mut target: impl Write,
|
file_name_without_extension.set_extension("");
|
||||||
) -> Result<(), Box<dyn Error>> {
|
String::from(
|
||||||
let const_name = source_path
|
file_name_without_extension
|
||||||
.file_stem()
|
.file_name()
|
||||||
.ok_or("Not a filename")?
|
.unwrap()
|
||||||
.to_str()
|
.to_string_lossy(),
|
||||||
.ok_or("Filename is not valid unicode")?
|
)
|
||||||
.to_uppercase();
|
.to_uppercase()
|
||||||
let content = String::from_utf8(read_file(source_path)?)?;
|
|
||||||
let fence = content.chars().filter(|&c| c == '#').collect::<String>() + "#";
|
|
||||||
|
|
||||||
writeln!(
|
|
||||||
target,
|
|
||||||
"pub const {const_name}: &str = r{fence}\"{content}\"{fence};"
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_static_output_files(
|
pub fn create_static_output_files(source_dir: &str) {
|
||||||
source_dir: &Path,
|
let out_dir = env::var("OUT_DIR").unwrap();
|
||||||
dest_path: &Path,
|
let dest_path = Path::new(&out_dir).join("static_files.rs");
|
||||||
) -> Result<(), Box<dyn Error>> {
|
let mut f = File::create(&dest_path).unwrap();
|
||||||
let mut f = File::create(dest_path)?;
|
match read_dir(source_dir) {
|
||||||
match source_dir.read_dir() {
|
|
||||||
Ok(dir_content) => {
|
Ok(dir_content) => {
|
||||||
for maybe_dir_entry in dir_content {
|
for maybe_dir_entry in dir_content {
|
||||||
create_static_output(&maybe_dir_entry?.path(), &mut f)?;
|
let file_path = maybe_dir_entry.unwrap().path();
|
||||||
|
let mut buffer = String::new();
|
||||||
|
File::open(file_path.clone())
|
||||||
|
.unwrap()
|
||||||
|
.read_to_string(&mut buffer)
|
||||||
|
.unwrap();
|
||||||
|
let fence = buffer.chars().filter(|c| *c == '#').collect::<String>() + "#";
|
||||||
|
f.write_all(
|
||||||
|
format!(
|
||||||
|
"pub const {}: &str = r{1}\"{2}\"{1};\n",
|
||||||
|
get_const_name(&file_path),
|
||||||
|
fence,
|
||||||
|
buffer
|
||||||
|
)
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.kind() != NotFound {
|
if err.kind() != NotFound {
|
||||||
return Err(format!("Unexpected error: {err}").into());
|
panic!("Unexpected error: {}", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() -> Result<(), Box<dyn Error>> {
|
#[allow(unused)]
|
||||||
create_static_output_files(
|
fn main() {
|
||||||
Path::new("static_files"),
|
create_static_output_files("static_files");
|
||||||
&(Path::new(&env::var("OUT_DIR")?).join("static_files.rs")),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,12 @@ impl<D: Clone> ImplementationBuilder<Cert<D>> for DefaultBuilder {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Implementation =
|
type Implementation = CertSymbol<
|
||||||
CertSymbol<SetuidCommandRunner<String>, SetuidCommandRunner<String>, D, PathBuf>;
|
SetuidCommandRunner<'static, String, StdCommandRunner>,
|
||||||
|
SetuidCommandRunner<'static, String, StdCommandRunner>,
|
||||||
|
D,
|
||||||
|
PathBuf,
|
||||||
|
>;
|
||||||
fn create(
|
fn create(
|
||||||
resource: &Cert<D>,
|
resource: &Cert<D>,
|
||||||
target: &<Cert<D> as Resource>::Artifact,
|
target: &<Cert<D> as Resource>::Artifact,
|
||||||
|
|
@ -124,7 +128,7 @@ impl<D: Clone> ImplementationBuilder<Cert<D>> for DefaultBuilder {
|
||||||
) -> Self::Implementation {
|
) -> Self::Implementation {
|
||||||
CertSymbol::new(
|
CertSymbol::new(
|
||||||
resource.0.clone(),
|
resource.0.clone(),
|
||||||
SetuidCommandRunner::new(user_name.0),
|
SetuidCommandRunner::new(user_name.0, &StdCommandRunner),
|
||||||
root_cert.into(),
|
root_cert.into(),
|
||||||
account_key.into(),
|
account_key.into(),
|
||||||
challenges_dir.into(),
|
challenges_dir.into(),
|
||||||
|
|
@ -282,7 +286,7 @@ impl<D: Clone + Display, P: AsRef<Path>, C: Clone + Into<PhpFpmPoolConfig>>
|
||||||
&resource.0,
|
&resource.0,
|
||||||
cert,
|
cert,
|
||||||
key,
|
key,
|
||||||
nginx::php_snippet(resource.2, pool.0, &resource.1) + &resource.3,
|
nginx::php_snippet(resource.2, &pool.0, &resource.1) + &resource.3,
|
||||||
challenges_snippet_path,
|
challenges_snippet_path,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -428,7 +432,7 @@ impl<D: Clone> ImplementationBuilder<PhpFpmPool<D>> for DefaultBuilder {
|
||||||
(
|
(
|
||||||
FileSymbol::new(
|
FileSymbol::new(
|
||||||
conf_path.clone().into(),
|
conf_path.clone().into(),
|
||||||
php_fpm_pool_config(&user_name.0, socket_path, &resource.1),
|
php_fpm_pool_config(&user_name.0, &socket_path, &resource.1),
|
||||||
),
|
),
|
||||||
ReloadServiceSymbol::new(StdCommandRunner, service_name.0.clone()),
|
ReloadServiceSymbol::new(StdCommandRunner, service_name.0.clone()),
|
||||||
)
|
)
|
||||||
|
|
@ -444,7 +448,7 @@ impl<D, P: AsRef<Path>> ImplementationBuilder<SystemdSocketService<D, P>> for De
|
||||||
FileSymbol<PathBuf, String>,
|
FileSymbol<PathBuf, String>,
|
||||||
SystemdUserSessionSymbol<'static, String, StdCommandRunner>,
|
SystemdUserSessionSymbol<'static, String, StdCommandRunner>,
|
||||||
OwnerSymbol<StdCommandRunner, StdCommandRunner, PathBuf, String>,
|
OwnerSymbol<StdCommandRunner, StdCommandRunner, PathBuf, String>,
|
||||||
UserServiceSymbol<'static, PathBuf, String>,
|
UserServiceSymbol<'static, PathBuf, String, StdCommandRunner>,
|
||||||
);
|
);
|
||||||
fn create(
|
fn create(
|
||||||
resource: &SystemdSocketService<D, P>,
|
resource: &SystemdSocketService<D, P>,
|
||||||
|
|
@ -471,7 +475,12 @@ impl<D, P: AsRef<Path>> ImplementationBuilder<SystemdSocketService<D, P>> for De
|
||||||
user_name.0.clone(),
|
user_name.0.clone(),
|
||||||
StdCommandRunner,
|
StdCommandRunner,
|
||||||
),
|
),
|
||||||
UserServiceSymbol::new(socket_path.clone().into(), user_name.0.clone(), resource.1),
|
UserServiceSymbol::new(
|
||||||
|
socket_path.clone().into(),
|
||||||
|
user_name.0.clone(),
|
||||||
|
resource.1,
|
||||||
|
&StdCommandRunner,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,9 +63,13 @@ impl CommandRunner for StdCommandRunner {
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.spawn()?;
|
.spawn()
|
||||||
|
.expect("Failed to spawn child process");
|
||||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||||
stdin.write_all(input).await?;
|
stdin
|
||||||
|
.write_all(input)
|
||||||
|
.await
|
||||||
|
.expect("Failed to write to stdin");
|
||||||
let res = child.wait_with_output().await;
|
let res = child.wait_with_output().await;
|
||||||
//println!("{:?}", res);
|
//println!("{:?}", res);
|
||||||
#[allow(clippy::let_and_return)]
|
#[allow(clippy::let_and_return)]
|
||||||
|
|
@ -74,13 +78,17 @@ impl CommandRunner for StdCommandRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SetuidCommandRunner<U: AsRef<str>> {
|
pub struct SetuidCommandRunner<'a, U: AsRef<str>, C: CommandRunner> {
|
||||||
|
command_runner: &'a C,
|
||||||
user_name: U,
|
user_name: U,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<U: AsRef<str>> SetuidCommandRunner<U> {
|
impl<'a, U: AsRef<str>, C: CommandRunner> SetuidCommandRunner<'a, U, C> {
|
||||||
pub const fn new(user_name: U) -> Self {
|
pub fn new(user_name: U, command_runner: &'a C) -> Self {
|
||||||
Self { user_name }
|
SetuidCommandRunner {
|
||||||
|
command_runner,
|
||||||
|
user_name,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,13 +121,13 @@ impl Drop for TempSetEnv<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl<U: AsRef<str>> CommandRunner for SetuidCommandRunner<U> {
|
impl<U: AsRef<str>, C: CommandRunner> CommandRunner for SetuidCommandRunner<'_, U, C> {
|
||||||
async fn run(&self, program: &str, args: &[&OsStr], input: &[u8]) -> IoResult<Output> {
|
async fn run(&self, program: &str, args: &[&OsStr], input: &[u8]) -> IoResult<Output> {
|
||||||
let uid = get_user_by_name(self.user_name.as_ref())
|
let uid = get_user_by_name(self.user_name.as_ref())
|
||||||
.expect("User does not exist")
|
.expect("User does not exist")
|
||||||
.uid();
|
.uid();
|
||||||
let set_home = TempSetEnv::new("HOME", format!("/home/{}", self.user_name.as_ref()));
|
let set_home = TempSetEnv::new("HOME", format!("/home/{}", self.user_name.as_ref()));
|
||||||
let set_dbus = TempSetEnv::new("XDG_RUNTIME_DIR", format!("/run/user/{uid}"));
|
let set_dbus = TempSetEnv::new("XDG_RUNTIME_DIR", format!("/run/user/{}", uid));
|
||||||
//println!("{} {:?}", program, args);
|
//println!("{} {:?}", program, args);
|
||||||
let mut child = Command::new(program)
|
let mut child = Command::new(program)
|
||||||
.args(args)
|
.args(args)
|
||||||
|
|
@ -143,6 +151,42 @@ impl<U: AsRef<str>> CommandRunner for SetuidCommandRunner<U> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SuCommandRunner<'a, C>
|
||||||
|
where
|
||||||
|
C: CommandRunner,
|
||||||
|
{
|
||||||
|
command_runner: &'a C,
|
||||||
|
user_name: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, C> SuCommandRunner<'a, C>
|
||||||
|
where
|
||||||
|
C: 'a + CommandRunner,
|
||||||
|
{
|
||||||
|
pub fn new(user_name: &'a str, command_runner: &'a C) -> SuCommandRunner<'a, C> {
|
||||||
|
SuCommandRunner {
|
||||||
|
command_runner,
|
||||||
|
user_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Su doesn't set XDG_RUNTIME_DIR
|
||||||
|
// https://github.com/systemd/systemd/blob/master/src/login/pam_systemd.c#L439
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl<'a, C> CommandRunner for SuCommandRunner<'a, C>
|
||||||
|
where
|
||||||
|
C: 'a + CommandRunner,
|
||||||
|
{
|
||||||
|
async fn run(&self, program: &str, args: &[&OsStr], input: &[u8]) -> IoResult<Output> {
|
||||||
|
let raw_new_args = [self.user_name, "-s", "/usr/bin/env", "--", program];
|
||||||
|
let mut new_args: Vec<&OsStr> = raw_new_args.iter().map(AsRef::as_ref).collect();
|
||||||
|
new_args.extend_from_slice(args);
|
||||||
|
self.command_runner.run("su", &new_args, input).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::args;
|
use crate::args;
|
||||||
|
|
@ -163,10 +207,10 @@ mod test {
|
||||||
loop {
|
loop {
|
||||||
futures_util::select! {
|
futures_util::select! {
|
||||||
_ = res => {},
|
_ = res => {},
|
||||||
_ = ps => assert!(start.elapsed().as_millis() < 1000),
|
_ = ps => assert!((Instant::now() - start).as_millis() < 1000),
|
||||||
complete => break,
|
complete => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@
|
||||||
clippy::cargo_common_metadata,
|
clippy::cargo_common_metadata,
|
||||||
clippy::future_not_send,
|
clippy::future_not_send,
|
||||||
clippy::missing_errors_doc,
|
clippy::missing_errors_doc,
|
||||||
clippy::missing_panics_doc,
|
|
||||||
clippy::module_name_repetitions,
|
clippy::module_name_repetitions,
|
||||||
rustdoc::all,
|
rustdoc::all,
|
||||||
missing_docs,
|
missing_docs,
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ pub trait Policy {
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn path_for_data(name: impl Display) -> PathBuf {
|
fn path_for_data(name: impl Display) -> PathBuf {
|
||||||
Path::new("/root/data").join(format!("_{name}"))
|
Path::new("/root/data").join(format!("_{}", name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,7 +121,7 @@ impl<P, D: AsRef<str>> ResourceLocator<KeyAndCertBundle<D>> for DefaultLocator<P
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<POLICY, P: AsRef<Path>> ResourceLocator<File<P>> for DefaultLocator<POLICY> {
|
impl<'a, POLICY, P: AsRef<Path>> ResourceLocator<File<P>> for DefaultLocator<POLICY> {
|
||||||
type Prerequisites = Dir<PathBuf>;
|
type Prerequisites = Dir<PathBuf>;
|
||||||
fn locate(resource: &File<P>) -> (<File<P> as Resource>::Artifact, Self::Prerequisites) {
|
fn locate(resource: &File<P>) -> (<File<P> as Resource>::Artifact, Self::Prerequisites) {
|
||||||
((), Dir(resource.0.as_ref().parent().unwrap().into()))
|
((), Dir(resource.0.as_ref().parent().unwrap().into()))
|
||||||
|
|
@ -370,7 +370,7 @@ impl<D: Clone + AsRef<str>, P: Policy> ResourceLocator<PhpFpmPool<D>> for Defaul
|
||||||
php_version, user.0
|
php_version, user.0
|
||||||
)),
|
)),
|
||||||
user,
|
user,
|
||||||
ServiceNameArtifact(format!("php{php_version}-fpm")),
|
ServiceNameArtifact(format!("php{}-fpm", php_version)),
|
||||||
),
|
),
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,44 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::io::{stderr, Write};
|
use std::io::stderr;
|
||||||
use std::rc::Rc;
|
use std::io::Write;
|
||||||
|
|
||||||
// The log crate defines
|
// The log crate defines
|
||||||
// 1 - Error, 2 - Warn, 3 - Info, 4 - Debug, 5 - Trace
|
// 1 - Error, 2 - Warn, 3 - Info, 4 - Debug, 5 - Trace
|
||||||
pub type Level = usize;
|
pub type Level = usize;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Entry<S>(pub Level, pub S);
|
pub struct Entry<S>(pub Level, pub S);
|
||||||
|
|
||||||
pub trait Logger {
|
pub trait Logger {
|
||||||
fn write(&self, level: Level, msg: &str);
|
fn write<S: AsRef<str> + Into<String>>(&self, level: Level, msg: S)
|
||||||
fn writeln(&self, level: Level, msg: &str);
|
where
|
||||||
fn info(&self, msg: &str) {
|
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);
|
self.writeln(3, msg);
|
||||||
}
|
}
|
||||||
fn debug(&self, msg: &str) {
|
fn debug<S: AsRef<str> + Into<String>>(&self, msg: S)
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
self.writeln(4, msg);
|
self.writeln(4, msg);
|
||||||
}
|
}
|
||||||
fn trace(&self, msg: &str) {
|
fn trace<S: AsRef<str> + Into<String>>(&self, msg: S)
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
self.writeln(5, msg);
|
self.writeln(5, msg);
|
||||||
}
|
}
|
||||||
fn put<'a>(&self, entries: impl IntoIterator<Item = Entry<&'a str>>) -> usize {
|
fn put<S: AsRef<str> + Into<String>>(&self, entries: impl IntoIterator<Item = Entry<S>>) -> usize
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
let mut c = 0;
|
let mut c = 0;
|
||||||
for item in entries {
|
for item in entries {
|
||||||
self.writeln(item.0, item.1);
|
self.writeln(item.0, item.1);
|
||||||
|
|
@ -38,21 +54,21 @@ pub struct StdErrLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Logger for StdErrLogger {
|
impl Logger for StdErrLogger {
|
||||||
fn write(&self, _level: Level, msg: &str) {
|
fn write<S: AsRef<str> + Into<String>>(&self, _level: Level, msg: S) {
|
||||||
*self.line_started.borrow_mut() = true;
|
*self.line_started.borrow_mut() = true;
|
||||||
write!(&mut stderr(), "{msg}").expect("Could not write to stderr");
|
write!(&mut stderr(), "{}", msg.as_ref()).unwrap();
|
||||||
}
|
}
|
||||||
fn writeln(&self, _level: Level, msg: &str) {
|
fn writeln<S: AsRef<str> + Into<String>>(&self, _level: Level, msg: S) {
|
||||||
if self.line_started.replace(false) {
|
if self.line_started.replace(false) {
|
||||||
writeln!(&mut stderr()).expect("Could not write to stderr");
|
writeln!(&mut stderr()).unwrap();
|
||||||
}
|
}
|
||||||
writeln!(&mut stderr(), "{msg}").expect("Could not write to stderr");
|
writeln!(&mut stderr(), "{}", msg.as_ref()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Drop for StdErrLogger {
|
impl Drop for StdErrLogger {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if *self.line_started.borrow() {
|
if *self.line_started.borrow() {
|
||||||
writeln!(&mut stderr()).expect("Could not write to stderr");
|
writeln!(&mut stderr()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -70,21 +86,21 @@ impl<'a, L> FilteringLogger<'a, L> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, L: Logger> Logger for FilteringLogger<'a, L> {
|
impl<'a, L: Logger> Logger for FilteringLogger<'a, L> {
|
||||||
fn write(&self, level: Level, msg: &str) {
|
fn write<S: AsRef<str> + Into<String>>(&self, level: Level, str: S) {
|
||||||
if level <= self.max_level {
|
if level <= self.max_level {
|
||||||
self.logger.write(level, msg);
|
self.logger.write(level, str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn writeln(&self, level: Level, msg: &str) {
|
fn writeln<S: AsRef<str> + Into<String>>(&self, level: Level, str: S) {
|
||||||
if level <= self.max_level {
|
if level <= self.max_level {
|
||||||
self.logger.writeln(level, msg);
|
self.logger.writeln(level, str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct StoringLogger {
|
pub struct StoringLogger {
|
||||||
log: Rc<RefCell<(bool, Vec<Entry<String>>)>>,
|
log: RefCell<(bool, Vec<Entry<String>>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoringLogger {
|
impl StoringLogger {
|
||||||
|
|
@ -93,28 +109,30 @@ impl StoringLogger {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn release(self) -> Vec<Entry<String>> {
|
pub fn release(self) -> Vec<Entry<String>> {
|
||||||
Rc::try_unwrap(self.log).unwrap().into_inner().1
|
self.log.into_inner().1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Logger for StoringLogger {
|
impl Logger for StoringLogger {
|
||||||
fn write(&self, level: Level, msg: &str) {
|
fn write<S: AsRef<str> + Into<String>>(&self, level: Level, line: S) {
|
||||||
let mut log = self.log.borrow_mut();
|
let mut log = self.log.borrow_mut();
|
||||||
let entry = if log.0 {
|
let entry = if log.0 {
|
||||||
log.1.pop().map(|e| Entry(min(e.0, level), e.1 + msg))
|
log
|
||||||
|
.1
|
||||||
|
.pop()
|
||||||
|
.map(|e| Entry(min(e.0, level), e.1 + line.as_ref()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
.unwrap_or_else(|| Entry(level, msg.to_string()));
|
.unwrap_or_else(|| Entry(level, line.into()));
|
||||||
log.0 = true;
|
log.0 = true;
|
||||||
log.1.push(entry);
|
log.1.push(entry);
|
||||||
}
|
}
|
||||||
fn writeln(&self, level: Level, msg: &str) {
|
fn writeln<S: AsRef<str> + Into<String>>(&self, level: Level, line: S) {
|
||||||
let mut log = self.log.borrow_mut();
|
let mut log = self.log.borrow_mut();
|
||||||
log.0 = false;
|
log.0 = false;
|
||||||
log.1.push(Entry(level, msg.to_string()));
|
log.1.push(Entry(level, line.into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ impl<D> Resource for KeyAndCertBundle<D> {
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct File<P>(pub P, pub String);
|
pub struct File<P>(pub P, pub String);
|
||||||
impl<P> Resource for File<P> {
|
impl<'a, P> Resource for File<P> {
|
||||||
type Artifact = ();
|
type Artifact = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,7 +228,7 @@ impl<D> Resource for Cron<D> {
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
pub trait FromResource<R> {
|
pub trait FromResource<R> {
|
||||||
fn from_resource(from: &Rc<R>) -> (Self, Weak<R>)
|
fn from_resource(from: R) -> (Self, Weak<R>)
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +250,8 @@ macro_rules! default_resources {
|
||||||
}
|
}
|
||||||
|
|
||||||
$(impl<'a, D> FromResource<$type> for DefaultResources<'a, D> {
|
$(impl<'a, D> FromResource<$type> for DefaultResources<'a, D> {
|
||||||
fn from_resource(inner: &Rc<$type>) -> (Self, Weak<$type>) {
|
fn from_resource(from: $type) -> (Self, Weak<$type>) {
|
||||||
|
let inner = Rc::new(from);
|
||||||
(Self::$name(Rc::clone(&inner)), Rc::downgrade(&inner))
|
(Self::$name(Rc::clone(&inner)), Rc::downgrade(&inner))
|
||||||
}
|
}
|
||||||
})*
|
})*
|
||||||
|
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
use super::{Add, AddResult, AddableResource};
|
|
||||||
use crate::async_utils::sleep;
|
|
||||||
use crate::resources::{FromArtifact, FromResource};
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use futures_util::future::{FutureExt, Shared};
|
|
||||||
use slog::{trace, Logger};
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::future::Future;
|
|
||||||
use std::hash::Hash;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
type ResourceCache<Rs, As> = HashMap<Rs, Shared<Pin<Box<dyn Future<Output = (As, bool)>>>>>;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Cache<I, Rs, As> {
|
|
||||||
resources: RefCell<ResourceCache<Rs, As>>,
|
|
||||||
inner: Rc<I>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I, Rs, As> Cache<I, Rs, As> {
|
|
||||||
pub fn new(inner: I) -> Self {
|
|
||||||
Self {
|
|
||||||
resources: RefCell::default(),
|
|
||||||
inner: Rc::new(inner),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
impl<R: AddableResource + Debug, I, Rs, As> Add<R> for Cache<I, Rs, As>
|
|
||||||
where
|
|
||||||
Rs: Hash + Eq + 'static + FromResource<R>,
|
|
||||||
As: 'static + FromArtifact<R> + Clone,
|
|
||||||
I: 'static + Add<R>,
|
|
||||||
{
|
|
||||||
// FIXME: https://github.com/rust-lang/rust-clippy/issues/6353
|
|
||||||
#[allow(clippy::await_holding_refcell_ref)]
|
|
||||||
async fn add(&self, logger: &Rc<Logger>, resource: Rc<R>, force_run: bool) -> AddResult<R> {
|
|
||||||
let (storable_resource, weak_resource) = Rs::from_resource(&resource);
|
|
||||||
let mut resources = self.resources.borrow_mut();
|
|
||||||
let result = if let Some(future) = resources.remove(&storable_resource) {
|
|
||||||
assert!(
|
|
||||||
!force_run,
|
|
||||||
"Forcing to run an already-added resource is a logical error"
|
|
||||||
);
|
|
||||||
resources.insert(storable_resource, future.clone());
|
|
||||||
drop(resources);
|
|
||||||
trace!(logger, "Resource already added");
|
|
||||||
Ok(future.await)
|
|
||||||
} else {
|
|
||||||
let inner_weak = Rc::downgrade(&self.inner);
|
|
||||||
let logger_weak = Rc::downgrade(logger);
|
|
||||||
let future = Box::pin(async move {
|
|
||||||
let inner = inner_weak.upgrade().expect("Dangling!");
|
|
||||||
let logger = logger_weak.upgrade().expect("Dangling!");
|
|
||||||
let resource = weak_resource.upgrade().expect("Dangling!");
|
|
||||||
let result = inner.add(&logger, Rc::clone(&resource), force_run).await;
|
|
||||||
|
|
||||||
// Need to convert Box<Error> to String for Clone for Shared
|
|
||||||
result
|
|
||||||
.map(|(t, did_run)| (As::from_artifact(t), did_run))
|
|
||||||
.map_err(|e| e.to_string())
|
|
||||||
})
|
|
||||||
.shared();
|
|
||||||
let future_clone = future.clone();
|
|
||||||
resources.insert(
|
|
||||||
storable_resource,
|
|
||||||
(Box::pin(async move {
|
|
||||||
let result = future_clone.await;
|
|
||||||
if result.is_err() {
|
|
||||||
// Step back to give the initial caller time to handle the error before unwrapping
|
|
||||||
sleep(Duration::from_millis(0)).await;
|
|
||||||
}
|
|
||||||
result.unwrap()
|
|
||||||
}) as Pin<Box<dyn Future<Output = (As, bool)>>>)
|
|
||||||
.shared(),
|
|
||||||
);
|
|
||||||
drop(resources);
|
|
||||||
let result = future.await;
|
|
||||||
result.map_err(std::convert::Into::into)
|
|
||||||
};
|
|
||||||
result.map(|(t, did_run)| (t.into_artifact(), did_run))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
204
src/setup/core.rs
Normal file
204
src/setup/core.rs
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
use super::runnable::Runnable;
|
||||||
|
use super::setup::Setup;
|
||||||
|
use super::util::{AddableResource, InternalAddResult};
|
||||||
|
use super::SymbolRunner;
|
||||||
|
use crate::async_utils::join;
|
||||||
|
use crate::loggers::{Logger, StoringLogger};
|
||||||
|
use crate::resources::{FromArtifact, FromResource};
|
||||||
|
use crate::symbols::Symbol;
|
||||||
|
use crate::to_artifact::ToArtifact;
|
||||||
|
use crate::{ImplementationBuilder, ResourceLocator};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
pub trait AddGeneric<X: ToArtifact> {
|
||||||
|
async fn add_generic(&self, x: X) -> InternalAddResult<X>;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! add_generic {
|
||||||
|
( $($name:ident)* ) => (
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
impl<SR: 'static, _L: 'static, _B: 'static, Rs: 'static + Hash + Eq, As: 'static + Clone, $($name: AddableResource,)*>
|
||||||
|
AddGeneric<($($name,)*)> for Setup<SR, _L, _B, Rs, As>
|
||||||
|
where
|
||||||
|
$(
|
||||||
|
RegularSetupCore<SR, _L, _B>: SetupCore<$name, Self>,
|
||||||
|
As: FromArtifact<$name>,
|
||||||
|
Rs: FromResource<$name>,
|
||||||
|
$name::Artifact: Clone
|
||||||
|
),*
|
||||||
|
{
|
||||||
|
#[allow(unused, clippy::shadow_unrelated)]
|
||||||
|
async fn add_generic(&self, ($($name,)*): ($($name,)*)) -> Result<(StoringLogger, ($($name::Artifact,)*), bool), (StoringLogger, Box<dyn Error>)>
|
||||||
|
{
|
||||||
|
let ($($name,)*) = join!($(self.add($name, false),)*);
|
||||||
|
let logger = StoringLogger::default();
|
||||||
|
let mut did_run_any = false;
|
||||||
|
$(
|
||||||
|
let (log, artifact, did_run) = $name?;
|
||||||
|
logger.put(log.release());
|
||||||
|
did_run_any = did_run_any || did_run;
|
||||||
|
let $name = artifact;
|
||||||
|
)*
|
||||||
|
Ok((logger, ($($name,)*), did_run_any))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for_each_tuple!(add_generic);
|
||||||
|
|
||||||
|
// This is for self-referential T
|
||||||
|
// FIXME: Wait for specialization
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl<
|
||||||
|
SR: 'static + SymbolRunner,
|
||||||
|
T: AddableResource,
|
||||||
|
Rs: 'static + Hash + Eq + FromResource<T>,
|
||||||
|
As: 'static + FromArtifact<T> + Clone,
|
||||||
|
L: 'static + ResourceLocator<T, Prerequisites = Option<T>>,
|
||||||
|
B: 'static + ImplementationBuilder<T>,
|
||||||
|
> AddGeneric<Option<T>> for Setup<SR, L, B, Rs, As>
|
||||||
|
where
|
||||||
|
<B as ImplementationBuilder<T>>::Implementation: Runnable + Debug,
|
||||||
|
Self: AddGeneric<B::Prerequisites>,
|
||||||
|
T::Artifact: Clone,
|
||||||
|
{
|
||||||
|
async fn add_generic(&self, r: Option<T>) -> InternalAddResult<Option<T>> {
|
||||||
|
Ok(match r {
|
||||||
|
Some(r) => {
|
||||||
|
let (logger, result, did_run) = self.add(r, false).await?;
|
||||||
|
(logger, Some(result), did_run)
|
||||||
|
}
|
||||||
|
None => (StoringLogger::default(), None, false),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl<
|
||||||
|
T: AddableResource,
|
||||||
|
Rs: 'static + Hash + Eq + FromResource<T>,
|
||||||
|
As: 'static + FromArtifact<T> + Clone,
|
||||||
|
SR: 'static,
|
||||||
|
L: 'static,
|
||||||
|
B: 'static,
|
||||||
|
> AddGeneric<T> for Setup<SR, L, B, Rs, As>
|
||||||
|
where
|
||||||
|
T::Artifact: Clone,
|
||||||
|
RegularSetupCore<SR, L, B>: 'static + SetupCore<T, Self>,
|
||||||
|
{
|
||||||
|
async fn add_generic(&self, r: T) -> InternalAddResult<T> {
|
||||||
|
self.add(r, false).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
pub trait SetupCore<R: AddableResource, S> {
|
||||||
|
async fn add<RR: AsRef<R>>(
|
||||||
|
&self,
|
||||||
|
setup: &S,
|
||||||
|
resource: RR,
|
||||||
|
force_run: bool,
|
||||||
|
) -> InternalAddResult<R>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RegularSetupCore<SR, L, B> {
|
||||||
|
symbol_runner: SR,
|
||||||
|
phantom: PhantomData<(L, B)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SR, L, B> RegularSetupCore<SR, L, B> {
|
||||||
|
pub fn new(symbol_runner: SR) -> Self {
|
||||||
|
Self {
|
||||||
|
symbol_runner,
|
||||||
|
phantom: PhantomData::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl<SR: SymbolRunner, L, B, R: AddableResource, S> SetupCore<R, S> for RegularSetupCore<SR, L, B>
|
||||||
|
where
|
||||||
|
B: ImplementationBuilder<R>,
|
||||||
|
<B as ImplementationBuilder<R>>::Implementation: Runnable + Debug,
|
||||||
|
L: ResourceLocator<R>,
|
||||||
|
S: AddGeneric<B::Prerequisites> + AddGeneric<<L as ResourceLocator<R>>::Prerequisites>,
|
||||||
|
{
|
||||||
|
async fn add<RR: AsRef<R>>(
|
||||||
|
&self,
|
||||||
|
setup: &S,
|
||||||
|
resource: RR,
|
||||||
|
force_run: bool,
|
||||||
|
) -> InternalAddResult<R> {
|
||||||
|
let resource = resource.as_ref();
|
||||||
|
let logger = StoringLogger::new();
|
||||||
|
logger.write(4, format!("Adding {:?} ... ", resource));
|
||||||
|
logger.write(4, format!("(force_run is {})", force_run));
|
||||||
|
let (location, location_prereqs) = L::locate(resource);
|
||||||
|
logger.trace(format!("Adding location prereqs for {:?}", resource));
|
||||||
|
let result = setup.add_generic(location_prereqs).await;
|
||||||
|
if let Err((log, e)) = result {
|
||||||
|
logger.put(log.release());
|
||||||
|
return Err((logger, e));
|
||||||
|
}
|
||||||
|
let (location_prereq_logger, _, location_prereqs_did_run) = result.unwrap();
|
||||||
|
logger.put(location_prereq_logger.release());
|
||||||
|
logger.trace(format!(
|
||||||
|
"Location prereqs for {:?} did_run: {}",
|
||||||
|
resource, location_prereqs_did_run
|
||||||
|
));
|
||||||
|
logger.trace(format!("Adding implementation prereqs for {:?}", resource));
|
||||||
|
let result = setup.add_generic(B::prerequisites(resource)).await;
|
||||||
|
if let Err((log, e)) = result {
|
||||||
|
logger.put(log.release());
|
||||||
|
return Err((logger, e));
|
||||||
|
}
|
||||||
|
let (impl_prereq_logger, prereqs, prereqs_did_run) = result.unwrap();
|
||||||
|
logger.put(impl_prereq_logger.release());
|
||||||
|
logger.trace(format!(
|
||||||
|
"Implementation prereqs for {:?} did_run: {}",
|
||||||
|
resource, prereqs_did_run
|
||||||
|
));
|
||||||
|
logger.trace(format!("Running implementation for {:?}", resource));
|
||||||
|
let implementation = B::create(resource, &location, prereqs);
|
||||||
|
let did_run_result = implementation
|
||||||
|
.run(
|
||||||
|
&self.symbol_runner,
|
||||||
|
&logger,
|
||||||
|
force_run || location_prereqs_did_run || prereqs_did_run,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match did_run_result {
|
||||||
|
Ok(did_run) => {
|
||||||
|
logger.write(4, "done.");
|
||||||
|
Ok((logger, location, did_run))
|
||||||
|
}
|
||||||
|
Err(e) => Err((logger, e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl<SR: SymbolRunner, L, B> SymbolRunner for RegularSetupCore<SR, L, B> {
|
||||||
|
async fn run_symbol<S: Symbol + Debug, LOG: Logger>(
|
||||||
|
&self,
|
||||||
|
symbol: &S,
|
||||||
|
parent_logger: &LOG,
|
||||||
|
force: bool,
|
||||||
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
|
let logger = StoringLogger::new();
|
||||||
|
logger.write(4, format!("Directly running {:?} ...", symbol));
|
||||||
|
let result = self.symbol_runner.run_symbol(symbol, &logger, force).await;
|
||||||
|
logger.write(4, "done.");
|
||||||
|
let max_level = if result.is_err() { 5 } else { 3 };
|
||||||
|
parent_logger.put(logger.release().into_iter().filter(|e| e.0 <= max_level));
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
388
src/setup/mod.rs
388
src/setup/mod.rs
|
|
@ -1,388 +1,12 @@
|
||||||
mod realizer;
|
mod core;
|
||||||
mod symbol_runner;
|
|
||||||
mod util;
|
mod util;
|
||||||
|
pub use util::{AddResult, AddableResource};
|
||||||
|
mod symbol_runner;
|
||||||
pub use symbol_runner::{
|
pub use symbol_runner::{
|
||||||
DelayingSymbolRunner, DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner,
|
DelayingSymbolRunner, DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner,
|
||||||
SymbolRunner,
|
SymbolRunner,
|
||||||
};
|
};
|
||||||
mod cache;
|
|
||||||
mod runnable;
|
mod runnable;
|
||||||
use crate::loggers::Logger;
|
#[allow(clippy::module_inception)]
|
||||||
use crate::resources::{DefaultArtifacts, DefaultResources, FromArtifact, FromResource};
|
mod setup;
|
||||||
use crate::symbols::Symbol;
|
pub use setup::SetupFacade as Setup;
|
||||||
use crate::{DefaultBuilder, DefaultLocator};
|
|
||||||
use crate::{ImplementationBuilder, ResourceLocator};
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use cache::Cache;
|
|
||||||
use realizer::Realizer;
|
|
||||||
use runnable::Runnable;
|
|
||||||
use slog::o;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::hash::Hash;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use util::{Add, AddGeneric, AddResult, AddableResource, Recorder};
|
|
||||||
|
|
||||||
// Necessary for the recursive type
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ActualSetup<SR, L, B, Rs, As>(Cache<Realizer<SR, L, B, Self>, Rs, As>);
|
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
impl<T: AddableResource + Debug, SR, L, B, Rs, As> Add<T> for ActualSetup<SR, L, B, Rs, As>
|
|
||||||
where
|
|
||||||
Cache<Realizer<SR, L, B, Self>, Rs, As>: Add<T>,
|
|
||||||
{
|
|
||||||
async fn add(&self, logger: &Rc<slog::Logger>, r: Rc<T>, force_run: bool) -> AddResult<T> {
|
|
||||||
self.0.add(logger, r, force_run).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is for self-referential T
|
|
||||||
// FIXME: Wait for specialization
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
impl<
|
|
||||||
SR: 'static + SymbolRunner,
|
|
||||||
T: AddableResource,
|
|
||||||
Rs: 'static + Hash + Eq + FromResource<T>,
|
|
||||||
As: 'static + FromArtifact<T> + Clone,
|
|
||||||
L: 'static + ResourceLocator<T, Prerequisites = Option<T>>,
|
|
||||||
B: 'static + ImplementationBuilder<T>,
|
|
||||||
> AddGeneric<Option<T>> for ActualSetup<SR, L, B, Rs, As>
|
|
||||||
where
|
|
||||||
<B as ImplementationBuilder<T>>::Implementation: Runnable + Debug,
|
|
||||||
Self: AddGeneric<B::Prerequisites>,
|
|
||||||
T::Artifact: Clone,
|
|
||||||
// These bounds cannot be replaced by
|
|
||||||
// `Realizer<SR, L, B>: Add<T, Self>`
|
|
||||||
// because the prerequisites are Option<T>, too, and thus this would
|
|
||||||
// require AddGeneric<Option<T>> to already be implemented
|
|
||||||
{
|
|
||||||
async fn add_generic(
|
|
||||||
&self,
|
|
||||||
logger: &Rc<slog::Logger>,
|
|
||||||
r: Option<T>,
|
|
||||||
force_run: bool,
|
|
||||||
) -> AddResult<Option<T>> {
|
|
||||||
Ok(match r {
|
|
||||||
Some(r) => {
|
|
||||||
let (result, did_run) = self.add(logger, Rc::new(r), force_run).await?;
|
|
||||||
(Some(result), did_run)
|
|
||||||
}
|
|
||||||
None => (None, false),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Setup<
|
|
||||||
SR,
|
|
||||||
LOG,
|
|
||||||
L = DefaultLocator,
|
|
||||||
B = DefaultBuilder,
|
|
||||||
Rs = DefaultResources<'static, &'static str>,
|
|
||||||
As = DefaultArtifacts<'static, &'static str>,
|
|
||||||
>(LOG, Rc<SR>, Rc<ActualSetup<SR, L, B, Rs, As>>);
|
|
||||||
|
|
||||||
impl<SR, LOG> Setup<SR, LOG> {
|
|
||||||
pub fn new(symbol_runner: SR, logger: LOG) -> Self {
|
|
||||||
Self::new_with(symbol_runner, logger)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<L, B, As, SR, LOG, Rs: Hash + Eq> Setup<SR, LOG, L, B, Rs, As> {
|
|
||||||
pub fn new_with(symbol_runner: SR, logger: LOG) -> Self {
|
|
||||||
let runner = Rc::new(symbol_runner);
|
|
||||||
let inner = Rc::new_cyclic(|inner| {
|
|
||||||
ActualSetup(Cache::new(Realizer::new(Rc::clone(&runner), inner.clone())))
|
|
||||||
});
|
|
||||||
Self(logger, runner, inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<
|
|
||||||
SR: 'static,
|
|
||||||
LOG: 'static + Logger,
|
|
||||||
L: 'static,
|
|
||||||
B: 'static,
|
|
||||||
Rs: 'static + Eq + Hash,
|
|
||||||
As: 'static,
|
|
||||||
> Setup<SR, LOG, L, B, Rs, As>
|
|
||||||
{
|
|
||||||
pub async fn add_force<R: AddableResource>(&self, resource: R, force_run: bool) -> AddResult<R>
|
|
||||||
where
|
|
||||||
Rs: FromResource<R>,
|
|
||||||
As: FromArtifact<R> + Clone,
|
|
||||||
ActualSetup<SR, L, B, Rs, As>: Add<R>,
|
|
||||||
{
|
|
||||||
let recorder = Recorder::default();
|
|
||||||
let result = {
|
|
||||||
let log = Rc::new(slog::Logger::root(recorder.clone(), o!()));
|
|
||||||
self.2.add(&log, Rc::new(resource), force_run).await
|
|
||||||
};
|
|
||||||
self.log_result(recorder, result.as_ref().map(|(_, did_run)| *did_run));
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add<R: AddableResource>(&self, resource: R) -> AddResult<R>
|
|
||||||
where
|
|
||||||
Rs: FromResource<R>,
|
|
||||||
As: FromArtifact<R> + Clone,
|
|
||||||
R::Artifact: Clone,
|
|
||||||
ActualSetup<SR, L, B, Rs, As>: Add<R>,
|
|
||||||
{
|
|
||||||
self.add_force(resource, false).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run_symbol<S: Symbol + Debug>(
|
|
||||||
&self,
|
|
||||||
symbol: S,
|
|
||||||
force: bool,
|
|
||||||
) -> Result<bool, Box<dyn Error>>
|
|
||||||
where
|
|
||||||
SR: SymbolRunner,
|
|
||||||
{
|
|
||||||
let recorder = Recorder::default();
|
|
||||||
let result = {
|
|
||||||
let log = Rc::new(slog::Logger::root(
|
|
||||||
recorder.clone(),
|
|
||||||
o!("symbol" => format!("{symbol:?}")),
|
|
||||||
));
|
|
||||||
self.1.run_symbol(&symbol, &log, force).await
|
|
||||||
};
|
|
||||||
self.log_result(recorder, result.as_ref().copied());
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_result(&self, recorder: Recorder, result: Result<bool, &Box<dyn Error>>) {
|
|
||||||
let log = match result {
|
|
||||||
Ok(false) => String::new(),
|
|
||||||
Ok(true) => recorder.into_string(slog::Level::Info),
|
|
||||||
Err(e) => recorder.into_string(slog::Level::Trace),
|
|
||||||
};
|
|
||||||
if log.is_empty() {
|
|
||||||
self.0.write(3, ".");
|
|
||||||
} else {
|
|
||||||
self.0.writeln(3, &log);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::SymbolRunner;
|
|
||||||
use crate::async_utils::run;
|
|
||||||
use crate::loggers::{Entry, StoringLogger};
|
|
||||||
use crate::resources::{FromArtifact, FromResource, Resource};
|
|
||||||
use crate::symbols::Symbol;
|
|
||||||
use crate::to_artifact::ToArtifact;
|
|
||||||
use crate::{ImplementationBuilder, ResourceLocator, Setup};
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use regex::Regex;
|
|
||||||
use slog::{info, Logger};
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::rc::{Rc, Weak};
|
|
||||||
|
|
||||||
struct TestSymbolRunner {
|
|
||||||
count: Rc<RefCell<usize>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
impl SymbolRunner for TestSymbolRunner {
|
|
||||||
async fn run_symbol<S: Symbol + Debug>(
|
|
||||||
&self,
|
|
||||||
symbol: &S,
|
|
||||||
logger: &Logger,
|
|
||||||
force: bool,
|
|
||||||
) -> Result<bool, Box<dyn Error>> {
|
|
||||||
info!(logger, "run");
|
|
||||||
let run = force || !symbol.target_reached().await?;
|
|
||||||
if run {
|
|
||||||
*self.count.borrow_mut() += 1;
|
|
||||||
}
|
|
||||||
Ok(run)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
struct TestResource<T>(&'static str, T);
|
|
||||||
impl<T> Resource for TestResource<T> {
|
|
||||||
type Artifact = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
|
||||||
enum Resources {
|
|
||||||
A(Rc<TestResource<&'static str>>),
|
|
||||||
B(Rc<TestResource<()>>),
|
|
||||||
}
|
|
||||||
impl FromResource<TestResource<&'static str>> for Resources {
|
|
||||||
fn from_resource(
|
|
||||||
inner: &Rc<TestResource<&'static str>>,
|
|
||||||
) -> (Self, Weak<TestResource<&'static str>>) {
|
|
||||||
(Self::A(Rc::clone(&inner)), Rc::downgrade(&inner))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromResource<TestResource<()>> for Resources {
|
|
||||||
fn from_resource(inner: &Rc<TestResource<()>>) -> (Self, Weak<TestResource<()>>) {
|
|
||||||
(Self::B(Rc::clone(&inner)), Rc::downgrade(&inner))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Artifacts;
|
|
||||||
impl<V> FromArtifact<TestResource<V>> for Artifacts {
|
|
||||||
fn from_artifact(_from: ()) -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
#[allow(clippy::unused_unit)]
|
|
||||||
fn into_artifact(self) -> () {
|
|
||||||
#[allow(clippy::unused_unit)]
|
|
||||||
()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TestResourceLocator;
|
|
||||||
impl<T> ResourceLocator<TestResource<T>> for TestResourceLocator {
|
|
||||||
type Prerequisites = ();
|
|
||||||
fn locate(_resource: &TestResource<T>) -> (<TestResource<T> as ToArtifact>::Artifact, ()) {
|
|
||||||
((), ())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TestImplementationBuilder;
|
|
||||||
impl ImplementationBuilder<TestResource<&'static str>> 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: <Self::Prerequisites as ToArtifact>::Artifact,
|
|
||||||
) -> Self::Implementation {
|
|
||||||
TestSymbol {
|
|
||||||
reached: resource.0.chars().next().unwrap().is_uppercase(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl ImplementationBuilder<TestResource<()>> 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<bool, Box<dyn Error>> {
|
|
||||||
Ok(self.reached)
|
|
||||||
}
|
|
||||||
async fn execute(&self) -> Result<(), Box<dyn Error>> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
fn get_setup() -> (
|
|
||||||
Rc<RefCell<usize>>,
|
|
||||||
Setup<
|
|
||||||
TestSymbolRunner,
|
|
||||||
StoringLogger,
|
|
||||||
TestResourceLocator,
|
|
||||||
TestImplementationBuilder,
|
|
||||||
Resources,
|
|
||||||
Artifacts,
|
|
||||||
>,
|
|
||||||
StoringLogger,
|
|
||||||
) {
|
|
||||||
let count = Rc::new(RefCell::new(0));
|
|
||||||
let runner = TestSymbolRunner {
|
|
||||||
count: Rc::clone(&count),
|
|
||||||
};
|
|
||||||
let logger = StoringLogger::new();
|
|
||||||
(count, Setup::new_with(runner, logger.clone()), logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn correctly_uses_force() {
|
|
||||||
run(async {
|
|
||||||
let (count, setup, _) = get_setup();
|
|
||||||
setup.add(TestResource("A", "b")).await.unwrap();
|
|
||||||
assert_eq!(*count.borrow(), 2);
|
|
||||||
setup.add(TestResource("A", "b")).await.unwrap();
|
|
||||||
assert_eq!(*count.borrow(), 2);
|
|
||||||
|
|
||||||
let (count, setup, _) = get_setup();
|
|
||||||
setup.add(TestResource("A", "B")).await.unwrap();
|
|
||||||
assert_eq!(*count.borrow(), 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn run_reached_symbol() {
|
|
||||||
run(async {
|
|
||||||
let (count, setup, log) = get_setup();
|
|
||||||
let did_run = setup
|
|
||||||
.run_symbol(TestSymbol { reached: true }, false)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
drop(setup);
|
|
||||||
assert!(!did_run);
|
|
||||||
assert_eq!(*count.borrow(), 0);
|
|
||||||
assert_eq!(log.release(), vec![Entry(3, ".".into())]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn run_not_reached_symbol() {
|
|
||||||
run(async {
|
|
||||||
let (count, setup, log) = get_setup();
|
|
||||||
let did_run = setup
|
|
||||||
.run_symbol(TestSymbol { reached: false }, false)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
drop(setup);
|
|
||||||
assert!(did_run);
|
|
||||||
assert_eq!(*count.borrow(), 1);
|
|
||||||
let log = log.release();
|
|
||||||
assert_eq!(log.len(), 1);
|
|
||||||
assert_eq!(log[0].0, 3);
|
|
||||||
let re = Regex::new(r"^symbol: TestSymbol \{ reached: false \}\n \w+ \d{1,2} \d{2}:\d{2}:\d{2}.\d{3} INFO run\n$").unwrap();
|
|
||||||
assert!(re.is_match(&log[0].1));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
use super::{ActualSetup, AddGeneric, Cache, Realizer};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_tuple_add_generic() {
|
|
||||||
let setup = Rc::new_cyclic(|inner| {
|
|
||||||
ActualSetup(Cache::<Realizer<(), (), (), _>, (), ()>::new(
|
|
||||||
Realizer::new(Rc::new(()), inner.clone()),
|
|
||||||
))
|
|
||||||
});
|
|
||||||
run(async {
|
|
||||||
assert!(setup
|
|
||||||
.add_generic(
|
|
||||||
&Rc::new(slog::Logger::root(slog::Discard, slog::o!())),
|
|
||||||
(),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.is_ok());
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
use super::{Add, AddGeneric, AddResult, AddableResource, Runnable, SymbolRunner};
|
|
||||||
use crate::{ImplementationBuilder, ResourceLocator};
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use slog::{debug, o, trace, Logger};
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::rc::{Rc, Weak};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Realizer<SR, L, B, S> {
|
|
||||||
symbol_runner: Rc<SR>,
|
|
||||||
outer: Weak<S>,
|
|
||||||
phantom: PhantomData<(L, B)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<SR, L, B, S> Realizer<SR, L, B, S> {
|
|
||||||
pub fn new(symbol_runner: Rc<SR>, outer: Weak<S>) -> Self {
|
|
||||||
Self {
|
|
||||||
symbol_runner,
|
|
||||||
outer,
|
|
||||||
phantom: PhantomData::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
impl<R, SR, L, B, S> Add<R> for Realizer<SR, L, B, S>
|
|
||||||
where
|
|
||||||
R: AddableResource,
|
|
||||||
SR: SymbolRunner,
|
|
||||||
L: ResourceLocator<R>,
|
|
||||||
B: ImplementationBuilder<R>,
|
|
||||||
<B as ImplementationBuilder<R>>::Implementation: Runnable + Debug,
|
|
||||||
S: AddGeneric<B::Prerequisites> + AddGeneric<<L as ResourceLocator<R>>::Prerequisites>,
|
|
||||||
{
|
|
||||||
async fn add(&self, logger: &Rc<Logger>, resource: Rc<R>, force_run: bool) -> AddResult<R> {
|
|
||||||
let setup = self.outer.upgrade().unwrap();
|
|
||||||
let logger = Rc::new(logger.new(o!("resource" => format!("{resource:?}"))));
|
|
||||||
trace!(logger, "(force_run is {})", force_run);
|
|
||||||
let (location, location_prereqs) = L::locate(&resource);
|
|
||||||
trace!(logger, "Adding location prereqs ...");
|
|
||||||
let (_, location_prereqs_did_run) = (*setup)
|
|
||||||
.add_generic(&logger, location_prereqs, false)
|
|
||||||
.await?;
|
|
||||||
trace!(
|
|
||||||
logger,
|
|
||||||
"Location prereqs did_run: {}",
|
|
||||||
location_prereqs_did_run
|
|
||||||
);
|
|
||||||
|
|
||||||
trace!(logger, "Adding implementation prereqs ...");
|
|
||||||
let (prereqs, prereqs_did_run) = (*setup)
|
|
||||||
.add_generic(&logger, B::prerequisites(&resource), false)
|
|
||||||
.await?;
|
|
||||||
trace!(
|
|
||||||
logger,
|
|
||||||
"Implementation prereqs did_run: {}",
|
|
||||||
prereqs_did_run
|
|
||||||
);
|
|
||||||
|
|
||||||
trace!(logger, "Running implementation ...");
|
|
||||||
let implementation = B::create(&resource, &location, prereqs);
|
|
||||||
let did_run = implementation
|
|
||||||
.run(
|
|
||||||
&*self.symbol_runner,
|
|
||||||
&logger,
|
|
||||||
force_run || location_prereqs_did_run || prereqs_did_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
debug!(logger, "done.");
|
|
||||||
|
|
||||||
Ok((location, did_run))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
use super::SymbolRunner;
|
use super::SymbolRunner;
|
||||||
|
use crate::loggers::Logger;
|
||||||
use crate::symbols::Symbol;
|
use crate::symbols::Symbol;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use slog::Logger;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
// A generalization over symbols and tuples of symbols
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
pub trait Runnable {
|
pub trait Runnable {
|
||||||
async fn run<R: SymbolRunner>(
|
async fn run<R: SymbolRunner, L: Logger>(
|
||||||
&self,
|
&self,
|
||||||
runner: &R,
|
runner: &R,
|
||||||
logger: &Logger,
|
logger: &L,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<bool, Box<dyn Error>>;
|
) -> Result<bool, Box<dyn Error>>;
|
||||||
}
|
}
|
||||||
|
|
@ -22,10 +21,10 @@ impl<S> Runnable for S
|
||||||
where
|
where
|
||||||
Self: Symbol + Debug,
|
Self: Symbol + Debug,
|
||||||
{
|
{
|
||||||
async fn run<R: SymbolRunner>(
|
async fn run<R: SymbolRunner, L: Logger>(
|
||||||
&self,
|
&self,
|
||||||
runner: &R,
|
runner: &R,
|
||||||
logger: &Logger,
|
logger: &L,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<bool, Box<dyn Error>> {
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
runner.run_symbol(self, logger, force).await
|
runner.run_symbol(self, logger, force).await
|
||||||
|
|
@ -39,7 +38,7 @@ macro_rules! runnable_for_tuple {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
impl<$($name: Symbol + Debug,)*> Runnable for ($($name,)*) {
|
impl<$($name: Symbol + Debug,)*> Runnable for ($($name,)*) {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
async fn run<_R: SymbolRunner>(&self, runner: &_R, logger: &Logger, force: bool) -> Result<bool, Box<dyn Error>> {
|
async fn run<_R: SymbolRunner, _L: Logger>(&self, runner: &_R, logger: &_L, force: bool) -> Result<bool, Box<dyn Error>> {
|
||||||
let ($($name,)*) = self;
|
let ($($name,)*) = self;
|
||||||
let mut result = false;
|
let mut result = false;
|
||||||
$(result = runner.run_symbol($name, logger, force || result).await? || result;)*
|
$(result = runner.run_symbol($name, logger, force || result).await? || result;)*
|
||||||
|
|
@ -55,10 +54,10 @@ for_each_tuple!(runnable_for_tuple);
|
||||||
mod test {
|
mod test {
|
||||||
use super::Runnable;
|
use super::Runnable;
|
||||||
use crate::async_utils::run;
|
use crate::async_utils::run;
|
||||||
|
use crate::loggers::{Logger, StoringLogger};
|
||||||
use crate::symbols::Symbol;
|
use crate::symbols::Symbol;
|
||||||
use crate::SymbolRunner;
|
use crate::SymbolRunner;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use slog::{o, Discard, Logger};
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
@ -117,10 +116,10 @@ mod test {
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl SymbolRunner for TestSymbolRunner {
|
impl SymbolRunner for TestSymbolRunner {
|
||||||
async fn run_symbol<S: Symbol + Debug>(
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||||
&self,
|
&self,
|
||||||
symbol: &S,
|
symbol: &S,
|
||||||
_logger: &Logger,
|
_logger: &L,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<bool, Box<dyn Error>> {
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
let run = force || !symbol.target_reached().await?;
|
let run = force || !symbol.target_reached().await?;
|
||||||
|
|
@ -136,7 +135,7 @@ mod test {
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> (Rc<RefCell<usize>>, Result<bool, Box<dyn Error>>) {
|
) -> (Rc<RefCell<usize>>, Result<bool, Box<dyn Error>>) {
|
||||||
let (count, runner) = get_runner();
|
let (count, runner) = get_runner();
|
||||||
let res = run(runnable.run(&runner, &Logger::root(Discard, o!()), force));
|
let res = run(runnable.run(&runner, &StoringLogger::new(), force));
|
||||||
(count, res)
|
(count, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
338
src/setup/setup.rs
Normal file
338
src/setup/setup.rs
Normal file
|
|
@ -0,0 +1,338 @@
|
||||||
|
use super::core::{RegularSetupCore, SetupCore};
|
||||||
|
use super::runnable::Runnable;
|
||||||
|
use super::util::{AddResult, AddableResource, InternalAddResult};
|
||||||
|
use super::SymbolRunner;
|
||||||
|
use crate::async_utils::sleep;
|
||||||
|
use crate::loggers::{Logger, StoringLogger};
|
||||||
|
use crate::resources::{DefaultArtifacts, DefaultResources, FromArtifact, FromResource};
|
||||||
|
use crate::{DefaultBuilder, DefaultLocator};
|
||||||
|
use futures_util::future::{FutureExt, Shared};
|
||||||
|
use std::cell::{RefCell, RefMut};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
type Cache<Rs, As> = HashMap<Rs, Shared<Pin<Box<dyn Future<Output = (As, bool)>>>>>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SetupInner<CORE, Rs, As> {
|
||||||
|
core: CORE,
|
||||||
|
resources: RefCell<Cache<Rs, As>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Setup<SR, L, B, Rs, As>(Rc<SetupInner<RegularSetupCore<SR, L, B>, Rs, As>>);
|
||||||
|
|
||||||
|
impl<L: 'static, B: 'static, SR: 'static, Rs: Hash + Eq + 'static, As: 'static>
|
||||||
|
Setup<SR, L, B, Rs, As>
|
||||||
|
{
|
||||||
|
fn borrow_resources(&self) -> RefMut<'_, Cache<Rs, As>> {
|
||||||
|
self.0.resources.borrow_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: https://github.com/rust-lang/rust-clippy/issues/6353
|
||||||
|
#[allow(clippy::await_holding_refcell_ref)]
|
||||||
|
pub async fn add<R: AddableResource>(&self, resource: R, force_run: bool) -> InternalAddResult<R>
|
||||||
|
where
|
||||||
|
Rs: FromResource<R>,
|
||||||
|
As: FromArtifact<R> + Clone,
|
||||||
|
R::Artifact: Clone,
|
||||||
|
RegularSetupCore<SR, L, B>: SetupCore<R, Self>,
|
||||||
|
{
|
||||||
|
let (storable_resource, weak_resource) = Rs::from_resource(resource);
|
||||||
|
let mut resources = self.borrow_resources();
|
||||||
|
let result = if let Some(future) = resources.remove(&storable_resource) {
|
||||||
|
assert!(
|
||||||
|
!force_run,
|
||||||
|
"Forcing to run an already-added resource is a logical error"
|
||||||
|
);
|
||||||
|
resources.insert(storable_resource, future.clone());
|
||||||
|
drop(resources);
|
||||||
|
let logger = StoringLogger::default();
|
||||||
|
logger.trace(format!(
|
||||||
|
"{:?} already added",
|
||||||
|
weak_resource.upgrade().expect("Dangling!")
|
||||||
|
));
|
||||||
|
let (t, did_run) = future.await;
|
||||||
|
Ok((logger, t, did_run))
|
||||||
|
} else {
|
||||||
|
let inner_weak = Rc::downgrade(&self.0);
|
||||||
|
let future = Box::pin(async move {
|
||||||
|
let this = Self(inner_weak.upgrade().expect("Dangling!"));
|
||||||
|
let resource = weak_resource.upgrade().expect("Dangling!");
|
||||||
|
// Need to convert Box<Error> to String for Clone for Shared
|
||||||
|
let result = this.0.core.add(&this, resource, force_run).await;
|
||||||
|
|
||||||
|
result
|
||||||
|
.map(|(logger, t, did_run)| (logger, As::from_artifact(t), did_run))
|
||||||
|
.map_err(|(logger, e)| (logger, e.to_string()))
|
||||||
|
})
|
||||||
|
.shared();
|
||||||
|
let future_clone = future.clone();
|
||||||
|
resources.insert(
|
||||||
|
storable_resource,
|
||||||
|
(Box::pin(async move {
|
||||||
|
let result = future_clone.await;
|
||||||
|
if result.is_err() {
|
||||||
|
// Step back to give the initial caller time to handle the error before unwrapping
|
||||||
|
sleep(Duration::from_millis(0)).await;
|
||||||
|
}
|
||||||
|
result.map(|(_, t, did_run)| (t, did_run)).unwrap()
|
||||||
|
}) as Pin<Box<dyn Future<Output = (As, bool)>>>)
|
||||||
|
.shared(),
|
||||||
|
);
|
||||||
|
drop(resources);
|
||||||
|
let result = future.await;
|
||||||
|
result.map_err(|(logger, e)| (logger, e.into()))
|
||||||
|
};
|
||||||
|
result.map(|(logger, t, did_run)| (logger, t.into_artifact(), did_run))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SetupFacade<
|
||||||
|
SR,
|
||||||
|
LOG,
|
||||||
|
L = DefaultLocator,
|
||||||
|
B = DefaultBuilder,
|
||||||
|
Rs = DefaultResources<'static, &'static str>,
|
||||||
|
As = DefaultArtifacts<'static, &'static str>,
|
||||||
|
>(LOG, Setup<SR, L, B, Rs, As>);
|
||||||
|
|
||||||
|
impl<SR, LOG> SetupFacade<SR, LOG> {
|
||||||
|
pub fn new(symbol_runner: SR, logger: LOG) -> Self {
|
||||||
|
Self::new_with(symbol_runner, logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, B, As, SR, LOG, Rs: Hash + Eq> SetupFacade<SR, LOG, L, B, Rs, As> {
|
||||||
|
pub fn new_with(symbol_runner: SR, logger: LOG) -> Self {
|
||||||
|
Self(
|
||||||
|
logger,
|
||||||
|
Setup(Rc::new(SetupInner {
|
||||||
|
core: RegularSetupCore::new(symbol_runner),
|
||||||
|
resources: RefCell::default(),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
SR: 'static,
|
||||||
|
LOG: 'static + Logger,
|
||||||
|
L: 'static,
|
||||||
|
B: 'static,
|
||||||
|
Rs: 'static + Eq + Hash,
|
||||||
|
As: 'static,
|
||||||
|
> SetupFacade<SR, LOG, L, B, Rs, As>
|
||||||
|
{
|
||||||
|
pub async fn add_force<R: AddableResource>(&self, resource: R, force_run: bool) -> AddResult<R>
|
||||||
|
where
|
||||||
|
Rs: FromResource<R>,
|
||||||
|
As: FromArtifact<R> + Clone,
|
||||||
|
R::Artifact: Clone,
|
||||||
|
RegularSetupCore<SR, L, B>: SetupCore<R, Setup<SR, L, B, Rs, As>>,
|
||||||
|
{
|
||||||
|
let result = self.1.add(resource, force_run).await;
|
||||||
|
match result {
|
||||||
|
Ok((logger, t, did_run)) => {
|
||||||
|
if self
|
||||||
|
.0
|
||||||
|
.put(logger.release().into_iter().filter(|e| e.0 <= 3))
|
||||||
|
== 0
|
||||||
|
{
|
||||||
|
self.0.write(3, ".");
|
||||||
|
}
|
||||||
|
Ok((t, did_run))
|
||||||
|
}
|
||||||
|
Err((logger, e)) => {
|
||||||
|
self.0.put(logger.release());
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn add<R: AddableResource>(&self, resource: R) -> AddResult<R>
|
||||||
|
where
|
||||||
|
RegularSetupCore<SR, L, B>: SetupCore<R, Setup<SR, L, B, Rs, As>>,
|
||||||
|
Rs: FromResource<R>,
|
||||||
|
As: FromArtifact<R> + Clone,
|
||||||
|
R::Artifact: Clone,
|
||||||
|
SR: SymbolRunner,
|
||||||
|
{
|
||||||
|
self.add_force(resource, false).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_symbol<S: Runnable>(
|
||||||
|
&self,
|
||||||
|
symbol: S,
|
||||||
|
force: bool,
|
||||||
|
) -> Result<bool, Box<dyn Error>>
|
||||||
|
where
|
||||||
|
RegularSetupCore<SR, L, B>: SymbolRunner,
|
||||||
|
{
|
||||||
|
symbol.run(&(self.1).0.core, &self.0, force).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::SymbolRunner;
|
||||||
|
use crate::async_utils::run;
|
||||||
|
use crate::loggers::{Logger, StoringLogger};
|
||||||
|
use crate::resources::{FromArtifact, FromResource, Resource};
|
||||||
|
use crate::symbols::Symbol;
|
||||||
|
use crate::to_artifact::ToArtifact;
|
||||||
|
use crate::{ImplementationBuilder, ResourceLocator, Setup};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
|
struct TestSymbolRunner {
|
||||||
|
count: Rc<RefCell<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl SymbolRunner for TestSymbolRunner {
|
||||||
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||||
|
&self,
|
||||||
|
symbol: &S,
|
||||||
|
_logger: &L,
|
||||||
|
force: bool,
|
||||||
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
|
let run = force || !symbol.target_reached().await?;
|
||||||
|
if run {
|
||||||
|
*self.count.borrow_mut() += 1;
|
||||||
|
}
|
||||||
|
Ok(run)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
struct TestResource<T>(&'static str, T);
|
||||||
|
impl<T> Resource for TestResource<T> {
|
||||||
|
type Artifact = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||||
|
enum Resources {
|
||||||
|
A(Rc<TestResource<&'static str>>),
|
||||||
|
B(Rc<TestResource<()>>),
|
||||||
|
}
|
||||||
|
impl FromResource<TestResource<&'static str>> for Resources {
|
||||||
|
fn from_resource(from: TestResource<&'static str>) -> (Self, Weak<TestResource<&'static str>>) {
|
||||||
|
let inner = Rc::new(from);
|
||||||
|
(Self::A(Rc::clone(&inner)), Rc::downgrade(&inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FromResource<TestResource<()>> for Resources {
|
||||||
|
fn from_resource(from: TestResource<()>) -> (Self, Weak<TestResource<()>>) {
|
||||||
|
let inner = Rc::new(from);
|
||||||
|
(Self::B(Rc::clone(&inner)), Rc::downgrade(&inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Artifacts;
|
||||||
|
impl<V> FromArtifact<TestResource<V>> for Artifacts {
|
||||||
|
fn from_artifact(_from: ()) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
#[allow(clippy::unused_unit)]
|
||||||
|
fn into_artifact(self) -> () {
|
||||||
|
#[allow(clippy::unused_unit)]
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestResourceLocator;
|
||||||
|
impl<T> ResourceLocator<TestResource<T>> for TestResourceLocator {
|
||||||
|
type Prerequisites = ();
|
||||||
|
fn locate(_resource: &TestResource<T>) -> (<TestResource<T> as ToArtifact>::Artifact, ()) {
|
||||||
|
((), ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestImplementationBuilder;
|
||||||
|
impl ImplementationBuilder<TestResource<&'static str>> 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: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||||
|
) -> Self::Implementation {
|
||||||
|
TestSymbol {
|
||||||
|
reached: resource.0.chars().next().unwrap().is_uppercase(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ImplementationBuilder<TestResource<()>> 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<bool, Box<dyn Error>> {
|
||||||
|
Ok(self.reached)
|
||||||
|
}
|
||||||
|
async fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_setup() -> (
|
||||||
|
Rc<RefCell<usize>>,
|
||||||
|
Setup<
|
||||||
|
TestSymbolRunner,
|
||||||
|
StoringLogger,
|
||||||
|
TestResourceLocator,
|
||||||
|
TestImplementationBuilder,
|
||||||
|
Resources,
|
||||||
|
Artifacts,
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
let count = Rc::new(RefCell::new(0));
|
||||||
|
let runner = TestSymbolRunner {
|
||||||
|
count: Rc::clone(&count),
|
||||||
|
};
|
||||||
|
(count, Setup::new_with(runner, StoringLogger::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn correctly_uses_force() {
|
||||||
|
run(async {
|
||||||
|
let (count, setup) = get_setup();
|
||||||
|
setup.add(TestResource("A", "b")).await.unwrap();
|
||||||
|
assert_eq!(*count.borrow(), 2);
|
||||||
|
setup.add(TestResource("A", "b")).await.unwrap();
|
||||||
|
assert_eq!(*count.borrow(), 2);
|
||||||
|
|
||||||
|
let (count, setup) = get_setup();
|
||||||
|
setup.add(TestResource("A", "B")).await.unwrap();
|
||||||
|
assert_eq!(*count.borrow(), 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::async_utils::sleep;
|
use crate::async_utils::sleep;
|
||||||
|
use crate::loggers::Logger;
|
||||||
use crate::symbols::Symbol;
|
use crate::symbols::Symbol;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use slog::{debug, info, o, trace, Logger};
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
@ -9,10 +9,10 @@ use std::time::Duration;
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
pub trait SymbolRunner {
|
pub trait SymbolRunner {
|
||||||
async fn run_symbol<S: Symbol + Debug>(
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||||
&self,
|
&self,
|
||||||
symbol: &S,
|
symbol: &S,
|
||||||
logger: &Logger,
|
logger: &L,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<bool, Box<dyn Error>>;
|
) -> Result<bool, Box<dyn Error>>;
|
||||||
}
|
}
|
||||||
|
|
@ -37,19 +37,18 @@ impl InitializingSymbolRunner {
|
||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exec_symbol<S: Symbol + Debug>(
|
async fn exec_symbol<S: Symbol + Debug, L: Logger>(
|
||||||
&self,
|
&self,
|
||||||
symbol: &S,
|
symbol: &S,
|
||||||
logger: &Logger,
|
logger: &L,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
info!(logger, "Executing {:?}", symbol);
|
logger.info(format!("Executing {:?}", symbol));
|
||||||
symbol.execute().await?;
|
symbol.execute().await?;
|
||||||
let target_reached = symbol.target_reached().await?;
|
let target_reached = symbol.target_reached().await?;
|
||||||
trace!(
|
logger.trace(format!(
|
||||||
logger,
|
|
||||||
"Symbol reports target_reached: {:?} (should be true)",
|
"Symbol reports target_reached: {:?} (should be true)",
|
||||||
target_reached
|
target_reached
|
||||||
);
|
));
|
||||||
if target_reached {
|
if target_reached {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -60,26 +59,25 @@ impl InitializingSymbolRunner {
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl SymbolRunner for InitializingSymbolRunner {
|
impl SymbolRunner for InitializingSymbolRunner {
|
||||||
async fn run_symbol<S: Symbol + Debug>(
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||||
&self,
|
&self,
|
||||||
symbol: &S,
|
symbol: &S,
|
||||||
logger: &Logger,
|
logger: &L,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<bool, Box<dyn Error>> {
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
let executed = if force {
|
let executed = if force {
|
||||||
debug!(logger, "Forcing symbol execution");
|
logger.debug("Forcing symbol execution");
|
||||||
self.exec_symbol(symbol, logger).await?;
|
self.exec_symbol(symbol, logger).await?;
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let target_reached = symbol.target_reached().await?;
|
let target_reached = symbol.target_reached().await?;
|
||||||
if target_reached {
|
if target_reached {
|
||||||
debug!(logger, "{:?} already reached", symbol);
|
logger.debug(format!("{:?} already reached", symbol));
|
||||||
} else {
|
} else {
|
||||||
trace!(
|
logger.trace(format!(
|
||||||
logger,
|
|
||||||
"Symbol reports target_reached: {:?}",
|
"Symbol reports target_reached: {:?}",
|
||||||
target_reached
|
target_reached
|
||||||
);
|
));
|
||||||
self.exec_symbol(symbol, logger).await?;
|
self.exec_symbol(symbol, logger).await?;
|
||||||
}
|
}
|
||||||
!target_reached
|
!target_reached
|
||||||
|
|
@ -108,10 +106,10 @@ where
|
||||||
clippy::cast_possible_truncation,
|
clippy::cast_possible_truncation,
|
||||||
clippy::cast_precision_loss
|
clippy::cast_precision_loss
|
||||||
)]
|
)]
|
||||||
async fn run_symbol<S: Symbol + Debug>(
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||||
&self,
|
&self,
|
||||||
symbol: &S,
|
symbol: &S,
|
||||||
logger: &Logger,
|
logger: &L,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<bool, Box<dyn Error>> {
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
sleep(Duration::from_millis(
|
sleep(Duration::from_millis(
|
||||||
|
|
@ -141,23 +139,23 @@ impl DrySymbolRunner {
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl SymbolRunner for DrySymbolRunner {
|
impl SymbolRunner for DrySymbolRunner {
|
||||||
async fn run_symbol<S: Symbol + Debug>(
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||||
&self,
|
&self,
|
||||||
symbol: &S,
|
symbol: &S,
|
||||||
logger: &Logger,
|
logger: &L,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<bool, Box<dyn Error>> {
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
let would_execute = if force {
|
let would_execute = if force {
|
||||||
info!(logger, "Would force-execute");
|
logger.info(format!("Would force-execute {:?}", symbol));
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let target_reached = symbol.target_reached().await?;
|
let target_reached = symbol.target_reached().await?;
|
||||||
debug!(
|
logger.debug(format!(
|
||||||
logger,
|
"Symbol reports target_reached: {:?}",
|
||||||
"Symbol reports target_reached: {:?}", target_reached
|
target_reached
|
||||||
);
|
));
|
||||||
if !target_reached {
|
if !target_reached {
|
||||||
info!(logger, "Would execute");
|
logger.info(format!("Would execute {:?}", symbol));
|
||||||
}
|
}
|
||||||
!target_reached
|
!target_reached
|
||||||
};
|
};
|
||||||
|
|
@ -180,19 +178,18 @@ impl<R> SymbolRunner for ReportingSymbolRunner<R>
|
||||||
where
|
where
|
||||||
R: SymbolRunner,
|
R: SymbolRunner,
|
||||||
{
|
{
|
||||||
async fn run_symbol<S: Symbol + Debug>(
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||||
&self,
|
&self,
|
||||||
symbol: &S,
|
symbol: &S,
|
||||||
logger: &Logger,
|
logger: &L,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<bool, Box<dyn Error>> {
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
let logger = logger.new(o!("symbol" => format!("{symbol:?}")));
|
logger.debug(format!("Running symbol {:?}", symbol));
|
||||||
debug!(logger, "Running ...");
|
let res = self.0.run_symbol(symbol, logger, force).await;
|
||||||
let res = self.0.run_symbol(symbol, &logger, force).await;
|
|
||||||
if let Err(ref e) = res {
|
if let Err(ref e) = res {
|
||||||
info!(logger, "failed with {}", e);
|
logger.info(format!("Failed on {:?} with {}, aborting.", symbol, e));
|
||||||
} else {
|
} else {
|
||||||
debug!(logger, "Successfully finished");
|
logger.debug(format!("Successfully finished {:?}", symbol));
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
@ -203,9 +200,9 @@ mod test {
|
||||||
use super::{DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner, SymbolRunner};
|
use super::{DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner, SymbolRunner};
|
||||||
use crate::async_utils::sleep;
|
use crate::async_utils::sleep;
|
||||||
use crate::async_utils::{run, try_join};
|
use crate::async_utils::{run, try_join};
|
||||||
|
use crate::loggers::StoringLogger;
|
||||||
use crate::symbols::Symbol;
|
use crate::symbols::Symbol;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use slog::{o, Discard, Logger};
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
@ -266,7 +263,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_symbol<S: Symbol + Debug>(s: S) -> Result<bool, Box<dyn Error>> {
|
fn run_symbol<S: Symbol + Debug>(s: S) -> Result<bool, Box<dyn Error>> {
|
||||||
run(InitializingSymbolRunner::new().run_symbol(&s, &Logger::root(Discard, o!()), false))
|
run(InitializingSymbolRunner::new().run_symbol(&s, &StoringLogger::new(), false))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -319,8 +316,8 @@ mod test {
|
||||||
let s1 = SleeperSymbol;
|
let s1 = SleeperSymbol;
|
||||||
let s2 = DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]);
|
let s2 = DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]);
|
||||||
|
|
||||||
let l1 = Logger::root(Discard, o!());
|
let l1 = StoringLogger::new();
|
||||||
let l2 = Logger::root(Discard, o!());
|
let l2 = StoringLogger::new();
|
||||||
let runner1 = InitializingSymbolRunner::new();
|
let runner1 = InitializingSymbolRunner::new();
|
||||||
let result = try_join!(
|
let result = try_join!(
|
||||||
runner1.run_symbol(&s1, &l1, false),
|
runner1.run_symbol(&s1, &l1, false),
|
||||||
|
|
@ -330,6 +327,8 @@ mod test {
|
||||||
assert_eq!(result, (false, true));
|
assert_eq!(result, (false, true));
|
||||||
|
|
||||||
let s2 = DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]);
|
let s2 = DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]);
|
||||||
|
let l1 = StoringLogger::new();
|
||||||
|
let l2 = StoringLogger::new();
|
||||||
let runner2 = DrySymbolRunner::new();
|
let runner2 = DrySymbolRunner::new();
|
||||||
let result = try_join!(
|
let result = try_join!(
|
||||||
runner2.run_symbol(&s1, &l1, false),
|
runner2.run_symbol(&s1, &l1, false),
|
||||||
|
|
@ -339,6 +338,8 @@ mod test {
|
||||||
assert_eq!(result, (false, true));
|
assert_eq!(result, (false, true));
|
||||||
|
|
||||||
let s2 = DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]);
|
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 runner3 = ReportingSymbolRunner::new(runner1);
|
||||||
let result = try_join!(
|
let result = try_join!(
|
||||||
runner3.run_symbol(&s1, &l1, false),
|
runner3.run_symbol(&s1, &l1, false),
|
||||||
|
|
|
||||||
|
|
@ -1,131 +1,12 @@
|
||||||
use crate::async_utils::join;
|
use crate::loggers::StoringLogger;
|
||||||
use crate::resources::Resource;
|
use crate::resources::Resource;
|
||||||
use crate::to_artifact::ToArtifact;
|
use crate::to_artifact::ToArtifact;
|
||||||
use async_trait::async_trait;
|
|
||||||
use slog::{Drain, Filter, Logger, OwnedKVList, Record};
|
|
||||||
use slog_async::AsyncRecord;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
pub trait AddableResource: 'static + Resource + Debug {}
|
pub trait AddableResource: 'static + Resource + Debug {}
|
||||||
impl<R> AddableResource for R where R: 'static + Resource + Debug {}
|
impl<R> AddableResource for R where R: 'static + Resource + Debug {}
|
||||||
|
|
||||||
pub type AddResult<R> = Result<(<R as ToArtifact>::Artifact, bool), Box<dyn Error>>;
|
pub type AddResult<R> = Result<(<R as ToArtifact>::Artifact, bool), Box<dyn Error>>;
|
||||||
|
pub type InternalAddResult<R> =
|
||||||
#[async_trait(?Send)]
|
Result<(StoringLogger, <R as ToArtifact>::Artifact, bool), (StoringLogger, Box<dyn Error>)>;
|
||||||
pub trait Add<R: AddableResource> {
|
|
||||||
async fn add(&self, logger: &Rc<Logger>, resource: Rc<R>, force_run: bool) -> AddResult<R>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
pub trait AddGeneric<X: ToArtifact> {
|
|
||||||
async fn add_generic(&self, logger: &Rc<Logger>, x: X, force_run: bool) -> AddResult<X>;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! add_generic {
|
|
||||||
( $($name:ident)* ) => (
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
impl<_S, $($name: AddableResource,)*>
|
|
||||||
AddGeneric<($($name,)*)> for _S
|
|
||||||
where
|
|
||||||
$(
|
|
||||||
_S: AddGeneric<$name>
|
|
||||||
),*
|
|
||||||
{
|
|
||||||
#[allow(unused, clippy::shadow_unrelated)]
|
|
||||||
async fn add_generic(&self, logger: &Rc<Logger>, ($($name,)*): ($($name,)*), force_run: bool) -> AddResult<($($name,)*)>
|
|
||||||
{
|
|
||||||
let ($($name,)*) = join!($(self.add_generic(logger, $name, force_run),)*);
|
|
||||||
let mut did_run_any = false;
|
|
||||||
$(
|
|
||||||
let (artifact, did_run) = $name?;
|
|
||||||
did_run_any = did_run_any || did_run;
|
|
||||||
let $name = artifact;
|
|
||||||
)*
|
|
||||||
Ok((($($name,)*), did_run_any))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for_each_tuple!(add_generic);
|
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
impl<R: AddableResource + Debug, S: Add<R>> AddGeneric<R> for S {
|
|
||||||
async fn add_generic(&self, logger: &Rc<Logger>, r: R, force_run: bool) -> AddResult<R> {
|
|
||||||
self.add(logger, Rc::new(r), force_run).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// From https://users.rust-lang.org/t/how-to-send-a-writer-into-a-thread/4965/10
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Output<W>(Rc<RefCell<W>>);
|
|
||||||
|
|
||||||
impl<W: Write> Output<W> {
|
|
||||||
pub fn new(w: W) -> Self {
|
|
||||||
Self(Rc::new(RefCell::new(w)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> Write for Output<W> {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.0.borrow_mut().write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.0.borrow_mut().flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct Recorder(Arc<Mutex<Vec<AsyncRecord>>>);
|
|
||||||
|
|
||||||
impl Drain for Recorder {
|
|
||||||
type Ok = ();
|
|
||||||
type Err = slog::Never;
|
|
||||||
|
|
||||||
fn log(&self, record: &Record<'_>, logger_values: &OwnedKVList) -> Result<Self::Ok, Self::Err> {
|
|
||||||
self
|
|
||||||
.0
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.push(AsyncRecord::from(record, logger_values));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Recorder {
|
|
||||||
pub fn into_string(self, filter_level: slog::Level) -> String {
|
|
||||||
let output = Output::new(vec![]);
|
|
||||||
{
|
|
||||||
let decorator = slog_term::PlainDecorator::new(output.clone());
|
|
||||||
let drain = Filter::new(
|
|
||||||
slog_term::CompactFormat::new(decorator).build(),
|
|
||||||
move |record| record.level().is_at_least(filter_level),
|
|
||||||
);
|
|
||||||
let Ok(mutex) = Arc::try_unwrap(self.0) else { panic!("cannot unwrap Arc") }; // AsyncRecord does not implement Debug, so we cannot unwrap
|
|
||||||
for record in mutex.into_inner().unwrap() {
|
|
||||||
record.log_to(&drain).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String::from_utf8(Rc::try_unwrap(output.0).unwrap().into_inner())
|
|
||||||
.expect("Record output should be valid UTF-8")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::Recorder;
|
|
||||||
use slog::Level;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn records_no_output() {
|
|
||||||
let recorder = Recorder::default();
|
|
||||||
assert_eq!(recorder.into_string(Level::Trace), "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ pub struct Checkout<_C, C, P, S, B> {
|
||||||
phantom: PhantomData<_C>,
|
phantom: PhantomData<_C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<_C, C, P, S, B> Checkout<_C, C, P, S, B> {
|
impl<C, _C, P, S, B> Checkout<_C, C, P, S, B> {
|
||||||
pub fn new(target: P, source: S, branch: B, command_runner: C) -> Self {
|
pub fn new(target: P, source: S, branch: B, command_runner: C) -> Self {
|
||||||
Self {
|
Self {
|
||||||
target,
|
target,
|
||||||
|
|
@ -29,7 +29,7 @@ impl<_C, C, P, S, B> Checkout<_C, C, P, S, B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, S, B> Checkout<_C, C, P, S, B> {
|
impl<C: CommandRunner, _C: Borrow<C>, P: AsRef<Path>, S, B> Checkout<C, _C, P, S, B> {
|
||||||
async fn run_git(&self, args: &[impl AsRef<OsStr>]) -> Result<Vec<u8>, Box<dyn Error>> {
|
async fn run_git(&self, args: &[impl AsRef<OsStr>]) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
let mut new_args = Vec::with_capacity(args.len() + 2);
|
let mut new_args = Vec::with_capacity(args.len() + 2);
|
||||||
new_args.extend_from_slice(args!["-C", self.target.as_ref()]);
|
new_args.extend_from_slice(args!["-C", self.target.as_ref()]);
|
||||||
|
|
@ -43,8 +43,8 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, S, B> Checkout<_C, C, P,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, S: AsRef<str>, B: AsRef<str>> Symbol
|
impl<C: CommandRunner, _C: Borrow<C>, P: AsRef<Path>, S: AsRef<str>, B: AsRef<str>> Symbol
|
||||||
for Checkout<_C, C, P, S, B>
|
for Checkout<C, _C, P, S, B>
|
||||||
{
|
{
|
||||||
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||||
if !self.target.as_ref().exists() {
|
if !self.target.as_ref().exists() {
|
||||||
|
|
@ -132,7 +132,7 @@ mod test {
|
||||||
args: RefCell::new(vec![]),
|
args: RefCell::new(vec![]),
|
||||||
};
|
};
|
||||||
let checkout: Checkout<DummyCommandRunner, _, _, _, _> =
|
let checkout: Checkout<DummyCommandRunner, _, _, _, _> =
|
||||||
Checkout::new(".", "source", "branch", &c);
|
Checkout::new("target", "source", "branch", &c);
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
assert!(run(checkout.target_reached()).unwrap());
|
assert!(run(checkout.target_reached()).unwrap());
|
||||||
let end = Instant::now();
|
let end = Instant::now();
|
||||||
|
|
@ -142,11 +142,12 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
first_two_args,
|
first_two_args,
|
||||||
[
|
[
|
||||||
["-C", ".", "fetch", "source", "branch"],
|
["-C", "target", "fetch", "source", "branch"],
|
||||||
["-C", ".", "rev-list", "-1", "HEAD"],
|
["-C", "target", "rev-list", "-1", "HEAD"],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(args[2], ["-C", ".", "rev-list", "-1", "FETCH_HEAD"]);
|
drop(first_two_args);
|
||||||
|
assert_eq!(args[2], ["-C", "target", "rev-list", "-1", "FETCH_HEAD"]);
|
||||||
|
|
||||||
assert!((end - start).as_millis() >= 100);
|
assert!((end - start).as_millis() >= 100);
|
||||||
assert!((end - start).as_millis() < 150);
|
assert!((end - start).as_millis() < 150);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub struct Database<'a, D, S, C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, D, S, C: CommandRunner> Database<'a, D, S, C> {
|
impl<'a, D, S, C: CommandRunner> Database<'a, D, S, C> {
|
||||||
pub const fn new(db_name: D, seed_file: S, command_runner: &'a C) -> Self {
|
pub fn new(db_name: D, seed_file: S, command_runner: &'a C) -> Self {
|
||||||
Self {
|
Self {
|
||||||
db_name,
|
db_name,
|
||||||
seed_file,
|
seed_file,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub struct Dump<'a, N, C, S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, N, C: CommandRunner, S> Dump<'a, N, C, S> {
|
impl<'a, N, C: CommandRunner, S> Dump<'a, N, C, S> {
|
||||||
pub const fn new(db_name: N, storage: S, command_runner: &'a C) -> Self {
|
pub fn new(db_name: N, storage: S, command_runner: &'a C) -> Self {
|
||||||
Self {
|
Self {
|
||||||
db_name,
|
db_name,
|
||||||
storage,
|
storage,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ pub struct User<'a, U, C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, U: AsRef<str>, C: CommandRunner> User<'a, U, C> {
|
impl<'a, U: AsRef<str>, C: CommandRunner> User<'a, U, C> {
|
||||||
pub const fn new(user_name: U, command_runner: &'a C) -> Self {
|
pub fn new(user_name: U, command_runner: &'a C) -> Self {
|
||||||
Self {
|
Self {
|
||||||
user_name,
|
user_name,
|
||||||
command_runner,
|
command_runner,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub struct Install<'a, T: AsRef<Path>, C: CommandRunner> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: AsRef<Path>, C: CommandRunner> Install<'a, T, C> {
|
impl<'a, T: AsRef<Path>, C: CommandRunner> Install<'a, T, C> {
|
||||||
pub const fn new(target: T, command_runner: &'a C) -> Self {
|
pub fn new(target: T, command_runner: &'a C) -> Self {
|
||||||
Self {
|
Self {
|
||||||
target,
|
target,
|
||||||
command_runner,
|
command_runner,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub struct PostgreSQLDatabase<'a, N: AsRef<str>, S: AsRef<str>, C: CommandRunner
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, N: AsRef<str>, S: AsRef<str>, C: CommandRunner> PostgreSQLDatabase<'a, N, S, C> {
|
impl<'a, N: AsRef<str>, S: AsRef<str>, C: CommandRunner> PostgreSQLDatabase<'a, N, S, C> {
|
||||||
pub const fn new(name: N, seed_file: S, command_runner: &'a C) -> Self {
|
pub fn new(name: N, seed_file: S, command_runner: &'a C) -> Self {
|
||||||
PostgreSQLDatabase {
|
PostgreSQLDatabase {
|
||||||
name,
|
name,
|
||||||
seed_file,
|
seed_file,
|
||||||
|
|
@ -25,7 +25,7 @@ impl<'a, N: AsRef<str>, S: AsRef<str>, C: CommandRunner> PostgreSQLDatabase<'a,
|
||||||
.command_runner
|
.command_runner
|
||||||
.get_output(
|
.get_output(
|
||||||
"su",
|
"su",
|
||||||
args!["-", "postgres", "-c", format!("psql -t -c \"{sql}\"")],
|
args!["-", "postgres", "-c", format!("psql -t -c \"{}\"", sql)],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(String::from_utf8(b)?)
|
Ok(String::from_utf8(b)?)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use std::marker::PhantomData;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum StorageDirection {
|
pub enum StorageDirection {
|
||||||
Load,
|
Load,
|
||||||
Store,
|
Store,
|
||||||
|
|
|
||||||
|
|
@ -8,23 +8,28 @@ use std::path::Path;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UserService<'a, S: AsRef<Path>, U: AsRef<str>> {
|
pub struct UserService<'a, S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> {
|
||||||
socket_path: S,
|
socket_path: S,
|
||||||
service_name: &'a str,
|
service_name: &'a str,
|
||||||
command_runner: SetuidCommandRunner<U>,
|
command_runner: SetuidCommandRunner<'a, U, R>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: AsRef<Path>, U: AsRef<str>> UserService<'static, S, U> {
|
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> UserService<'static, S, U, R> {
|
||||||
pub const fn new(socket_path: S, user_name: U, service_name: &'static str) -> Self {
|
pub fn new(
|
||||||
|
socket_path: S,
|
||||||
|
user_name: U,
|
||||||
|
service_name: &'static str,
|
||||||
|
command_runner: &'static R,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
socket_path,
|
socket_path,
|
||||||
service_name,
|
service_name,
|
||||||
command_runner: SetuidCommandRunner::new(user_name),
|
command_runner: SetuidCommandRunner::new(user_name, command_runner),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: AsRef<Path>, U: AsRef<str>> UserService<'_, S, U> {
|
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> UserService<'_, S, U, R> {
|
||||||
async fn systemctl_wait_for_dbus(&self, args: &[&OsStr]) -> Result<String, Box<dyn Error>> {
|
async fn systemctl_wait_for_dbus(&self, args: &[&OsStr]) -> Result<String, Box<dyn Error>> {
|
||||||
let mut tries = 5;
|
let mut tries = 5;
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -80,7 +85,7 @@ impl<S: AsRef<Path>, U: AsRef<str>> UserService<'_, S, U> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl<S: AsRef<Path>, U: AsRef<str>> Symbol for UserService<'_, S, U> {
|
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> Symbol for UserService<'_, S, U, R> {
|
||||||
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||||
self.check_if_service().await
|
self.check_if_service().await
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use std::path::Path;
|
||||||
pub struct Key<C, P> {
|
pub struct Key<C, P> {
|
||||||
file_path: P,
|
file_path: P,
|
||||||
command_runner: C,
|
command_runner: C,
|
||||||
bits: u32,
|
bytes: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, P> Key<C, P> {
|
impl<C, P> Key<C, P> {
|
||||||
|
|
@ -16,7 +16,7 @@ impl<C, P> Key<C, P> {
|
||||||
Self {
|
Self {
|
||||||
file_path,
|
file_path,
|
||||||
command_runner,
|
command_runner,
|
||||||
bits: 4096,
|
bytes: 4096,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,10 +42,8 @@ impl<C: CommandRunner, P: AsRef<Path>> Symbol for Key<C, P> {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(
|
// FIXME check bytes
|
||||||
stdout.ends_with(b"RSA key ok\n")
|
Ok(stdout.ends_with(b"RSA key ok\n"))
|
||||||
&& stdout.starts_with(format!("RSA Private-Key: ({} bit, 2 primes)\n", self.bits).as_ref()),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(&self) -> Result<(), Box<dyn Error>> {
|
async fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||||
|
|
@ -57,7 +55,7 @@ impl<C: CommandRunner, P: AsRef<Path>> Symbol for Key<C, P> {
|
||||||
"genrsa",
|
"genrsa",
|
||||||
"-out",
|
"-out",
|
||||||
self.file_path.as_ref(),
|
self.file_path.as_ref(),
|
||||||
self.bits.to_string(),
|
self.bytes.to_string(),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ pub struct Plugin<'a, P, N, R> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Plugin<'a, P, N, R> {
|
impl<'a, P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Plugin<'a, P, N, R> {
|
||||||
pub const fn new(base: P, name: N, command_runner: &'a R) -> Self {
|
pub fn new(base: P, name: N, command_runner: &'a R) -> Self {
|
||||||
Self {
|
Self {
|
||||||
base,
|
base,
|
||||||
name,
|
name,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub struct Translation<'a, C, D, R> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, D, C: AsRef<str>, R: CommandRunner> Translation<'a, C, D, R> {
|
impl<'a, D, C: AsRef<str>, R: CommandRunner> Translation<'a, C, D, R> {
|
||||||
pub const fn new(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self {
|
pub fn new(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self {
|
||||||
Self {
|
Self {
|
||||||
path,
|
path,
|
||||||
version,
|
version,
|
||||||
|
|
@ -33,7 +33,7 @@ impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Translation<'_, C, D, R> {
|
||||||
fn get_pairs(&self) -> Vec<(String, PathBuf)> {
|
fn get_pairs(&self) -> Vec<(String, PathBuf)> {
|
||||||
let version_x = self
|
let version_x = self
|
||||||
.version
|
.version
|
||||||
.trim_end_matches(|c: char| c.is_ascii_digit())
|
.trim_end_matches(|c: char| c.is_digit(10))
|
||||||
.to_owned()
|
.to_owned()
|
||||||
+ "x";
|
+ "x";
|
||||||
let locale = self.locale.as_ref();
|
let locale = self.locale.as_ref();
|
||||||
|
|
@ -51,7 +51,7 @@ impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Translation<'_, C, D, R> {
|
||||||
] {
|
] {
|
||||||
for format in &["po", "mo"] {
|
for format in &["po", "mo"] {
|
||||||
res.push((
|
res.push((
|
||||||
format!("https://translate.wordpress.org/projects/wp/{version_x}/{in_slug}{path_locale}/default/export-translations?format={format}"),
|
format!("https://translate.wordpress.org/projects/wp/{}/{}{}/default/export-translations?format={}", version_x, in_slug, path_locale, format),
|
||||||
[self.path.as_ref(), format!("{}{}.{}", out_slug, self.locale.as_ref(), format).as_ref()].iter().collect()
|
[self.path.as_ref(), format!("{}{}.{}", out_slug, self.locale.as_ref(), format).as_ref()].iter().collect()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,55 +11,3 @@ pub fn acme_challenges_snippet<P: AsRef<Path>>(path: P) -> String {
|
||||||
path.as_ref().to_str().unwrap()
|
path.as_ref().to_str().unwrap()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::{server_config, uwsgi_snippet};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_uwsgi() {
|
|
||||||
assert_eq!(
|
|
||||||
server_config(
|
|
||||||
"testdomain",
|
|
||||||
"/certpath",
|
|
||||||
"/keypath",
|
|
||||||
uwsgi_snippet("/uwsgi.sock", "/static"),
|
|
||||||
"/challenges_snippet.conf"
|
|
||||||
),
|
|
||||||
"server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
listen [::]:443 ssl http2;
|
|
||||||
server_name testdomain;
|
|
||||||
include \"/challenges_snippet.conf\";
|
|
||||||
|
|
||||||
ssl_certificate /certpath;
|
|
||||||
ssl_certificate_key /keypath;
|
|
||||||
add_header Strict-Transport-Security \"max-age=31536000\";
|
|
||||||
|
|
||||||
root /static;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri @proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
location @proxy {
|
|
||||||
include uwsgi_params;
|
|
||||||
uwsgi_pass unix:/uwsgi.sock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Redirect all HTTP links to the matching HTTPS page
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
listen [::]:80;
|
|
||||||
server_name testdomain;
|
|
||||||
include \"/challenges_snippet.conf\";
|
|
||||||
|
|
||||||
location / {
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -78,26 +78,21 @@ pub fn php_snippet<SOCKET: AsRef<Path>, STATIC: AsRef<Path>>(
|
||||||
pub fn redir_snippet(target: &str) -> String {
|
pub fn redir_snippet(target: &str) -> String {
|
||||||
format!(
|
format!(
|
||||||
"location / {{
|
"location / {{
|
||||||
return 301 $scheme://{target}$request_uri;
|
return 301 $scheme://{}$request_uri;
|
||||||
}}"
|
}}",
|
||||||
|
target
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SocketSpec {
|
pub trait SocketSpec {
|
||||||
fn to_proxy_pass(&self) -> String;
|
fn to_nginx(&self) -> String;
|
||||||
fn to_uwsgi_pass(&self) -> String;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsRef<Path>> SocketSpec for T {
|
impl<T: AsRef<Path>> SocketSpec for T {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn to_proxy_pass(&self) -> String {
|
fn to_nginx(&self) -> String {
|
||||||
format!("unix:{}:", self.as_ref().to_str().unwrap())
|
format!("unix:{}:", self.as_ref().to_str().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn to_uwsgi_pass(&self) -> String {
|
|
||||||
format!("unix:{}", self.as_ref().to_str().unwrap())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -112,12 +107,7 @@ impl LocalTcpSocket {
|
||||||
|
|
||||||
impl SocketSpec for LocalTcpSocket {
|
impl SocketSpec for LocalTcpSocket {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn to_proxy_pass(&self) -> String {
|
fn to_nginx(&self) -> String {
|
||||||
format!("localhost:{}", self.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn to_uwsgi_pass(&self) -> String {
|
|
||||||
format!("localhost:{}", self.0)
|
format!("localhost:{}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -139,33 +129,12 @@ pub fn proxy_snippet<S: SocketSpec, STATIC: AsRef<Path>>(
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
}}",
|
}}",
|
||||||
static_path.as_ref().to_str().unwrap(),
|
static_path.as_ref().to_str().unwrap(),
|
||||||
socket_path.to_proxy_pass()
|
socket_path.to_nginx()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn uwsgi_snippet<S: SocketSpec, STATIC: AsRef<Path>>(
|
pub fn static_snippet<S: AsRef<Path>>(static_path: S) -> String {
|
||||||
socket_path: S,
|
|
||||||
static_path: STATIC,
|
|
||||||
) -> String {
|
|
||||||
format!(
|
|
||||||
"root {};
|
|
||||||
|
|
||||||
location / {{
|
|
||||||
try_files $uri @proxy;
|
|
||||||
}}
|
|
||||||
|
|
||||||
location @proxy {{
|
|
||||||
include uwsgi_params;
|
|
||||||
uwsgi_pass {};
|
|
||||||
}}",
|
|
||||||
static_path.as_ref().to_str().unwrap(),
|
|
||||||
socket_path.to_uwsgi_pass()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn static_snippet(static_path: impl AsRef<Path>) -> String {
|
|
||||||
format!(
|
format!(
|
||||||
"root {};
|
"root {};
|
||||||
try_files $uri $uri/ $uri.html =404;
|
try_files $uri $uri/ $uri.html =404;
|
||||||
|
|
@ -175,7 +144,7 @@ pub fn static_snippet(static_path: impl AsRef<Path>) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn dokuwiki_snippet() -> &'static str {
|
pub fn dokuwiki_snippet() -> String {
|
||||||
"
|
"
|
||||||
location ~ /(data/|conf/|bin/|inc/|install.php) { deny all; }
|
location ~ /(data/|conf/|bin/|inc/|install.php) { deny all; }
|
||||||
|
|
||||||
|
|
@ -187,11 +156,11 @@ pub const fn dokuwiki_snippet() -> &'static str {
|
||||||
rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last;
|
rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last;
|
||||||
rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last;
|
rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last;
|
||||||
rewrite ^/(.*) /doku.php?id=$1&$args last;
|
rewrite ^/(.*) /doku.php?id=$1&$args last;
|
||||||
}"
|
}".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn nextcloud_snippet() -> &'static str {
|
pub fn nextcloud_snippet() -> String {
|
||||||
"
|
"
|
||||||
client_max_body_size 500M;
|
client_max_body_size 500M;
|
||||||
|
|
||||||
|
|
@ -241,20 +210,5 @@ pub const fn nextcloud_snippet() -> &'static str {
|
||||||
access_log off;
|
access_log off;
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
}
|
.into()
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::default_server;
|
|
||||||
#[test]
|
|
||||||
fn test_default_server() {
|
|
||||||
assert_eq!(
|
|
||||||
default_server("filename"),
|
|
||||||
r#"server {
|
|
||||||
listen 80 default_server;
|
|
||||||
listen [::]:80 default_server;
|
|
||||||
include "filename";
|
|
||||||
}"#
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,25 +56,3 @@ env[PATH] = /usr/local/bin:/usr/bin:/bin
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::fpm_pool_config;
|
|
||||||
#[test]
|
|
||||||
fn test_fpm_pool_config() {
|
|
||||||
assert_eq!(
|
|
||||||
fpm_pool_config("user", "socket", &5.into()),
|
|
||||||
r"[user]
|
|
||||||
|
|
||||||
user = user
|
|
||||||
group = www-data
|
|
||||||
listen = socket
|
|
||||||
listen.owner = www-data
|
|
||||||
pm = ondemand
|
|
||||||
catch_workers_output = yes
|
|
||||||
env[PATH] = /usr/local/bin:/usr/bin:/bin
|
|
||||||
pm.max_children = 5
|
|
||||||
"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ macro_rules! to_artifact {
|
||||||
for_each_tuple!(to_artifact);
|
for_each_tuple!(to_artifact);
|
||||||
|
|
||||||
impl<T: Resource> ToArtifact for Option<T> {
|
impl<T: Resource> ToArtifact for Option<T> {
|
||||||
|
// FIXME: https://github.com/rust-lang/rust-clippy/issues/2843
|
||||||
|
#![allow(clippy::use_self)]
|
||||||
type Artifact = Option<T::Artifact>;
|
type Artifact = Option<T::Artifact>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ fn may_not_read_file() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn may_not_create_file() {
|
fn may_not_create_file() {
|
||||||
let symbol = get_symbol(Path::new("/proc/somefile"));
|
let symbol = get_symbol(&Path::new("/proc/somefile"));
|
||||||
|
|
||||||
run(async {
|
run(async {
|
||||||
// Could also return an error
|
// Could also return an error
|
||||||
|
|
@ -109,7 +109,7 @@ fn may_not_create_file() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn directory_missing() {
|
fn directory_missing() {
|
||||||
let symbol = get_symbol(Path::new("/nonexisting"));
|
let symbol = get_symbol(&Path::new("/nonexisting"));
|
||||||
|
|
||||||
run(async {
|
run(async {
|
||||||
// Could also return an error
|
// Could also return an error
|
||||||
|
|
|
||||||
169
tests/setup.rs
169
tests/setup.rs
|
|
@ -1,149 +1,100 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use regex::Regex;
|
|
||||||
use schematics::async_utils::{run, sleep};
|
use schematics::async_utils::{run, sleep};
|
||||||
use schematics::loggers::{Entry, StoringLogger};
|
use schematics::loggers::{Logger, StoringLogger};
|
||||||
use schematics::resources::{AcmeUser, Cert, Csr, GitCheckout};
|
use schematics::resources::{AcmeUser, Cert, Csr, GitCheckout};
|
||||||
use schematics::symbols::Symbol;
|
use schematics::symbols::Symbol;
|
||||||
use schematics::Setup;
|
use schematics::Setup;
|
||||||
use schematics::SymbolRunner;
|
use schematics::SymbolRunner;
|
||||||
use slog::{info, Logger as SlogLogger};
|
use std::cell::RefCell;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct TestSymbolRunner {
|
struct TestSymbolRunner {
|
||||||
run: bool,
|
count: Rc<RefCell<usize>>,
|
||||||
}
|
|
||||||
|
|
||||||
impl TestSymbolRunner {
|
|
||||||
fn new(run: bool) -> Self {
|
|
||||||
Self { run }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl SymbolRunner for TestSymbolRunner {
|
impl SymbolRunner for TestSymbolRunner {
|
||||||
async fn run_symbol<S: Symbol + Debug>(
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||||
&self,
|
&self,
|
||||||
_symbol: &S,
|
_symbol: &S,
|
||||||
logger: &SlogLogger,
|
_logger: &L,
|
||||||
_force: bool,
|
_force: bool,
|
||||||
) -> Result<bool, Box<dyn Error>> {
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
info!(logger, "run_symbol");
|
*self.count.borrow_mut() += 1;
|
||||||
sleep(Duration::from_millis(0)).await;
|
sleep(Duration::from_millis(0)).await;
|
||||||
Ok(self.run)
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test(
|
|
||||||
count: usize,
|
|
||||||
body: fn(setup: Setup<TestSymbolRunner, StoringLogger>) -> (),
|
|
||||||
) -> Vec<Entry<String>> {
|
|
||||||
let runner = TestSymbolRunner::new(false);
|
|
||||||
let logger = StoringLogger::new();
|
|
||||||
{
|
|
||||||
let setup = Setup::new(runner, logger.clone());
|
|
||||||
body(setup);
|
|
||||||
}
|
|
||||||
assert_eq!(logger.release(), vec![Entry(3, ".".repeat(count))]);
|
|
||||||
|
|
||||||
let runner = TestSymbolRunner::new(true);
|
|
||||||
let logger = StoringLogger::new();
|
|
||||||
{
|
|
||||||
let setup = Setup::new(runner, logger.clone());
|
|
||||||
body(setup);
|
|
||||||
}
|
|
||||||
logger.release()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_create_an_acme_user() {
|
fn can_create_an_acme_user() {
|
||||||
let mut result = test(1, |setup| {
|
let count = Rc::new(RefCell::new(0));
|
||||||
assert_eq!((run(setup.add(AcmeUser)).unwrap().0).0, "acme");
|
let runner = TestSymbolRunner {
|
||||||
});
|
count: Rc::clone(&count),
|
||||||
let entry = result
|
};
|
||||||
.pop()
|
let setup = Setup::new(runner, StoringLogger::new());
|
||||||
.expect("log is empty but should contain one entry");
|
assert_eq!((run(setup.add(AcmeUser)).unwrap().0).0, "acme");
|
||||||
assert_eq!(result.len(), 0, "log has more than one entry");
|
|
||||||
assert_eq!(entry.0, 3, "log entry has wrong level");
|
|
||||||
let re =
|
|
||||||
Regex::new(r"^resource: AcmeUser\n \w+ \d{1,2} \d{2}:\d{2}:\d{2}.\d{3} INFO run_symbol\n$")
|
|
||||||
.unwrap();
|
|
||||||
assert!(
|
|
||||||
re.is_match(&entry.1),
|
|
||||||
"log output {} does not match {}",
|
|
||||||
entry.1,
|
|
||||||
re
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn runs_only_once() {
|
fn runs_only_once() {
|
||||||
let mut result = test(2, |setup| {
|
run(async {
|
||||||
run(async {
|
let count = Rc::new(RefCell::new(0));
|
||||||
assert_eq!(
|
let runner = TestSymbolRunner {
|
||||||
(setup.add(Csr("somehost")).await.unwrap().0)
|
count: Rc::clone(&count),
|
||||||
.as_ref()
|
};
|
||||||
.to_str()
|
let setup = Setup::new(runner, StoringLogger::new());
|
||||||
.unwrap(),
|
assert_eq!(
|
||||||
"/etc/ssl/local_certs/somehost.csr",
|
(setup.add(Csr("somehost")).await.unwrap().0)
|
||||||
);
|
.as_ref()
|
||||||
assert_eq!(
|
.to_str()
|
||||||
(setup.add(Csr("somehost")).await.unwrap().0)
|
.unwrap(),
|
||||||
.as_ref()
|
"/etc/ssl/local_certs/somehost.csr",
|
||||||
.to_str()
|
);
|
||||||
.unwrap(),
|
assert_eq!(
|
||||||
"/etc/ssl/local_certs/somehost.csr",
|
(setup.add(Csr("somehost")).await.unwrap().0)
|
||||||
);
|
.as_ref()
|
||||||
});
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"/etc/ssl/local_certs/somehost.csr",
|
||||||
|
);
|
||||||
|
assert_eq!(*count.borrow(), 2 + 5); // Key and CSR + 5 dirs
|
||||||
});
|
});
|
||||||
let entry = result
|
|
||||||
.pop()
|
|
||||||
.expect("log is empty but should contain entries");
|
|
||||||
assert_eq!(entry.0, 3, "log entry has wrong level");
|
|
||||||
assert_eq!(entry.1, ".", "log entry has wrong content");
|
|
||||||
let entry = result
|
|
||||||
.pop()
|
|
||||||
.expect("log is empty but should contain entries");
|
|
||||||
assert_eq!(entry.0, 3, "log entry has wrong level");
|
|
||||||
assert_eq!(entry.1.matches("run_symbol").count(), 7); // Key and CSR + 5 dirs
|
|
||||||
assert_eq!(result.len(), 0, "log has more than one entry");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_create_an_acme_cert() {
|
fn can_create_an_acme_cert() {
|
||||||
let mut result = test(1, |setup| {
|
let count = Rc::new(RefCell::new(0));
|
||||||
assert_eq!(
|
let runner = TestSymbolRunner {
|
||||||
(run(setup.add(Cert("somehost"))).unwrap().0)
|
count: Rc::clone(&count),
|
||||||
.as_ref()
|
};
|
||||||
.to_str()
|
let setup = Setup::new(runner, StoringLogger::new());
|
||||||
.unwrap(),
|
assert_eq!(
|
||||||
"/etc/ssl/local_certs/somehost.crt",
|
(run(setup.add(Cert("somehost"))).unwrap().0)
|
||||||
);
|
.as_ref()
|
||||||
});
|
.to_str()
|
||||||
let entry = result
|
.unwrap(),
|
||||||
.pop()
|
"/etc/ssl/local_certs/somehost.crt",
|
||||||
.expect("log is empty but should contain one entry");
|
);
|
||||||
assert_eq!(entry.0, 3, "log entry has wrong level");
|
assert_eq!(*count.borrow(), 19);
|
||||||
assert_eq!(entry.1.matches("run_symbol").count(), 19);
|
|
||||||
assert_eq!(result.len(), 0, "log has more than one entry");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_create_a_git_checkout() {
|
fn can_create_a_git_checkout() {
|
||||||
let mut result = test(1, |setup| {
|
let count = Rc::new(RefCell::new(0));
|
||||||
run(setup.add(GitCheckout(
|
let runner = TestSymbolRunner {
|
||||||
"/tmp/somepath".into(),
|
count: Rc::clone(&count),
|
||||||
"/tmp/some_src_repo",
|
};
|
||||||
"master",
|
let setup = Setup::new(runner, StoringLogger::new());
|
||||||
)))
|
run(setup.add(GitCheckout(
|
||||||
.unwrap();
|
"/tmp/somepath".into(),
|
||||||
});
|
"/tmp/some_src_repo",
|
||||||
let entry = result
|
"master",
|
||||||
.pop()
|
)))
|
||||||
.expect("log is empty but should contain one entry");
|
.unwrap();
|
||||||
assert_eq!(entry.0, 3, "log entry has wrong level");
|
assert_eq!(*count.borrow(), 3);
|
||||||
assert_eq!(entry.1.matches("run_symbol").count(), 3);
|
|
||||||
assert_eq!(result.len(), 0, "log has more than one entry");
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue