Experiment with futures

This commit is contained in:
Adrian Heine 2020-08-16 11:08:22 +02:00
parent 907fbf95db
commit 2d3e3688fa
44 changed files with 2081 additions and 1242 deletions

View file

@ -1,5 +1,6 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::borrow::Borrow;
use std::error::Error;
use std::fs::File as FsFile;
@ -44,24 +45,29 @@ impl<_C, C, D, P> Cert<_C, C, D, P> {
const DAYS_IN_SECONDS: u32 = 24 * 60 * 60;
#[async_trait(?Send)]
impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef<str>, P: AsRef<Path>> Symbol for Cert<_C, C, D, P> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.cert_path.as_ref().exists() {
return Ok(false);
}
let output = self.command_runner.borrow().run_with_args(
"openssl",
args![
"x509",
"-in",
self.cert_path.as_ref(),
"-noout",
"-subject",
"-checkend",
(30 * DAYS_IN_SECONDS).to_string(),
],
)?;
let output = self
.command_runner
.borrow()
.run_with_args(
"openssl",
args![
"x509",
"-in",
self.cert_path.as_ref(),
"-noout",
"-subject",
"-checkend",
(30 * DAYS_IN_SECONDS).to_string(),
],
)
.await?;
if output.status.success()
&& output.stdout
== format!(
@ -83,6 +89,7 @@ impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef<str>, P: AsRef<Path>> Symbol for
self.cert_path.as_ref(),
],
)
.await
.is_ok(),
)
} else if output.status.code() == Some(1)
@ -99,18 +106,22 @@ impl<_C: CommandRunner, C: Borrow<_C>, D: AsRef<str>, P: AsRef<Path>> Symbol for
}
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
let output = self.command_runner.borrow().get_output(
"acme-tiny",
args![
"--account-key",
self.account_key_path.as_ref(),
"--csr",
self.csr_path.as_ref(),
"--acme-dir",
self.challenges_path.as_ref(),
],
)?;
async fn execute(&self) -> Result<(), Box<dyn Error>> {
let output = self
.command_runner
.borrow()
.get_output(
"acme-tiny",
args![
"--account-key",
self.account_key_path.as_ref(),
"--csr",
self.csr_path.as_ref(),
"--acme-dir",
self.challenges_path.as_ref(),
],
)
.await?;
let mut file = FsFile::create(self.cert_path.as_ref())?;
file.write_all(&output)?;
Ok(())

View file

@ -1,4 +1,5 @@
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
use std::fs::{metadata, File};
use std::io::copy;
@ -22,8 +23,9 @@ impl<S, D, I> Concat<S, D, I> {
}
}
#[async_trait(?Send)]
impl<S: AsRef<[I]>, D: AsRef<Path>, I: AsRef<Path>> Symbol for Concat<S, D, I> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let target = self.target.as_ref();
if !target.exists() {
return Ok(false);
@ -37,7 +39,7 @@ impl<S: AsRef<[I]>, D: AsRef<Path>, I: AsRef<Path>> Symbol for Concat<S, D, I> {
Ok(true)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
async fn execute(&self) -> Result<(), Box<dyn Error>> {
let mut file = File::create(self.target.as_ref())?;
for source in self.sources.as_ref() {
copy(&mut File::open(source)?, &mut file)?;

View file

@ -1,5 +1,6 @@
use crate::command_runner::CommandRunner;
use crate::command_runner::{is_success, CommandRunner};
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
#[derive(Debug)]
@ -19,23 +20,27 @@ impl<'r, U, R> Cron<'r, String, U, R> {
}
}
#[async_trait(?Send)]
impl<C: AsRef<str>, U: AsRef<str>, R: CommandRunner> Symbol for Cron<'_, C, U, R> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let tab = self
.command_runner
.get_output("crontab", args!["-l", "-u", self.user.as_ref()])?;
.get_output("crontab", args!["-l", "-u", self.user.as_ref()])
.await?;
Ok(tab == self.content.as_ref().as_bytes())
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
let output = self.command_runner.run_with_args_and_stdin(
"crontab",
args!["-u", self.user.as_ref(), "-",],
self.content.as_ref(),
async fn execute(&self) -> Result<(), Box<dyn Error>> {
is_success(
self
.command_runner
.run(
"crontab",
args!["-u", self.user.as_ref(), "-",],
self.content.as_ref(),
)
.await,
)?;
if !output.status.success() {
return Err(String::from_utf8(output.stderr)?.into());
}
Ok(())
}
}

View file

@ -1,4 +1,5 @@
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
use std::fs;
use std::io;
@ -15,8 +16,9 @@ impl<P> Dir<P> {
}
}
#[async_trait(?Send)]
impl<P: AsRef<Path>> Symbol for Dir<P> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.path.as_ref().exists() {
return Ok(false);
}
@ -30,7 +32,7 @@ impl<P: AsRef<Path>> Symbol for Dir<P> {
Ok(true)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
async fn execute(&self) -> Result<(), Box<dyn Error>> {
fs::create_dir(self.path.as_ref()).map_err(|e| Box::new(e) as Box<dyn Error>)
}
}

View file

@ -1,4 +1,5 @@
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
use std::fs::File as FsFile;
use std::io::{Read, Write};
@ -16,8 +17,9 @@ impl<D, C> File<D, C> {
}
}
#[async_trait(?Send)]
impl<D: AsRef<Path>, C: AsRef<str>> Symbol for File<D, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.path.as_ref().exists() {
return Ok(false);
}
@ -35,7 +37,7 @@ impl<D: AsRef<Path>, C: AsRef<str>> Symbol for File<D, C> {
}
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
async fn execute(&self) -> Result<(), Box<dyn Error>> {
let mut file = FsFile::create(self.path.as_ref())?;
file.write_all(self.content.as_ref().as_bytes())?;
Ok(())

View file

@ -1,5 +1,7 @@
use crate::async_utils::try_join;
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::borrow::Borrow;
use std::error::Error;
use std::ffi::OsStr;
@ -28,47 +30,121 @@ impl<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> {
fn _run_in_target_repo(&self, args: &[impl AsRef<OsStr>]) -> Result<Vec<u8>, Box<dyn Error>> {
let mut new_args = vec![OsStr::new("-C"), self.target.as_ref().as_ref()];
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);
new_args.extend_from_slice(args!["-C", self.target.as_ref()]);
new_args.extend(args.iter().map(AsRef::as_ref));
self.command_runner.borrow().get_output("git", &new_args)
self
.command_runner
.borrow()
.get_output("git", &new_args)
.await
}
}
#[async_trait(?Send)]
impl<C: CommandRunner, _C: Borrow<C>, P: AsRef<Path>, S: AsRef<str>, B: AsRef<str>> Symbol
for Checkout<C, _C, P, S, B>
{
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() {
return Ok(false);
}
self._run_in_target_repo(&["fetch", self.source.as_ref(), self.branch.as_ref()])?;
// git rev-list resolves tag objects
let fetch_head = self._run_in_target_repo(&["rev-list", "-1", "FETCH_HEAD"])?;
let head = self._run_in_target_repo(&["rev-list", "-1", "HEAD"])?;
let fetch_head_f = async {
self
.run_git(args!["fetch", self.source, self.branch])
.await?;
// git rev-list resolves tag objects
self.run_git(&["rev-list", "-1", "FETCH_HEAD"]).await
};
let head_f = self.run_git(&["rev-list", "-1", "HEAD"]);
let (fetch_head, head) = try_join!(fetch_head_f, head_f)?;
Ok(fetch_head == head)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
async fn execute(&self) -> Result<(), Box<dyn Error>> {
if !self.target.as_ref().exists() {
return self.command_runner.borrow().run_successfully(
"git",
args![
"clone",
"--depth",
"1",
"-b",
self.branch.as_ref(),
self.source.as_ref(),
self.target.as_ref(),
],
);
self
.command_runner
.borrow()
.run_successfully(
"git",
args![
"clone",
"--depth",
"1",
"-b",
self.branch.as_ref(),
self.source.as_ref(),
self.target.as_ref(),
],
)
.await?;
} else {
self
.run_git(&["fetch", self.source.as_ref(), self.branch.as_ref()])
.await?;
self.run_git(&["merge", "FETCH_HEAD"]).await?;
}
self._run_in_target_repo(&["fetch", self.source.as_ref(), self.branch.as_ref()])?;
self._run_in_target_repo(&["merge", "FETCH_HEAD"])?;
Ok(())
}
}
#[cfg(test)]
mod test {}
mod test {
use super::Checkout;
use crate::async_utils::run;
use crate::async_utils::sleep;
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::cell::RefCell;
use std::ffi::{OsStr, OsString};
use std::io::Result as IoResult;
use std::os::unix::process::ExitStatusExt;
use std::process::{ExitStatus, Output};
use std::time::{Duration, Instant};
struct DummyCommandRunner {
pub args: RefCell<Vec<Vec<OsString>>>,
}
#[async_trait(?Send)]
impl CommandRunner for DummyCommandRunner {
async fn run(&self, program: &str, args: &[&OsStr], stdin: &str) -> IoResult<Output> {
assert_eq!(program, "git");
assert_eq!(stdin, "");
sleep(Duration::from_millis(50)).await;
self
.args
.borrow_mut()
.push(args.iter().map(|a| a.to_os_string()).collect());
Ok(Output {
status: ExitStatus::from_raw(0),
stdout: vec![],
stderr: vec![],
})
}
}
#[test]
fn test() {
let c = DummyCommandRunner {
args: RefCell::new(vec![]),
};
let checkout: Checkout<DummyCommandRunner, _, _, _, _> =
Checkout::new("target", "source", "branch", &c);
let start = Instant::now();
assert!(run(checkout.target_reached()).unwrap());
let end = Instant::now();
assert_eq!(
c.args.into_inner(),
[
["-C", "target", "fetch", "source", "branch"],
["-C", "target", "rev-list", "-1", "HEAD"],
["-C", "target", "rev-list", "-1", "FETCH_HEAD"]
]
);
assert!((end - start).as_millis() >= 100);
assert!((end - start).as_millis() < 150);
}
}

View file

@ -1,4 +1,4 @@
mod checkout;
//pub mod submodules;
pub mod submodules;
pub use checkout::Checkout;

View file

@ -1,19 +1,20 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
use std::ffi::OsStr;
use std::fmt;
use std::path::Path;
use crate::command_runner::CommandRunner;
use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct GitSubmodules<'a, P: AsRef<Path>, C: CommandRunner> {
#[derive(Debug)]
pub struct GitSubmodules<'a, P, C> {
target: P,
command_runner: &'a C,
}
impl<'a, P: AsRef<Path>, C: CommandRunner> GitSubmodules<'a, P, C> {
impl<'a, P, C> GitSubmodules<'a, P, C> {
pub fn new(target: P, command_runner: &'a C) -> Self {
GitSubmodules {
Self {
target,
command_runner,
}
@ -27,20 +28,25 @@ impl<P: AsRef<Path>, C: CommandRunner> fmt::Display for GitSubmodules<'_, P, C>
}
impl<P: AsRef<Path>, C: CommandRunner> GitSubmodules<'_, P, C> {
fn _run_in_target_repo(&self, args: &[&OsStr]) -> Result<Vec<u8>, Box<dyn Error>> {
async fn _run_in_target_repo(&self, args: &[&OsStr]) -> Result<Vec<u8>, Box<dyn Error>> {
let mut new_args: Vec<&OsStr> = vec![];
new_args.extend_from_slice(args!["-C", self.target.as_ref()]);
new_args.extend_from_slice(args);
self.command_runner.get_output("git", &new_args)
self.command_runner.get_output("git", &new_args).await
}
}
#[async_trait(?Send)]
impl<P: AsRef<Path>, C: CommandRunner> Symbol for GitSubmodules<'_, P, C> {
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() {
return Ok(false);
}
let output = String::from_utf8(self._run_in_target_repo(args!["submodule", "status"])?)?;
let output = String::from_utf8(
self
._run_in_target_repo(args!["submodule", "status"])
.await?,
)?;
Ok(
output
.lines()
@ -48,21 +54,12 @@ impl<P: AsRef<Path>, C: CommandRunner> Symbol for GitSubmodules<'_, P, C> {
)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self._run_in_target_repo(args!["submodule", "update", "--init"])?;
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
._run_in_target_repo(args!["submodule", "update", "--init"])
.await?;
Ok(())
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
#[cfg(test)]

View file

@ -1,6 +1,7 @@
use crate::command_runner::CommandRunner;
use crate::storage::Storage;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
#[derive(Debug)]
@ -19,37 +20,45 @@ impl<'a, D, S, C: CommandRunner> Database<'a, D, S, C> {
}
}
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
async fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
let b = self
.command_runner
.get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])?;
.get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])
.await?;
Ok(String::from_utf8(b)?)
}
}
#[async_trait(?Send)]
impl<D: AsRef<str>, S: Storage, C: CommandRunner> Symbol for Database<'_, D, S, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(
self
.run_sql(&format!("SHOW DATABASES LIKE '{}'", self.db_name.as_ref()))?
.run_sql(&format!("SHOW DATABASES LIKE '{}'", self.db_name.as_ref()))
.await?
.trim_end()
== self.db_name.as_ref(),
)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.run_sql(&format!("CREATE DATABASE {}", self.db_name.as_ref()))?;
self.command_runner.run_successfully(
"sh",
args![
"-c",
format!(
"mariadb '{}' < {}",
self.db_name.as_ref(),
self.seed_file.read_filename()?.to_str().unwrap()
),
],
)
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.run_sql(&format!("CREATE DATABASE {}", self.db_name.as_ref()))
.await?;
self
.command_runner
.run_successfully(
"sh",
args![
"-c",
format!(
"mariadb '{}' < {}",
self.db_name.as_ref(),
self.seed_file.read_filename()?.to_str().unwrap()
),
],
)
.await
}
}

View file

@ -1,9 +1,9 @@
use std::error::Error;
use std::str::FromStr;
use crate::command_runner::CommandRunner;
use crate::storage::Storage;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
use std::str::FromStr;
#[derive(Debug)]
pub struct Dump<'a, N, C, S> {
@ -21,34 +21,39 @@ impl<'a, N, C: CommandRunner, S> Dump<'a, N, C, S> {
}
}
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
async fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
let b = self
.command_runner
.get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])?;
.get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])
.await?;
Ok(String::from_utf8(b)?)
}
}
#[async_trait(?Send)]
impl<N: AsRef<str>, C: CommandRunner, S: Storage> Symbol for Dump<'_, N, C, S> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let dump_date = self.storage.recent_date()?;
let _modified_date = self.run_sql(&format!("select UNIX_TIMESTAMP(MAX(UPDATE_TIME)) from information_schema.tables WHERE table_schema = '{}'", self.db_name.as_ref()))?;
let _modified_date = self.run_sql(&format!("select UNIX_TIMESTAMP(MAX(UPDATE_TIME)) from information_schema.tables WHERE table_schema = '{}'", self.db_name.as_ref())).await?;
let modified_date = _modified_date.trim_end();
Ok(modified_date != "NULL" && u64::from_str(modified_date)? <= dump_date)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
"sh",
args![
"-c",
format!(
"mysqldump '{}' > {}",
self.db_name.as_ref(),
self.storage.write_filename().to_str().unwrap()
),
],
)
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.command_runner
.run_successfully(
"sh",
args![
"-c",
format!(
"mysqldump '{}' > {}",
self.db_name.as_ref(),
self.storage.write_filename().to_str().unwrap()
),
],
)
.await
}
}

View file

@ -1,5 +1,6 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
#[derive(Debug)]
@ -16,32 +17,37 @@ impl<'a, U: AsRef<str>, C: CommandRunner> User<'a, U, C> {
}
}
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
async fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
let b = self
.command_runner
.get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])?;
.get_output("mariadb", args!["--skip-column-names", "-B", "-e", sql])
.await?;
Ok(String::from_utf8(b)?)
}
}
#[async_trait(?Send)]
impl<U: AsRef<str>, C: CommandRunner> Symbol for User<'_, U, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(
self
.run_sql(&format!(
"SELECT User FROM mysql.user WHERE User = '{}' AND plugin = 'unix_socket'",
self.user_name.as_ref()
))?
))
.await?
.trim_end()
== self.user_name.as_ref(),
)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.run_sql(&format!(
"GRANT ALL ON {0}.* TO {0} IDENTIFIED VIA unix_socket",
self.user_name.as_ref()
))?;
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.run_sql(&format!(
"GRANT ALL ON {0}.* TO {0} IDENTIFIED VIA unix_socket",
self.user_name.as_ref()
))
.await?;
Ok(())
}
}

View file

@ -1,9 +1,11 @@
use async_trait::async_trait;
use std::error::Error;
// Symbol
#[async_trait(?Send)]
pub trait Symbol {
fn target_reached(&self) -> Result<bool, Box<dyn Error>>;
fn execute(&self) -> Result<(), Box<dyn Error>>;
async fn target_reached(&self) -> Result<bool, Box<dyn Error>>;
async fn execute(&self) -> Result<(), Box<dyn Error>>;
}
pub mod acme;

View file

@ -1,5 +1,6 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
use std::fmt;
use std::path::Path;
@ -29,37 +30,39 @@ impl<T: AsRef<Path>, C: CommandRunner> fmt::Display for Install<'_, T, C> {
}
}
#[async_trait(?Send)]
impl<T: AsRef<Path>, C: CommandRunner> Symbol for Install<'_, T, C> {
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() {
return Ok(false);
}
let result = self.command_runner.run_with_args(
"sh",
args![
"-c",
format!("cd '{}' && npm ls", self.target.as_ref().to_str().unwrap()),
],
)?;
Ok(
result.status.success()
&& !String::from_utf8(result.stdout)
.unwrap()
.contains("(empty)"),
)
let output = self
.command_runner
.get_output(
"sh",
args![
"-c",
format!("cd '{}' && npm ls", self.target.as_ref().to_str().unwrap()),
],
)
.await?;
Ok(!String::from_utf8(output).unwrap().contains("(empty)"))
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
"sh",
args![
"-c",
format!(
"cd '{}' && npm install --production --unsafe-perm",
self.target.as_ref().to_str().unwrap()
),
],
)
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.command_runner
.run_successfully(
"sh",
args![
"-c",
format!(
"cd '{}' && npm install --production --unsafe-perm",
self.target.as_ref().to_str().unwrap()
),
],
)
.await
}
}

View file

@ -1,5 +1,6 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::borrow::Borrow;
use std::error::Error;
use std::fs;
@ -27,10 +28,11 @@ impl<_C, C, P, U> Owner<_C, C, P, U> {
}
}
#[async_trait(?Send)]
impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, U: AsRef<str>> Symbol
for Owner<_C, C, P, U>
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.path.as_ref().exists() {
return Ok(false);
}
@ -39,10 +41,14 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, U: AsRef<str>> Symbol
Ok(actual_uid == target_uid)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.borrow().run_successfully(
"chown",
args!["-R", self.user_name.as_ref(), self.path.as_ref()],
)
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.command_runner
.borrow()
.run_successfully(
"chown",
args!["-R", self.user_name.as_ref(), self.path.as_ref()],
)
.await
}
}

View file

@ -1,8 +1,10 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub struct PostgreSQLDatabase<'a, N: AsRef<str>, S: AsRef<str>, C: CommandRunner> {
name: N,
seed_file: S,
@ -18,11 +20,14 @@ impl<'a, N: AsRef<str>, S: AsRef<str>, C: CommandRunner> PostgreSQLDatabase<'a,
}
}
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
let b = self.command_runner.get_output(
"su",
args!["-", "postgres", "-c", format!("psql -t -c \"{}\"", sql)],
)?;
async fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
let b = self
.command_runner
.get_output(
"su",
args!["-", "postgres", "-c", format!("psql -t -c \"{}\"", sql)],
)
.await?;
Ok(String::from_utf8(b)?)
}
}
@ -35,54 +40,65 @@ impl<N: AsRef<str>, S: AsRef<str>, C: CommandRunner> fmt::Display
}
}
#[async_trait(?Send)]
impl<N: AsRef<str>, S: AsRef<str>, C: CommandRunner> Symbol for PostgreSQLDatabase<'_, N, S, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(
self
.run_sql(&format!(
"SELECT datname FROM pg_database WHERE datname LIKE '{}'",
self.name.as_ref()
))?
))
.await?
.trim()
== self.name.as_ref(),
)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
"su",
args![
"-",
"postgres",
"-c",
format!("createuser {}", self.name.as_ref())
],
)?;
self.command_runner.run_successfully(
"su",
args![
"-",
"postgres",
"-c",
format!(
"createdb -E UTF8 -T template0 -O {} {0}",
self.name.as_ref()
),
],
)?;
self.command_runner.run_successfully(
"su",
args![
"-",
"postgres",
"-c",
format!(
"psql '{}' < {}",
self.name.as_ref(),
self.seed_file.as_ref()
),
],
)
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.command_runner
.run_successfully(
"su",
args![
"-",
"postgres",
"-c",
format!("createuser {}", self.name.as_ref())
],
)
.await?;
self
.command_runner
.run_successfully(
"su",
args![
"-",
"postgres",
"-c",
format!(
"createdb -E UTF8 -T template0 -O {} {0}",
self.name.as_ref()
),
],
)
.await?;
self
.command_runner
.run_successfully(
"su",
args![
"-",
"postgres",
"-c",
format!(
"psql '{}' < {}",
self.name.as_ref(),
self.seed_file.as_ref()
),
],
)
.await
}
}

View file

@ -1,6 +1,7 @@
use crate::command_runner::CommandRunner;
use crate::storage::Storage;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::borrow::Borrow;
use std::error::Error;
use std::fs;
@ -36,10 +37,11 @@ impl<_C, C, P, S> SavedDirectory<_C, C, P, S> {
}
}
#[async_trait(?Send)]
impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, S: Storage> Symbol
for SavedDirectory<_C, C, P, S>
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let metadata = fs::metadata(self.path.as_ref());
// Check if dir exists
if let Err(e) = metadata {
@ -57,26 +59,34 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, S: Storage> Symbol
}
let dump_date = self.storage.recent_date()?;
let output = self.command_runner.borrow().get_output(
"sh",
args![
"-c",
format!(
"find {} -printf '%T@\\n' | sort -r | head -n1 | grep '^[0-9]\\+' -o",
self.path.as_ref().to_str().unwrap()
),
],
)?;
let output = self
.command_runner
.borrow()
.get_output(
"sh",
args![
"-c",
format!(
"find {} -printf '%T@\\n' | sort -r | head -n1 | grep '^[0-9]\\+' -o",
self.path.as_ref().to_str().unwrap()
),
],
)
.await?;
let modified_date = u64::from_str(String::from_utf8(output)?.trim_end())?;
if if self.dir == StorageDirection::Store {
modified_date > dump_date
} else {
dump_date > modified_date
} {
let output = self.command_runner.borrow().run_with_args(
"diff",
args!["-rq", self.storage.read_filename()?, self.path.as_ref()],
)?;
let output = self
.command_runner
.borrow()
.run_with_args(
"diff",
args!["-rq", self.storage.read_filename()?, self.path.as_ref()],
)
.await?;
match output.status.code() {
Some(0) => Ok(true),
Some(1) => Ok(false),
@ -87,21 +97,30 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, S: Storage> Symbol
}
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
async fn execute(&self) -> Result<(), Box<dyn Error>> {
if self.dir == StorageDirection::Load {
self
.command_runner
.borrow()
.run_successfully("rm", args!["-rf", self.path.as_ref()])?;
self.command_runner.borrow().run_successfully(
"cp",
args!["-a", self.storage.read_filename()?, self.path.as_ref()],
)
.run_successfully("rm", args!["-rf", self.path.as_ref()])
.await?;
self
.command_runner
.borrow()
.run_successfully(
"cp",
args!["-a", self.storage.read_filename()?, self.path.as_ref()],
)
.await
} else {
self.command_runner.borrow().run_successfully(
"cp",
args!["-a", self.path.as_ref(), self.storage.write_filename()],
)
self
.command_runner
.borrow()
.run_successfully(
"cp",
args!["-a", self.path.as_ref(), self.storage.write_filename()],
)
.await
}
}
}

View file

@ -1,5 +1,6 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::borrow::Borrow;
use std::error::Error;
use std::marker::PhantomData;
@ -21,15 +22,20 @@ impl<_C, C, S> ReloadService<_C, C, S> {
}
}
#[async_trait(?Send)]
impl<S: AsRef<str>, _C: CommandRunner, C: Borrow<_C>> Symbol for ReloadService<_C, C, S> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(true)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.borrow().run_successfully(
"systemctl",
args!["reload-or-restart", self.service.as_ref()],
)
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.command_runner
.borrow()
.run_successfully(
"systemctl",
args!["reload-or-restart", self.service.as_ref()],
)
.await
}
}

View file

@ -1,9 +1,10 @@
use crate::async_utils::sleep;
use crate::command_runner::{CommandRunner, SetuidCommandRunner};
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
use std::ffi::OsStr;
use std::path::Path;
use std::thread::sleep;
use std::time::Duration;
#[derive(Debug)]
@ -29,10 +30,10 @@ impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> UserService<'static, S, U,
}
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> UserService<'_, S, U, R> {
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;
loop {
let result = self.command_runner.run_with_args("systemctl", args)?;
let result = self.command_runner.run_with_args("systemctl", args).await?;
if result.status.success() {
return Ok(String::from_utf8(result.stdout)?.trim_end().to_string());
} else {
@ -46,28 +47,35 @@ impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> UserService<'_, S, U, R> {
if tries == 0 {
return Err("Gave up waiting for dbus to appear".to_string().into());
}
sleep(Duration::from_millis(500));
sleep(Duration::from_millis(500)).await;
}
}
fn check_if_service(&self) -> Result<bool, Box<dyn Error>> {
async fn check_if_service(&self) -> Result<bool, Box<dyn Error>> {
loop {
let active_state = self.systemctl_wait_for_dbus(args![
"--user",
"show",
"--property",
"ActiveState",
self.service_name,
])?;
let active_state = self
.systemctl_wait_for_dbus(args![
"--user",
"show",
"--property",
"ActiveState",
self.service_name,
])
.await?;
match active_state.as_ref() {
"ActiveState=activating" => sleep(Duration::from_millis(500)),
"ActiveState=activating" => sleep(Duration::from_millis(500)).await,
"ActiveState=active" => return Ok(true),
"ActiveState=failed" => {
return Err(
String::from_utf8(self.command_runner.get_output(
"journalctl",
args!["--user", format!("--user-unit={}", self.service_name)],
)?)?
String::from_utf8(
self
.command_runner
.get_output(
"journalctl",
args!["--user", format!("--user-unit={}", self.service_name)],
)
.await?,
)?
.into(),
)
}
@ -77,24 +85,29 @@ impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> UserService<'_, S, U, R> {
}
}
#[async_trait(?Send)]
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> Symbol for UserService<'_, S, U, R> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
self.check_if_service()
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
self.check_if_service().await
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.systemctl_wait_for_dbus(args!["--user", "enable", self.service_name])?;
self.systemctl_wait_for_dbus(args!["--user", "restart", self.service_name])?;
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.systemctl_wait_for_dbus(args!["--user", "enable", self.service_name])
.await?;
self
.systemctl_wait_for_dbus(args!["--user", "restart", self.service_name])
.await?;
loop {
if !(self.check_if_service()?) {
if !(self.check_if_service().await?) {
return Err("Generic error".into());
}
if self.socket_path.as_ref().exists() {
return Ok(());
}
sleep(Duration::from_millis(500));
sleep(Duration::from_millis(500)).await;
}
}
}

View file

@ -1,5 +1,6 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
use std::path::Path;
@ -18,16 +19,18 @@ impl<'a, U, C> UserSession<'a, U, C> {
}
}
#[async_trait(?Send)]
impl<U: AsRef<str>, C: CommandRunner> Symbol for UserSession<'_, U, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let path = Path::new("/var/lib/systemd/linger").join(self.user_name.as_ref());
Ok(path.exists())
// Could also do `loginctl show-user ${self.user_name} | grep -F 'Linger=yes`
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.command_runner
.run_successfully("loginctl", args!["enable-linger", self.user_name.as_ref()])
.await
}
}

View file

@ -1,5 +1,6 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::borrow::Borrow;
use std::error::Error;
use std::path::Path;
@ -23,37 +24,43 @@ impl<C, D, K, P> Csr<C, D, K, P> {
}
}
#[async_trait(?Send)]
impl<C: CommandRunner, D: Borrow<str>, K: Borrow<Path>, P: Borrow<Path>> Symbol
for Csr<C, D, K, P>
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.csr_path.borrow().exists() {
return Ok(false);
}
let output = self.command_runner.get_stderr(
"openssl",
args!["req", "-in", self.csr_path.borrow(), "-noout", "-verify",],
)?;
let output = self
.command_runner
.get_stderr(
"openssl",
args!["req", "-in", self.csr_path.borrow(), "-noout", "-verify",],
)
.await?;
Ok(output == b"verify OK\n")
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
"openssl",
args![
"req",
"-new",
"-sha256",
"-key",
self.key_path.borrow(),
"-out",
self.csr_path.borrow(),
"-subj",
format!("/CN={}", self.domain.borrow()),
],
)?;
Ok(())
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.command_runner
.run_successfully(
"openssl",
args![
"req",
"-new",
"-sha256",
"-key",
self.key_path.borrow(),
"-out",
self.csr_path.borrow(),
"-subj",
format!("/CN={}", self.domain.borrow()),
],
)
.await
}
}

View file

@ -1,5 +1,6 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
use std::path::Path;
@ -22,37 +23,44 @@ impl<C, P> Key<C, P> {
}
}
#[async_trait(?Send)]
impl<C: CommandRunner, P: AsRef<Path>> Symbol for Key<C, P> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.file_path.as_ref().exists() {
return Ok(false);
}
let stdout = self.command_runner.get_output(
"openssl",
args![
"rsa",
"-in",
self.file_path.as_ref(),
"-noout",
"-check",
"-text",
],
)?;
let stdout = self
.command_runner
.get_output(
"openssl",
args![
"rsa",
"-in",
self.file_path.as_ref(),
"-noout",
"-check",
"-text",
],
)
.await?;
// FIXME check bytes
Ok(stdout.ends_with(b"RSA key ok\n"))
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
"openssl",
args![
"genrsa",
"-out",
self.file_path.as_ref(),
self.get_bytes().to_string(),
],
)
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.command_runner
.run_successfully(
"openssl",
args![
"genrsa",
"-out",
self.file_path.as_ref(),
self.get_bytes().to_string(),
],
)
.await
}
}

View file

@ -1,5 +1,6 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
#[derive(Debug)]
@ -17,11 +18,13 @@ impl<U, C> User<U, C> {
}
}
#[async_trait(?Send)]
impl<U: AsRef<str>, C: CommandRunner> Symbol for User<U, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let output = self
.command_runner
.run_with_args("getent", args!["passwd", self.user_name.as_ref()])?;
.run_with_args("getent", args!["passwd", self.user_name.as_ref()])
.await?;
match output.status.code() {
Some(2) => Ok(false),
Some(0) => Ok(true),
@ -29,21 +32,24 @@ impl<U: AsRef<str>, C: CommandRunner> Symbol for User<U, C> {
}
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully(
"adduser",
args![
// "-m", // Necessary for Fedora, not accepted in Debian
"--system",
self.user_name.as_ref(),
],
)?;
Ok(())
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.command_runner
.run_successfully(
"adduser",
args![
// "-m", // Necessary for Fedora, not accepted in Debian
"--system",
self.user_name.as_ref(),
],
)
.await
}
}
#[cfg(test)]
mod test {
use crate::async_utils::run;
use crate::command_runner::StdCommandRunner;
use crate::symbols::user::User;
use crate::symbols::Symbol;
@ -54,7 +60,7 @@ mod test {
user_name: "nonexisting",
command_runner: StdCommandRunner,
};
assert_eq!(symbol.target_reached().unwrap(), false);
assert_eq!(run(symbol.target_reached()).unwrap(), false);
}
#[test]
@ -63,6 +69,6 @@ mod test {
user_name: "root",
command_runner: StdCommandRunner,
};
assert_eq!(symbol.target_reached().unwrap(), true);
assert_eq!(run(symbol.target_reached()).unwrap(), true);
}
}

View file

@ -1,3 +1,6 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use regex::Regex;
use std::error::Error;
use std::fs::File as FsFile;
@ -5,9 +8,6 @@ use std::io;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
#[derive(Debug)]
pub struct Plugin<'a, P, N, R> {
base: P,
@ -33,8 +33,9 @@ impl<'a, P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Plugin<'a, P, N, R> {
}
}
#[async_trait(?Send)]
impl<P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Symbol for Plugin<'_, P, N, R> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let base_path = self.get_path();
if !base_path.exists() {
return Ok(false);
@ -65,23 +66,26 @@ impl<P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Symbol for Plugin<'_, P, N
}
}
}
let upstream = self.command_runner.get_output(
"curl",
args![
"--form",
format!(
r###"plugins={{"plugins":{{"{0}/{0}.php":{{"Version":"{1}", "PluginURI":"{2}"}}}}}}"###,
self.name.as_ref(),
version,
plugin_uri
),
"https://api.wordpress.org/plugins/update-check/1.1/",
],
)?;
let upstream = self
.command_runner
.get_output(
"curl",
args![
"--form",
format!(
r###"plugins={{"plugins":{{"{0}/{0}.php":{{"Version":"{1}", "PluginURI":"{2}"}}}}}}"###,
self.name.as_ref(),
version,
plugin_uri
),
"https://api.wordpress.org/plugins/update-check/1.1/",
],
)
.await?;
Ok(String::from_utf8(upstream)?.contains(r###""plugins":[]"###))
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
async fn execute(&self) -> Result<(), Box<dyn Error>> {
let source = format!(
"https://downloads.wordpress.org/plugin/{}.zip",
self.name.as_ref()
@ -89,13 +93,18 @@ impl<P: AsRef<Path>, N: AsRef<str>, R: CommandRunner> Symbol for Plugin<'_, P, N
let zip = format!("/tmp/{}.zip", self.name.as_ref());
self
.command_runner
.run_successfully("curl", args![source, "-o", zip])?;
.run_successfully("curl", args![source, "-o", zip])
.await?;
self
.command_runner
.run_successfully("rm", args!["-rf", self.get_path()])?;
self.command_runner.run_successfully(
"unzip",
args![zip, "-d", self.base.as_ref().join("wp-content/plugins")],
)
.run_successfully("rm", args!["-rf", self.get_path()])
.await?;
self
.command_runner
.run_successfully(
"unzip",
args![zip, "-d", self.base.as_ref().join("wp-content/plugins")],
)
.await
}
}

View file

@ -1,5 +1,6 @@
use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use regex::Regex;
use std::cmp::max;
use std::error::Error;
@ -59,8 +60,9 @@ impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Translation<'_, C, D, R> {
}
}
#[async_trait(?Send)]
impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Symbol for Translation<'_, C, D, R> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let mut newest = String::new();
let match_date = Regex::new("(?m)^\"PO-Revision-Date: (.+)\\+0000\\\\n\"$").unwrap();
for (_, target) in self.get_pairs() {
@ -86,14 +88,17 @@ impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Symbol for Translation<'_,
}
}
}
let upstream = self.command_runner.get_output(
"curl",
args![format!(
"https://api.wordpress.org/core/version-check/1.7/?version={}&locale={}",
self.version,
self.locale.as_ref()
)],
)?;
let upstream = self
.command_runner
.get_output(
"curl",
args![format!(
"https://api.wordpress.org/core/version-check/1.7/?version={}&locale={}",
self.version,
self.locale.as_ref()
)],
)
.await?;
Ok(String::from_utf8(upstream)?.contains(&format!(
r###"language":"{}","version":"{}","updated":"{}"###,
self.locale.as_ref(),
@ -102,11 +107,12 @@ impl<C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Symbol for Translation<'_,
)))
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
async fn execute(&self) -> Result<(), Box<dyn Error>> {
for (source, target) in self.get_pairs() {
self
.command_runner
.run_successfully("curl", args!["--compressed", "-o", target, source,])?;
.run_successfully("curl", args!["--compressed", "-o", target, source,])
.await?;
}
Ok(())
}