Browse Source

Cargo format

master
Adrian Heine 5 years ago
parent
commit
8c0224e983
  1. 1
      rustfmt.toml
  2. 19
      src/bin.rs
  3. 24
      src/build.rs
  4. 104
      src/command_runner.rs
  5. 80
      src/factory.rs
  6. 23
      src/lib.rs
  7. 9
      src/loggers.rs
  8. 14
      src/repository.rs
  9. 150
      src/schema.rs
  10. 22
      src/storage.rs
  11. 42
      src/symbols/acme/account_key.rs
  12. 75
      src/symbols/acme/cert.rs
  13. 65
      src/symbols/acme/chain.rs
  14. 49
      src/symbols/acme/user.rs
  15. 38
      src/symbols/dir.rs
  16. 221
      src/symbols/factory.rs
  17. 47
      src/symbols/file.rs
  18. 48
      src/symbols/git/checkout.rs
  19. 25
      src/symbols/git/submodules.rs
  20. 87
      src/symbols/hook.rs
  21. 31
      src/symbols/list.rs
  22. 33
      src/symbols/mariadb/database.rs
  23. 45
      src/symbols/mariadb/database_dump.rs
  24. 2
      src/symbols/mariadb/mod.rs
  25. 35
      src/symbols/mariadb/user.rs
  26. 10
      src/symbols/mod.rs
  27. 110
      src/symbols/nginx/server.rs
  28. 8
      src/symbols/noop.rs
  29. 37
      src/symbols/npm.rs
  30. 44
      src/symbols/owner.rs
  31. 55
      src/symbols/postgresql/database.rs
  32. 45
      src/symbols/postgresql/database_dump.rs
  33. 107
      src/symbols/stored_directory.rs
  34. 119
      src/symbols/systemd/node_js_user_service.rs
  35. 18
      src/symbols/systemd/reload.rs
  36. 26
      src/symbols/systemd/user_session.rs
  37. 35
      src/symbols/tls/csr.rs
  38. 30
      src/symbols/tls/key.rs
  39. 75
      src/symbols/tls/self_signed_cert.rs
  40. 87
      src/symbols/user.rs
  41. 83
      src/symbols/wordpress/plugin.rs
  42. 112
      src/symbols/wordpress/translation.rs
  43. 13
      tests/file.rs
  44. 96
      tests/storage.rs

1
rustfmt.toml

@ -0,0 +1 @@
tab_spaces = 2

19
src/bin.rs

@ -1,13 +1,22 @@
use std::process::exit;
use std::env;
use std::process::exit;
pub fn schematics_main(run: &dyn Fn (bool) -> Result<(), ()>) {
pub fn schematics_main(run: &dyn Fn(bool) -> Result<(), ()>) {
let args: Vec<String> = env::args().collect();
let dry_run = match args.len() {
1 => false,
2 => if args[1] == "--dry-run" { true } else { panic!() },
_ => panic!()
2 => {
if args[1] == "--dry-run" {
true
} else {
panic!()
}
}
_ => panic!(),
};
exit(match run(dry_run) { Ok(_) => 0, Err(_) => 1 });
exit(match run(dry_run) {
Ok(_) => 0,
Err(_) => 1,
});
}

24
src/build.rs

@ -7,7 +7,13 @@ use std::path::{Path, PathBuf};
fn get_const_name<P: Clone + Into<PathBuf>>(p: &P) -> String {
let mut file_name_without_extension = p.clone().into();
file_name_without_extension.set_extension("");
String::from(file_name_without_extension.file_name().unwrap().to_string_lossy()).to_uppercase()
String::from(
file_name_without_extension
.file_name()
.unwrap()
.to_string_lossy(),
)
.to_uppercase()
}
pub fn create_static_output_files(source_dir: &str) {
@ -17,8 +23,20 @@ pub fn create_static_output_files(source_dir: &str) {
for maybe_dir_entry in read_dir(source_dir).unwrap() {
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();
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();
f.write_all(
format!(
"pub const {}: &str = r{1}\"{2}\"{1};\n",
get_const_name(&file_path),
fence,
buffer
)
.as_bytes(),
)
.unwrap();
}
}

104
src/command_runner.rs

@ -5,22 +5,35 @@ use std::process::Command;
use std::process::Output;
pub trait CommandRunner {
fn run_with_args<S: AsRef<OsStr> + ?Sized>(&self, program: &str, args: &[&S]) -> IoResult<Output>;
fn get_output<S: AsRef<OsStr> + ?Sized>(&self, program: &str, args: &[&S]) -> Result<Vec<u8>, Box<dyn Error>> {
fn run_with_args<S: AsRef<OsStr> + ?Sized>(&self, program: &str, args: &[&S])
-> IoResult<Output>;
fn get_output<S: AsRef<OsStr> + ?Sized>(
&self,
program: &str,
args: &[&S],
) -> Result<Vec<u8>, Box<dyn Error>> {
let output = try!(self.run_with_args(program, args));
if !output.status.success() {
return Err(try!(String::from_utf8(output.stderr)).into());
}
Ok(output.stdout)
}
fn get_stderr<S: AsRef<OsStr> + ?Sized>(&self, program: &str, args: &[&S]) -> Result<Vec<u8>, Box<dyn Error>> {
fn get_stderr<S: AsRef<OsStr> + ?Sized>(
&self,
program: &str,
args: &[&S],
) -> Result<Vec<u8>, Box<dyn Error>> {
let output = try!(self.run_with_args(program, args));
if !output.status.success() {
return Err(try!(String::from_utf8(output.stderr)).into());
}
Ok(output.stderr)
}
fn run_successfully<S: AsRef<OsStr> + ?Sized>(&self, program: &str, args: &[&S]) -> Result<(), Box<dyn Error>> {
fn run_successfully<S: AsRef<OsStr> + ?Sized>(
&self,
program: &str,
args: &[&S],
) -> Result<(), Box<dyn Error>> {
let output = try!(self.run_with_args(program, args));
if output.status.success() {
Ok(())
@ -34,7 +47,11 @@ pub trait CommandRunner {
pub struct StdCommandRunner;
impl CommandRunner for StdCommandRunner {
fn run_with_args<S: AsRef<OsStr> + ?Sized>(&self, program: &str, args: &[&S]) -> IoResult<Output> {
fn run_with_args<S: AsRef<OsStr> + ?Sized>(
&self,
program: &str,
args: &[&S],
) -> IoResult<Output> {
// FIXME: logger
//println!("{} {:?}", program, args);
let res = Command::new(program).args(args).output();
@ -44,28 +61,43 @@ impl CommandRunner for StdCommandRunner {
}
#[derive(Debug)]
pub struct SetuidCommandRunner<'a, C> where C: 'a + CommandRunner {
pub struct SetuidCommandRunner<'a, C>
where
C: 'a + CommandRunner,
{
command_runner: &'a C,
user_name: &'a str
user_name: &'a str,
}
impl<'a, C> SetuidCommandRunner<'a, C> where C: 'a + CommandRunner {
impl<'a, C> SetuidCommandRunner<'a, C>
where
C: 'a + CommandRunner,
{
pub fn new(user_name: &'a str, command_runner: &'a C) -> SetuidCommandRunner<'a, C> {
SetuidCommandRunner { command_runner, user_name }
SetuidCommandRunner {
command_runner,
user_name,
}
}
}
use std::os::unix::process::CommandExt;
use std::env;
use std::os::unix::process::CommandExt;
use users::get_user_by_name;
struct TempSetEnv<'a> { name: &'a str, old_value: Option<String> }
struct TempSetEnv<'a> {
name: &'a str,
old_value: Option<String>,
}
impl<'a> TempSetEnv<'a> {
fn new(name: &'a str, new_value: String) -> TempSetEnv<'a> {
let old_value = env::var(name);
env::set_var(name, new_value);
TempSetEnv { name, old_value: old_value.ok() }
TempSetEnv {
name,
old_value: old_value.ok(),
}
}
}
@ -73,14 +105,23 @@ impl<'a> Drop for TempSetEnv<'a> {
fn drop(&mut self) {
match self.old_value {
Some(ref val) => env::set_var(self.name, val),
None => env::remove_var(self.name)
None => env::remove_var(self.name),
}
}
}
impl<'a, C> CommandRunner for SetuidCommandRunner<'a, C> where C: 'a + CommandRunner {
fn run_with_args<S: AsRef<OsStr> + ?Sized>(&self, program: &str, args: &[&S]) -> IoResult<Output> {
let uid = get_user_by_name(self.user_name).expect("User does not exist").uid();
impl<'a, C> CommandRunner for SetuidCommandRunner<'a, C>
where
C: 'a + CommandRunner,
{
fn run_with_args<S: AsRef<OsStr> + ?Sized>(
&self,
program: &str,
args: &[&S],
) -> IoResult<Output> {
let uid = get_user_by_name(self.user_name)
.expect("User does not exist")
.uid();
let set_home = TempSetEnv::new("HOME", format!("/home/{}", self.user_name));
let set_dbus = TempSetEnv::new("XDG_RUNTIME_DIR", format!("/run/user/{}", uid));
//println!("{} {:?}", program, args);
@ -91,21 +132,37 @@ impl<'a, C> CommandRunner for SetuidCommandRunner<'a, C> where C: 'a + CommandRu
}
#[derive(Debug)]
pub struct SuCommandRunner<'a, C> where C: 'a + CommandRunner {
pub struct SuCommandRunner<'a, C>
where
C: 'a + CommandRunner,
{
command_runner: &'a C,
user_name: &'a str
user_name: &'a str,
}
impl<'a, C> SuCommandRunner<'a, C> where C: 'a + CommandRunner {
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 }
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
impl<'a, C> CommandRunner for SuCommandRunner<'a, C> where C: 'a + CommandRunner {
fn run_with_args<S: AsRef<OsStr> + ?Sized>(&self, program: &str, args: &[&S]) -> IoResult<Output> {
impl<'a, C> CommandRunner for SuCommandRunner<'a, C>
where
C: 'a + CommandRunner,
{
fn run_with_args<S: AsRef<OsStr> + ?Sized>(
&self,
program: &str,
args: &[&S],
) -> IoResult<Output> {
let raw_new_args = [self.user_name, "-s", "/usr/bin/env", "--", program];
let mut new_args: Vec<&OsStr> = raw_new_args.iter().map(|s| s.as_ref()).collect();
let old_args: Vec<&OsStr> = args.iter().map(|s| s.as_ref()).collect();
@ -115,5 +172,4 @@ impl<'a, C> CommandRunner for SuCommandRunner<'a, C> where C: 'a + CommandRunner
}
#[cfg(test)]
mod test {
}
mod test {}

80
src/factory.rs

@ -5,29 +5,35 @@ use loggers::StdErrLogger;
use repository::SymbolRepository;
use resources::Resource;
use schema::{NonRepeatingSymbolRunner, ReportingSymbolRunner, RequirementsResolvingSymbolRunner};
use symbols::{Symbol, SymbolRunner};
use symbols::dir::Dir;
use symbols::list::List;
use symbols::owner::Owner;
use symbols::systemd::user_session::SystemdUserSession;
use symbols::tls::{TlsCsr, TlsKey};
use symbols::user::{User, UserAdder};
use symbols::user::SystemUserAdder;
use symbols::user::{User, UserAdder};
use symbols::{Symbol, SymbolRunner};
#[derive(Default)]
pub struct Factory {
}
pub struct Factory {}
impl Factory {
pub fn new() -> Self {
Default::default()
}
pub fn get_repo<'a, CR: CommandRunner>(&self, command_runner: &'a CR) -> DefaultSymbolRepository<'a, SystemUserAdder<'a, CR>, CR> {
pub fn get_repo<'a, CR: CommandRunner>(
&self,
command_runner: &'a CR,
) -> DefaultSymbolRepository<'a, SystemUserAdder<'a, CR>, CR> {
DefaultSymbolRepository::new(command_runner)
}
pub fn get_symbol_runner<'a, RUNNER: SymbolRunner, REPO: SymbolRepository<'a>>(&self, symbol_runner: &'a RUNNER, repo: &'a REPO) -> Box<dyn 'a + SymbolRunner> {
pub fn get_symbol_runner<'a, RUNNER: SymbolRunner, REPO: SymbolRepository<'a>>(
&self,
symbol_runner: &'a RUNNER,
repo: &'a REPO,
) -> Box<dyn 'a + SymbolRunner> {
let runner1 = ReportingSymbolRunner::new(symbol_runner, StdErrLogger);
let runner2 = NonRepeatingSymbolRunner::new(runner1);
Box::new(RequirementsResolvingSymbolRunner::new(runner2, repo))
@ -42,7 +48,7 @@ pub struct DefaultSymbolRepository<'a, A: 'a + UserAdder, C: 'a + CommandRunner>
home_config: Regex,
csr: Regex,
private_key: Regex,
systemd_linger: Regex
systemd_linger: Regex,
}
impl<'a, C: 'a + CommandRunner> DefaultSymbolRepository<'a, SystemUserAdder<'a, C>, C> {
@ -54,54 +60,64 @@ impl<'a, C: 'a + CommandRunner> DefaultSymbolRepository<'a, SystemUserAdder<'a,
home_config: Regex::new("^/home/([^/]+)/.config(?:/|$)").unwrap(),
csr: Regex::new("^/etc/ssl/local_certs/([^/]+).csr$").unwrap(),
private_key: Regex::new("^/etc/ssl/private/([^/]+).key$").unwrap(),
systemd_linger: Regex::new("^/var/lib/systemd/linger/([^/]+)$").unwrap()
systemd_linger: Regex::new("^/var/lib/systemd/linger/([^/]+)$").unwrap(),
}
}
}
impl<'a, C: CommandRunner> SymbolRepository<'a> for DefaultSymbolRepository<'a, SystemUserAdder<'a, C>, C> {
impl<'a, C: CommandRunner> SymbolRepository<'a>
for DefaultSymbolRepository<'a, SystemUserAdder<'a, C>, C>
{
fn get_symbol(&'a self, resource: &Resource) -> Option<Box<dyn Symbol + 'a>> {
match resource.get_type() {
"user" => Some(Box::new(User::new(
resource.get_value().to_string().into(),
self.command_runner,
&self.user_adder
))),
resource.get_value().to_string().into(),
self.command_runner,
&self.user_adder,
))),
"dir" => {
let value = resource.get_value();
Some(
if let Some(matches) = self.home_config.captures(value) {
Box::new(List::new(vec![
Box::new(Dir::new(value.to_string())),
Box::new(Owner::new(value.to_string(), matches[1].to_string().into(), self.command_runner))
])) as Box<dyn Symbol>
} else if let Some(matches) = self.home.captures(value) {
Box::new(
User::new(matches[1].to_string().into(), self.command_runner, &self.user_adder)
) as Box<dyn Symbol>
} else { Box::new(Dir::new(value.to_string())) as Box<dyn Symbol> }
)
},
Some(if let Some(matches) = self.home_config.captures(value) {
Box::new(List::new(vec![
Box::new(Dir::new(value.to_string())),
Box::new(Owner::new(
value.to_string(),
matches[1].to_string().into(),
self.command_runner,
)),
])) as Box<dyn Symbol>
} else if let Some(matches) = self.home.captures(value) {
Box::new(User::new(
matches[1].to_string().into(),
self.command_runner,
&self.user_adder,
)) as Box<dyn Symbol>
} else {
Box::new(Dir::new(value.to_string())) as Box<dyn Symbol>
})
}
"file" => {
let value = resource.get_value();
if let Some(matches) = self.csr.captures(value) {
Some(Box::new(TlsCsr::new(
matches[1].to_string().into(),
self.command_runner
self.command_runner,
)) as Box<dyn Symbol>)
} else if let Some(matches) = self.private_key.captures(value) {
Some(Box::new(TlsKey::new(
matches[1].to_string().into(),
self.command_runner
self.command_runner,
)) as Box<dyn Symbol>)
} else if let Some(matches) = self.systemd_linger.captures(value) {
Some(Box::new(SystemdUserSession::new(
matches[1].to_string().into(),
self.command_runner
self.command_runner,
)) as Box<dyn Symbol>)
} else { None }
},
_ => None
} else {
None
}
}
_ => None,
}
}
}

23
src/lib.rs

@ -1,22 +1,21 @@
// rustfmt
#![deny(trivial_numeric_casts, unsafe_code,
unstable_features, unused_extern_crates,
unused_import_braces, unused_qualifications,
variant_size_differences
#![deny(
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
variant_size_differences
)]
#![warn(
unused_results
)]
#![warn(unused_results)]
/*
#![warn(missing_docs, trivial_casts,
missing_copy_implementations,
missing_debug_implementations
)]
*/
#![allow(box_pointers)]
extern crate regex;
@ -27,8 +26,8 @@ pub mod build;
pub mod command_runner;
pub mod factory;
pub mod loggers;
pub mod symbols;
pub mod schema;
pub mod repository;
pub mod resources;
pub mod schema;
pub mod storage;
pub mod symbols;

9
src/loggers.rs

@ -18,7 +18,7 @@ impl Logger for StdErrLogger {
}
pub struct FilteringLogger<'a> {
logger: &'a mut dyn Logger
logger: &'a mut dyn Logger,
}
impl<'a> FilteringLogger<'a> {
@ -28,14 +28,11 @@ impl<'a> FilteringLogger<'a> {
}
impl<'a> Logger for FilteringLogger<'a> {
fn debug(&mut self, _str: &str) {
return
}
fn debug(&mut self, _str: &str) {}
fn write(&mut self, str: &str) {
self.logger.write(str)
}
}
#[cfg(test)]
mod test {
}
mod test {}

14
src/repository.rs

@ -1,20 +1,23 @@
use std::collections::HashMap;
use symbols::Symbol;
use resources::Resource;
use symbols::Symbol;
pub trait SymbolRepository<'a> {
fn get_symbol(&'a self, resource: &Resource) -> Option<Box<dyn Symbol + 'a>>;
}
impl<'a, C> SymbolRepository<'a> for C where C: Fn(&Resource) -> Option<Box<dyn Symbol + 'a>> {
impl<'a, C> SymbolRepository<'a> for C
where
C: Fn(&Resource) -> Option<Box<dyn Symbol + 'a>>,
{
fn get_symbol(&'a self, resource: &Resource) -> Option<Box<dyn Symbol + 'a>> {
self(resource)
}
}
pub struct DispatchingSymbolRepository<'a> {
repositories: HashMap<&'a str, Box<dyn SymbolRepository<'a> + 'a>>
repositories: HashMap<&'a str, Box<dyn SymbolRepository<'a> + 'a>>,
}
impl<'a> DispatchingSymbolRepository<'a> {
@ -25,6 +28,9 @@ impl<'a> DispatchingSymbolRepository<'a> {
impl<'a> SymbolRepository<'a> for DispatchingSymbolRepository<'a> {
fn get_symbol(&'a self, resource: &Resource) -> Option<Box<dyn Symbol + 'a>> {
self.repositories.get(resource.get_type()).and_then(|repo| repo.get_symbol(resource))
self
.repositories
.get(resource.get_type())
.and_then(|repo| repo.get_symbol(resource))
}
}

150
src/schema.rs

@ -9,20 +9,20 @@ use symbols::{Symbol, SymbolRunner};
#[derive(Debug)]
pub enum SymbolRunError {
Symbol(Box<dyn Error>),
ExecuteDidNotReach(())
ExecuteDidNotReach(()),
}
impl Error for SymbolRunError {
fn description(&self) -> &str {
match self {
SymbolRunError::Symbol(_) => "Symbol execution error",
SymbolRunError::ExecuteDidNotReach(_) => "Target not reached after executing symbol"
SymbolRunError::ExecuteDidNotReach(_) => "Target not reached after executing symbol",
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
SymbolRunError::Symbol(ref e) => Some(&**e),
SymbolRunError::ExecuteDidNotReach(_) => None
SymbolRunError::ExecuteDidNotReach(_) => None,
}
}
}
@ -31,24 +31,25 @@ impl fmt::Display for SymbolRunError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SymbolRunError::Symbol(ref e) => write!(f, "{}", e),
SymbolRunError::ExecuteDidNotReach(_) => write!(f, "{}", self.description())
SymbolRunError::ExecuteDidNotReach(_) => write!(f, "{}", self.description()),
}
}
}
pub struct InitializingSymbolRunner<L: Logger> {
logger: RefCell<L>
logger: RefCell<L>,
}
impl<L: Logger> InitializingSymbolRunner<L> {
pub fn new(logger: L) -> Self {
Self { logger: RefCell::new(logger) }
Self {
logger: RefCell::new(logger),
}
}
}
impl<L: Logger> SymbolRunner for InitializingSymbolRunner<L> {
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>>
{
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>> {
let mut logger = self.logger.borrow_mut();
let target_reached = try!(symbol.target_reached());
if target_reached {
@ -58,7 +59,13 @@ impl<L: Logger> SymbolRunner for InitializingSymbolRunner<L> {
logger.write(format!("Executing {}", symbol).as_str());
try!(symbol.execute());
let target_reached = try!(symbol.target_reached());
logger.debug(format!("Symbol reports target_reached: {:?} (should be true)", target_reached).as_str());
logger.debug(
format!(
"Symbol reports target_reached: {:?} (should be true)",
target_reached
)
.as_str(),
);
if !target_reached {
return Err(Box::new(SymbolRunError::ExecuteDidNotReach(())));
}
@ -68,18 +75,19 @@ impl<L: Logger> SymbolRunner for InitializingSymbolRunner<L> {
}
pub struct DrySymbolRunner<L: Logger> {
logger: RefCell<L>
logger: RefCell<L>,
}
impl<L: Logger> DrySymbolRunner<L> {
pub fn new(logger: L) -> Self {
Self { logger: RefCell::new(logger) }
Self {
logger: RefCell::new(logger),
}
}
}
impl<L: Logger> SymbolRunner for DrySymbolRunner<L> {
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>>
{
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>> {
let mut logger = self.logger.borrow_mut();
let target_reached = try!(symbol.target_reached());
logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str());
@ -92,22 +100,29 @@ impl<L: Logger> SymbolRunner for DrySymbolRunner<L> {
pub struct ReportingSymbolRunner<'a, R: 'a + SymbolRunner, L: Logger>(&'a R, RefCell<L>);
impl<'a, R, L> ReportingSymbolRunner<'a, R, L> where R: SymbolRunner, L: Logger {
impl<'a, R, L> ReportingSymbolRunner<'a, R, L>
where
R: SymbolRunner,
L: Logger,
{
pub fn new(symbol_runner: &'a R, logger: L) -> Self {
ReportingSymbolRunner(symbol_runner, RefCell::new(logger))
}
}
impl<'a, R, L> SymbolRunner for ReportingSymbolRunner<'a, R, L> where R: SymbolRunner, L: Logger {
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>>
{
impl<'a, R, L> SymbolRunner for ReportingSymbolRunner<'a, R, L>
where
R: SymbolRunner,
L: Logger,
{
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>> {
let mut logger = self.1.borrow_mut();
logger.debug(format!("Running symbol {}", symbol).as_str());
let res = self.0.run_symbol(symbol);
match res {
Err(ref e) => {
logger.write(format!("Failed on {} with {}, aborting.", symbol, e).as_str());
},
}
Ok(_) => {
logger.debug(format!("Successfully finished {}", symbol).as_str());
}
@ -116,23 +131,28 @@ impl<'a, R, L> SymbolRunner for ReportingSymbolRunner<'a, R, L> where R: SymbolR
}
}
use std::collections::HashSet;
use resources::Resource;
use std::collections::HashSet;
pub struct NonRepeatingSymbolRunner<R: SymbolRunner> {
upstream: R,
done: RefCell<HashSet<Resource>>
done: RefCell<HashSet<Resource>>,
}
impl<R> NonRepeatingSymbolRunner<R> where R: SymbolRunner {
impl<R> NonRepeatingSymbolRunner<R>
where
R: SymbolRunner,
{
pub fn new(symbol_runner: R) -> Self {
NonRepeatingSymbolRunner{ upstream: symbol_runner, done: RefCell::new(HashSet::new()) }
NonRepeatingSymbolRunner {
upstream: symbol_runner,
done: RefCell::new(HashSet::new()),
}
}
}
impl<R: SymbolRunner> SymbolRunner for NonRepeatingSymbolRunner<R> {
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>>
{
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>> {
if let Some(resources) = symbol.provides() {
let mut done = self.done.borrow_mut();
let mut has_to_run = false;
@ -151,17 +171,29 @@ impl<R: SymbolRunner> SymbolRunner for NonRepeatingSymbolRunner<R> {
}
use std::marker::PhantomData;
pub struct RequirementsResolvingSymbolRunner<'a, 's, R: 'a + SymbolRunner, G: 'a + SymbolRepository<'s>>(R, &'a G, PhantomData<Box<dyn Symbol + 's>>);
impl<'s, 'a: 's, R, G> RequirementsResolvingSymbolRunner<'a, 's, R, G> where R: SymbolRunner, G: SymbolRepository<'s> {
pub struct RequirementsResolvingSymbolRunner<
'a,
's,
R: 'a + SymbolRunner,
G: 'a + SymbolRepository<'s>,
>(R, &'a G, PhantomData<Box<dyn Symbol + 's>>);
impl<'s, 'a: 's, R, G> RequirementsResolvingSymbolRunner<'a, 's, R, G>
where
R: SymbolRunner,
G: SymbolRepository<'s>,
{
pub fn new(symbol_runner: R, symbol_repo: &'a G) -> Self {
RequirementsResolvingSymbolRunner(symbol_runner, symbol_repo, PhantomData)
}
}
impl<'s, 'a: 's, R, G> SymbolRunner for RequirementsResolvingSymbolRunner<'a, 's, R, G> where R: SymbolRunner, G: SymbolRepository<'s> {
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>>
{
impl<'s, 'a: 's, R, G> SymbolRunner for RequirementsResolvingSymbolRunner<'a, 's, R, G>
where
R: SymbolRunner,
G: SymbolRepository<'s>,
{
fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box<dyn Error>> {
for resource in symbol.get_prerequisites() {
if let Some(dep) = self.1.get_symbol(&resource) {
try!(dep.as_action(self).run());
@ -180,13 +212,13 @@ mod test {
use std::fmt;
use loggers::Logger;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction};
use schema::SymbolRunner;
use schema::InitializingSymbolRunner;
use schema::SymbolRunner;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction};
#[derive(Debug, PartialEq, Clone)]
enum DummySymbolError {
Error(())
Error(()),
}
impl Error for DummySymbolError {
@ -203,7 +235,7 @@ mod test {
struct DummySymbol<'a> {
_execute: &'a dyn Fn() -> Result<(), Box<dyn Error>>,
_target_reached: &'a dyn Fn() -> Result<bool, Box<dyn Error>>
_target_reached: &'a dyn Fn() -> Result<bool, Box<dyn Error>>,
}
impl<'b> Symbol for DummySymbol<'b> {
@ -218,7 +250,10 @@ mod test {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> where Self: 'a {
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
@ -230,13 +265,19 @@ mod test {
}
impl<'a> DummySymbol<'a> {
fn new(target_reached: &'a dyn Fn() -> Result<bool, Box<dyn Error>>, execute: &'a dyn Fn() -> Result<(), Box<dyn Error>>) -> DummySymbol<'a> {
DummySymbol { _target_reached: target_reached, _execute: execute }
fn new(
target_reached: &'a dyn Fn() -> Result<bool, Box<dyn Error>>,
execute: &'a dyn Fn() -> Result<(), Box<dyn Error>>,
) -> DummySymbol<'a> {
DummySymbol {
_target_reached: target_reached,
_execute: execute,
}
}
}
struct DummyLogger {
log: Vec<String>
log: Vec<String>,
}
impl DummyLogger {
@ -256,28 +297,33 @@ mod test {
#[test]
fn nothing_needed_to_be_done() {
let result = InitializingSymbolRunner::new(DummyLogger::new()).run_symbol(&DummySymbol::new(&|| Ok(true), &|| Ok(())));
let result = InitializingSymbolRunner::new(DummyLogger::new())
.run_symbol(&DummySymbol::new(&|| Ok(true), &|| Ok(())));
assert!(result.is_ok());
}
#[test]
fn everything_is_ok() {
let first = RefCell::new(true);
let result = InitializingSymbolRunner::new(DummyLogger::new()).run_symbol(&DummySymbol::new(&|| {
let mut _first = first.borrow_mut();
Ok(if *_first {
*_first = false;
true
} else {
false
})
}, &|| Ok(())));
let result = InitializingSymbolRunner::new(DummyLogger::new()).run_symbol(&DummySymbol::new(
&|| {
let mut _first = first.borrow_mut();
Ok(if *_first {
*_first = false;
true
} else {
false
})
},
&|| Ok(()),
));
assert!(result.is_ok());
}
#[test]
fn executing_did_not_change_state() {
let result = InitializingSymbolRunner::new(DummyLogger::new()).run_symbol(&DummySymbol::new(&|| Ok(false), &|| Ok(())));
let result = InitializingSymbolRunner::new(DummyLogger::new())
.run_symbol(&DummySymbol::new(&|| Ok(false), &|| Ok(())));
assert_eq!(
result.unwrap_err().description(),
"Target not reached after executing symbol"
@ -286,7 +332,11 @@ mod test {
#[test]
fn executing_did_not_work() {
let err = InitializingSymbolRunner::new(DummyLogger::new()).run_symbol(&DummySymbol::new(&|| Ok(false), &|| Err(Box::new(DummySymbolError::Error(()))))).unwrap_err();
let err = InitializingSymbolRunner::new(DummyLogger::new())
.run_symbol(&DummySymbol::new(&|| Ok(false), &|| {
Err(Box::new(DummySymbolError::Error(())))
}))
.unwrap_err();
assert_eq!(err.description(), "Description");
}
}

22
src/storage.rs

@ -20,14 +20,19 @@ impl SimpleStorage {
fn get_path(&self, date: Option<u64>) -> String {
match date {
Some(d) => format!("{}/_{}/{}", self.0, self.1, d),
None => format!("{}/_{}", self.0, self.1)
None => format!("{}/_{}", self.0, self.1),
}
}
}
impl Storage for SimpleStorage {
fn write_filename(&self) -> String {
self.get_path(Some(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()))
self.get_path(Some(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
))
}
fn read_filename(&self) -> Result<String, Box<dyn Error>> {
@ -37,8 +42,15 @@ impl Storage for SimpleStorage {
fn recent_date(&self) -> Result<u64, Box<dyn Error>> {
let dir = self.get_path(None);
try!(read_dir(dir))
.map(|entry| entry.ok().and_then(|e| e.file_name().into_string().ok()).and_then(|filename| u64::from_str(&filename).ok()))
.fold(None, |maybe_newest, maybe_time| maybe_newest.into_iter().chain(maybe_time).max())
.ok_or_else(|| "Not found".to_string().into())
.map(|entry| {
entry
.ok()
.and_then(|e| e.file_name().into_string().ok())
.and_then(|filename| u64::from_str(&filename).ok())
})
.fold(None, |maybe_newest, maybe_time| {
maybe_newest.into_iter().chain(maybe_time).max()
})
.ok_or_else(|| "Not found".to_string().into())
}
}

42
src/symbols/acme/account_key.rs

@ -9,12 +9,15 @@ use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct AcmeAccountKey<'a, C: 'a + CommandRunner> {
path: Cow<'a, Path>,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> AcmeAccountKey<'a, C> {
pub fn new(path: Cow<'a, Path>, command_runner: &'a C) -> Self {
AcmeAccountKey { path, command_runner }
AcmeAccountKey {
path,
command_runner,
}
}
fn get_bytes(&self) -> u32 {
@ -33,27 +36,50 @@ impl<'a, C: CommandRunner> Symbol for AcmeAccountKey<'a, C> {
if !self.path.exists() {
return Ok(false);
}
let stdout = try!(self.command_runner.get_output("openssl", &["rsa".as_ref(), "-in".as_ref(), self.path.as_os_str(), "-noout".as_ref(), "-check".as_ref(), "-text".as_ref()]));
let stdout = try!(self.command_runner.get_output(
"openssl",
&[
"rsa".as_ref(),
"-in".as_ref(),
self.path.as_os_str(),
"-noout".as_ref(),
"-check".as_ref(),
"-text".as_ref()
]
));
Ok(stdout.starts_with(&format!("Private-Key: ({} bit)\n", self.get_bytes()).as_bytes()))
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully("openssl", &["genrsa".as_ref(), "-out".as_ref(), self.path.as_os_str(), self.get_bytes().to_string().as_ref()])
self.command_runner.run_successfully(
"openssl",
&[
"genrsa".as_ref(),
"-out".as_ref(),
self.path.as_os_str(),
self.get_bytes().to_string().as_ref(),
],
)
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![ Resource::new("dir", self.path.parent().unwrap().to_string_lossy() ) ]
vec![Resource::new(
"dir",
self.path.parent().unwrap().to_string_lossy(),
)]
}
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 {
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)]
mod test {
}
mod test {}

75
src/symbols/acme/cert.rs

@ -6,17 +6,20 @@ use std::io::Write;
use std::path::Path;
use command_runner::CommandRunner;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use resources::Resource;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct AcmeCert<'a, C: 'a + CommandRunner> {
domain: Cow<'a, str>,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> AcmeCert<'a, C> {
pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self {
AcmeCert { domain, command_runner }
AcmeCert {
domain,
command_runner,
}
}
fn get_csr_path(&self) -> String {
@ -34,7 +37,7 @@ impl<'a, C: CommandRunner> fmt::Display for AcmeCert<'a, C> {
}
}
const DAYS_IN_SECONDS: u32 = 24*60*60;
const DAYS_IN_SECONDS: u32 = 24 * 60 * 60;
impl<'a, C: CommandRunner> Symbol for AcmeCert<'a, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
@ -42,10 +45,44 @@ impl<'a, C: CommandRunner> Symbol for AcmeCert<'a, C> {
return Ok(false);
}
let output = try!(self.command_runner.run_with_args("openssl", &["x509", "-in", &self.get_cert_path(), "-noout", "-subject", "-checkend", &(30*DAYS_IN_SECONDS).to_string()]));
if output.status.success() && output.stdout == format!("subject=CN = {}\nCertificate will not expire\n", self.domain).as_bytes() {
Ok(self.command_runner.run_successfully("openssl", &["verify", "--untrusted", "/home/acme/lets_encrypt_x3_cross_signed.pem", &self.get_cert_path()]).is_ok())
} else if output.status.code() == Some(1) && output.stdout == format!("subject=CN = {}\nCertificate will expire\n", self.domain).as_bytes() {
let output = try!(self.command_runner.run_with_args(
"openssl",
&[
"x509",
"-in",
&self.get_cert_path(),
"-noout",
"-subject",
"-checkend",
&(30 * DAYS_IN_SECONDS).to_string()
]
));
if output.status.success()
&& output.stdout
== format!(
"subject=CN = {}\nCertificate will not expire\n",
self.domain
)
.as_bytes()
{
Ok(
self
.command_runner
.run_successfully(
"openssl",
&[
"verify",
"--untrusted",
"/home/acme/lets_encrypt_x3_cross_signed.pem",
&self.get_cert_path(),
],
)
.is_ok(),
)
} else if output.status.code() == Some(1)
&& output.stdout
== format!("subject=CN = {}\nCertificate will expire\n", self.domain).as_bytes()
{
Ok(false)
} else {
Err(try!(String::from_utf8(output.stderr)).into())
@ -53,25 +90,37 @@ impl<'a, C: CommandRunner> Symbol for AcmeCert<'a, C> {
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
let output = try!(self.command_runner.get_output("acme-tiny", &["--account-key", "/home/acme/account.key", "--csr", &self.get_csr_path(), "--acme-dir", "/home/acme/challenges/"]));
let output = try!(self.command_runner.get_output(
"acme-tiny",
&[
"--account-key",
"/home/acme/account.key",
"--csr",
&self.get_csr_path(),
"--acme-dir",
"/home/acme/challenges/"
]
));
let mut file = try!(FsFile::create(self.get_cert_path()));
try!(file.write_all(&output));
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![ Resource::new("file", self.get_csr_path()) ]
vec![Resource::new("file", self.get_csr_path())]
}
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 {
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)]
mod test {
}
mod test {}

65
src/symbols/acme/chain.rs

@ -6,17 +6,20 @@ use std::io::Write;
use std::path::Path;
use command_runner::CommandRunner;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use resources::Resource;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct AcmeCertChain<'a, C: 'a + CommandRunner> {
domain: Cow<'a, str>,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> AcmeCertChain<'a, C> {
pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self {
AcmeCertChain { domain, command_runner }
AcmeCertChain {
domain,
command_runner,
}
}
fn get_single_cert_path(&self) -> String {
@ -34,7 +37,7 @@ impl<'a, C: CommandRunner> fmt::Display for AcmeCertChain<'a, C> {
}
}
const DAYS_IN_SECONDS: u32 = 24*60*60;
const DAYS_IN_SECONDS: u32 = 24 * 60 * 60;
impl<'a, C: CommandRunner> Symbol for AcmeCertChain<'a, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
@ -42,34 +45,72 @@ impl<'a, C: CommandRunner> Symbol for AcmeCertChain<'a, C> {
return Ok(false);
}
let stdout = try!(self.command_runner.get_output("openssl", &["x509", "-in", &self.get_cert_chain_path(), "-noout", "-subject", "-checkend", &(30*DAYS_IN_SECONDS).to_string()]));
if stdout != format!("subject=CN = {}\nCertificate will not expire\n", self.domain).as_bytes() {
let stdout = try!(self.command_runner.get_output(
"openssl",
&[
"x509",
"-in",
&self.get_cert_chain_path(),
"-noout",
"-subject",
"-checkend",
&(30 * DAYS_IN_SECONDS).to_string()
]
));
if stdout
!= format!(
"subject=CN = {}\nCertificate will not expire\n",
self.domain
)
.as_bytes()
{
return Ok(false);
}
// FIXME: From my understanding, the -untrusted *.pem parameter shouldn't be necessary, but is necessary with openssl 1.1.0f-3
Ok(self.command_runner.run_successfully("openssl", &["verify", "-untrusted", "/home/acme/lets_encrypt_x3_cross_signed.pem", &self.get_cert_chain_path()]).is_ok())
Ok(
self
.command_runner
.run_successfully(
"openssl",
&[
"verify",
"-untrusted",
"/home/acme/lets_encrypt_x3_cross_signed.pem",
&self.get_cert_chain_path(),
],
)
.is_ok(),
)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
let output = try!(self.command_runner.get_output("cat", &[self.get_single_cert_path().as_ref(), "/home/acme/lets_encrypt_x3_cross_signed.pem"]));
let output = try!(self.command_runner.get_output(
"cat",
&[
self.get_single_cert_path().as_ref(),
"/home/acme/lets_encrypt_x3_cross_signed.pem"
]
));
let mut file = try!(FsFile::create(self.get_cert_chain_path()));
try!(file.write_all(&output));
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![ Resource::new("file", self.get_single_cert_path()) ]
vec![Resource::new("file", self.get_single_cert_path())]
}
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 {
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)]
mod test {
}
mod test {}

49
src/symbols/acme/user.rs

@ -1,17 +1,17 @@
use resources::Resource;
use std::borrow::{Borrow, Cow};
use std::error::Error;
use std::fmt;
use std::ops::Deref;
use std::path::PathBuf;
use resources::Resource;
use command_runner::CommandRunner;
use symbols::{Action, OwnedSymbolAction, SymbolAction, SymbolRunner, Symbol};
use symbols::acme::AcmeAccountKey;
use symbols::dir::Dir;
use symbols::file::File;
use symbols::list::List;
use symbols::owner::Owner;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct AcmeUser<'a>(Cow<'a, str>);
@ -38,7 +38,10 @@ impl<'a> Symbol for AcmeUser<'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> where Self: 'b {
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
@ -49,17 +52,41 @@ impl<'a> fmt::Display for AcmeUser<'a> {
}
}
pub fn new<'a, S: Into<Cow<'a, str>>, C: CommandRunner, P: 'a + Deref<Target=str>>(command_runner: &'a C, cert: P, user_name: S) -> Box<dyn Symbol + 'a> { // impl trait
pub fn new<'a, S: Into<Cow<'a, str>>, C: CommandRunner, P: 'a + Deref<Target = str>>(
command_runner: &'a C,
cert: P,
user_name: S,
) -> Box<dyn Symbol + 'a> {
// impl trait
let user_name_cow = user_name.into();
let account_key_file: PathBuf = ["/home", user_name_cow.borrow(), "account.key"].iter().collect();
let account_key_file: PathBuf = ["/home", user_name_cow.borrow(), "account.key"]
.iter()
.collect();
Box::new(List::new(vec![
Box::new(AcmeAccountKey::new(account_key_file.clone().into(), command_runner)),
Box::new(Owner::new(account_key_file.to_string_lossy().into_owned(), user_name_cow.clone(), command_runner)),
Box::new(AcmeAccountKey::new(
account_key_file.clone().into(),
command_runner,
)),
Box::new(Owner::new(
account_key_file.to_string_lossy().into_owned(),
user_name_cow.clone(),
command_runner,
)),
Box::new(Dir::new("/home/acme/challenges")),
Box::new(Owner::new("/home/acme/challenges", user_name_cow.clone(), command_runner)),
Box::new(Owner::new(
"/home/acme/challenges",
user_name_cow.clone(),
command_runner,
)),
Box::new(Dir::new("/etc/ssl/local_certs")),
Box::new(Owner::new("/etc/ssl/local_certs", user_name_cow, command_runner)),
Box::new(File::new("/home/acme/lets_encrypt_x3_cross_signed.pem", cert))
Box::new(Owner::new(
"/etc/ssl/local_certs",
user_name_cow,
command_runner,
)),
Box::new(File::new(
"/home/acme/lets_encrypt_x3_cross_signed.pem",
cert,
)),
]))
}

38
src/symbols/dir.rs

@ -7,17 +7,26 @@ use std::path::Path;
use resources::Resource;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct Dir<D> where D: AsRef<str> {
path: D
pub struct Dir<D>
where
D: AsRef<str>,
{
path: D,
}
impl<D> Dir<D> where D: AsRef<str> {
impl<D> Dir<D>
where
D: AsRef<str>,
{
pub fn new(path: D) -> Self {
Dir { path }
}
}
impl<D> Symbol for Dir<D> where D: AsRef<str> {
impl<D> Symbol for Dir<D>
where
D: AsRef<str>,
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let metadata = fs::metadata(self.path.as_ref());
// Check if dir exists
@ -31,7 +40,10 @@ impl<D> Symbol for Dir<D> where D: AsRef<str> {
if metadata.unwrap().is_dir() {
Ok(true)
} else {
Err(Box::new(io::Error::new(io::ErrorKind::AlreadyExists, "Could not create a directory, non-directory file exists")))
Err(Box::new(io::Error::new(
io::ErrorKind::AlreadyExists,
"Could not create a directory, non-directory file exists",
)))
}
}
@ -41,27 +53,33 @@ impl<D> Symbol for Dir<D> where D: AsRef<str> {
fn get_prerequisites(&self) -> Vec<Resource> {
if let Some(parent) = Path::new(self.path.as_ref()).parent() {
vec![ Resource::new("dir", parent.to_string_lossy()) ]
vec![Resource::new("dir", parent.to_string_lossy())]
} else {
vec![]
}
}
fn provides(&self) -> Option<Vec<Resource>> {
Some(vec![ Resource::new("dir", self.path.as_ref()) ])
Some(vec![Resource::new("dir", self.path.as_ref())])
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> where Self: 'a {
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<D> fmt::Display for Dir<D> where D: AsRef<str> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
impl<D> fmt::Display for Dir<D>
where
D: AsRef<str>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "Dir {}", self.path.as_ref())
}
}

221
src/symbols/factory.rs

@ -4,18 +4,18 @@ use std::path::Path;
use command_runner::{CommandRunner, SetuidCommandRunner};
use storage::{SimpleStorage, Storage};
use symbols::{Action, Symbol, SymbolRunner};
use symbols::acme::{AcmeCert, AcmeCertChain};
use symbols::file::File;
use symbols::git::checkout::GitCheckout;
use symbols::hook::Hook;
use symbols::list::ListAction;
use symbols::mariadb::{DatabaseDump, MariaDBDatabase, MariaDBUser};
use symbols::nginx::server::{NginxServer, server_config, php_server_config_snippet};
use symbols::nginx::server::{php_server_config_snippet, server_config, NginxServer};
use symbols::owner::Owner;
use symbols::stored_directory::{StoredDirectory, StorageDirection};
use symbols::stored_directory::{StorageDirection, StoredDirectory};
use symbols::systemd::reload::ReloadService;
use symbols::tls::SelfSignedTlsCert;
use symbols::{Action, Symbol, SymbolRunner};
pub trait Policy {
fn user_name_for_host(&self, host_name: &'static str) -> String;
@ -32,11 +32,11 @@ impl Policy for DefaultPolicy {
}
}
pub struct SymbolFactory<'a, C: 'a + CommandRunner, R: 'a + SymbolRunner, P: 'a + Policy>{
pub struct SymbolFactory<'a, C: 'a + CommandRunner, R: 'a + SymbolRunner, P: 'a + Policy> {
command_runner: &'a C,
acme_command_runner: SetuidCommandRunner<'a, C>,
symbol_runner: &'a R,
policy: &'a P
policy: &'a P,
}
impl<'b, C: 'b + CommandRunner, R: 'b + SymbolRunner, P: 'b + Policy> SymbolFactory<'b, C, R, P> {
@ -44,39 +44,45 @@ impl<'b, C: 'b + CommandRunner, R: 'b + SymbolRunner, P: 'b + Policy> SymbolFact
let acme_user = "acme"; // FIXME: CONFIG
let acme_command_runner = SetuidCommandRunner::new(acme_user, command_runner);
SymbolFactory { command_runner, acme_command_runner, symbol_runner, policy }
SymbolFactory {
command_runner,
acme_command_runner,
symbol_runner,
policy,
}
}
pub fn get_nginx_acme_server<'a, 'c: 'a, S: 'a + Symbol>(&'c self, host: &'static str, nginx_server_symbol: S) -> Box<dyn Action + 'a> {
pub fn get_nginx_acme_server<'a, 'c: 'a, S: 'a + Symbol>(
&'c self,
host: &'static str,
nginx_server_symbol: S,
) -> Box<dyn Action + 'a> {
Box::new(ListAction::new(vec![
Box::new(SelfSignedTlsCert::new(
host.into(),
self.command_runner
)).into_action(self.symbol_runner),
Box::new(SelfSignedTlsCert::new(host.into(), self.command_runner))
.into_action(self.symbol_runner),
Box::new(Hook::new(
nginx_server_symbol,
ReloadService::new("nginx", self.command_runner)
)).into_action(self.symbol_runner),
Box::new(AcmeCert::new(
host.into(),
&self.acme_command_runner
)).into_action(self.symbol_runner),
ReloadService::new("nginx", self.command_runner),
))
.into_action(self.symbol_runner),
Box::new(AcmeCert::new(host.into(), &self.acme_command_runner))
.into_action(self.symbol_runner),
Box::new(Hook::new(
AcmeCertChain::new(
host.into(),
&self.acme_command_runner
),
ReloadService::new("nginx", self.command_runner)
)).into_action(self.symbol_runner)
AcmeCertChain::new(host.into(), &self.acme_command_runner),
ReloadService::new("nginx", self.command_runner),
))
.into_action(self.symbol_runner),
]))
}
pub fn get_nginx_acme_challenge_config<'a>(&'a self) -> Box<dyn Action + 'a> {
Box::new(File::new(
"/etc/nginx/snippets/acme-challenge.conf", "location ^~ /.well-known/acme-challenge/ {
"/etc/nginx/snippets/acme-challenge.conf",
"location ^~ /.well-known/acme-challenge/ {
alias /home/acme/challenges/;
try_files $uri =404;
}"
)).into_action(self.symbol_runner)
}",
))
.into_action(self.symbol_runner)
}
fn get_php_fpm_pool_socket_path<'a>(&'a self, user_name: &str) -> String {
@ -89,7 +95,7 @@ impl<'b, C: 'b + CommandRunner, R: 'b + SymbolRunner, P: 'b + Policy> SymbolFact
File::new(
format!("/etc/php/7.0/fpm/pool.d/{}.conf", user_name),
format!(
"[{0}]
"[{0}]
user = {0}
group = www-data
@ -99,47 +105,66 @@ pm = ondemand
pm.max_children = 10
catch_workers_output = yes
env[PATH] = /usr/local/bin:/usr/bin:/bin
"
, user_name, socket)),
ReloadService::new("php7.0-fpm", self.command_runner)
)).into_action(self.symbol_runner)
",
user_name, socket
),
),
ReloadService::new("php7.0-fpm", self.command_runner),
))
.into_action(self.symbol_runner)
}
pub fn serve_php<'a>(&'a self, host_name: &'static str, root_dir: Cow<'a, str>) -> Box<dyn Action + 'a> {
pub fn serve_php<'a>(
&'a self,
host_name: &'static str,
root_dir: Cow<'a, str>,
) -> Box<dyn Action + 'a> {
let user_name = self.policy.user_name_for_host(host_name);
let socket = self.get_php_fpm_pool_socket_path(&user_name);
Box::new(ListAction::new(vec![
self.get_php_fpm_pool(&user_name),
self.get_nginx_acme_server(host_name,
NginxServer::new_php(
host_name,
socket.into(),
root_dir,
self.command_runner
)
)
self.get_nginx_acme_server(
host_name,
NginxServer::new_php(host_name, socket.into(), root_dir, self.command_runner),
),
]))
}
pub fn serve_wordpress<'a>(&'a self, host_name: &'static str, root_dir: Cow<'a, str>) -> Box<dyn Action + 'a> {
pub fn serve_wordpress<'a>(
&'a self,
host_name: &'static str,
root_dir: Cow<'a, str>,
) -> Box<dyn Action + 'a> {
let user_name = self.policy.user_name_for_host(host_name);
let socket = self.get_php_fpm_pool_socket_path(&user_name);
Box::new(ListAction::new(vec![
self.get_php_fpm_pool(&user_name),
self.get_nginx_acme_server(host_name,
self.get_nginx_acme_server(
host_name,
NginxServer::new(
host_name,
server_config(host_name, &format!("{}
server_config(
host_name,
&format!(
"{}
location / {{
try_files $uri $uri/ /index.php?$args;
}}
", php_server_config_snippet(socket.into(), root_dir))),
self.command_runner
))
",
php_server_config_snippet(socket.into(), root_dir)
),
),
self.command_runner,
),
),
]))
}
pub fn serve_dokuwiki<'a>(&'a self, host_name: &'static str, root_dir: &'static str) -> Box<dyn Action + 'a> {
pub fn serve_dokuwiki<'a>(
&'a self,
host_name: &'static str,
root_dir: &'static str,
) -> Box<dyn Action + 'a> {
let user_name = self.policy.user_name_for_host(host_name);
let socket = self.get_php_fpm_pool_socket_path(&user_name);
Box::new(ListAction::new(vec![
@ -174,15 +199,23 @@ env[PATH] = /usr/local/bin:/usr/bin:/bin
]))
}
pub fn serve_nextcloud<'a>(&'a self, host_name: &'static str, root_dir: Cow<'a, str>) -> Box<dyn Action + 'a> {
pub fn serve_nextcloud<'a>(
&'a self,
host_name: &'static str,
root_dir: Cow<'a, str>,
) -> Box<dyn Action + 'a> {
let user_name = self.policy.user_name_for_host(host_name);
let socket = self.get_php_fpm_pool_socket_path(&user_name);
Box::new(ListAction::new(vec![
self.get_php_fpm_pool(&user_name),
self.get_nginx_acme_server(host_name,
self.get_nginx_acme_server(
host_name,
NginxServer::new(
host_name,
server_config(host_name, &format!("{}
server_config(
host_name,
&format!(
"{}
client_max_body_size 500M;
# Disable gzip to avoid the removal of the ETag header
@ -230,50 +263,106 @@ env[PATH] = /usr/local/bin:/usr/bin:/bin
location ~* \\.(?:jpg|jpeg|gif|bmp|ico|png|swf)$ {{
access_log off;
}}
", php_server_config_snippet(socket.into(), root_dir))),
self.command_runner
))
",
php_server_config_snippet(socket.into(), root_dir)
),
),
self.command_runner,
),
),
]))
}
pub fn serve_redir<'a>(&'a self, host_name: &'static str, target: &'static str) -> Box<dyn Action + 'a> {
self.get_nginx_acme_server(host_name, NginxServer::new_redir(host_name, target, self.command_runner))
pub fn serve_redir<'a>(
&'a self,
host_name: &'static str,
target: &'static str,
) -> Box<dyn Action + 'a> {
self.get_nginx_acme_server(
host_name,
NginxServer::new_redir(host_name, target, self.command_runner),
)
}
pub fn serve_static<'a>(&'a self, host_name: &'static str, dir: &'a str) -> Box<dyn Action + 'a> {
self.get_nginx_acme_server(host_name, NginxServer::new_static(host_name, dir, self.command_runner))
self.get_nginx_acme_server(
host_name,
NginxServer::new_static(host_name, dir, self.command_runner),
)
}
pub fn get_stored_directory<'a, T: Into<String>>(&'a self, storage_name: &'static str, target: T) -> (Box<dyn Action + 'a>, Box<dyn Action + 'a>) {
pub fn get_stored_directory<'a, T: Into<String>>(
&'a self,
storage_name: &'static str,
target: T,
) -> (Box<dyn Action + 'a>, Box<dyn Action + 'a>) {
let data = SimpleStorage::new("/root/data".to_string(), storage_name.to_string());
let string_target = target.into();
(
Box::new(StoredDirectory::new(string_target.clone().into(), data.clone(), StorageDirection::Save, self.command_runner)).into_action(self.symbol_runner),
Box::new(StoredDirectory::new(string_target.into(), data.clone(), StorageDirection::Load, self.command_runner)).into_action(self.symbol_runner)
Box::new(StoredDirectory::new(
string_target.clone().into(),
data.clone(),
StorageDirection::Save,
self.command_runner,
))
.into_action(self.symbol_runner),
Box::new(StoredDirectory::new(
string_target.into(),
data.clone(),
StorageDirection::Load,
self.command_runner,
))
.into_action(self.symbol_runner),
)
}
pub fn get_mariadb_database<'a>(&'a self, name: &'static str) -> Box<dyn Action + 'a> {
let db_dump = SimpleStorage::new("/root/data".to_string(), format!("{}.sql", name));
Box::new(ListAction::new(vec![
Box::new(MariaDBDatabase::new(name.into(), db_dump.read_filename().unwrap().into(), self.command_runner)).into_action(self.symbol_runner),
Box::new(DatabaseDump::new(name, db_dump, self.command_runner)).into_action(self.symbol_runner)
Box::new(MariaDBDatabase::new(
name.into(),
db_dump.read_filename().unwrap().into(),
self.command_runner,
))
.into_action(self.symbol_runner),
Box::new(DatabaseDump::new(name, db_dump, self.command_runner))
.into_action(self.symbol_runner),
]))
}
pub fn get_mariadb_user<'a>(&'a self, user_name: &'static str) -> Box<dyn Action + 'a> {
Box::new(MariaDBUser::new(user_name.into(), self.command_runner)).into_action(self.symbol_runner)
Box::new(MariaDBUser::new(user_name.into(), self.command_runner))
.into_action(self.symbol_runner)
}
pub fn get_git_checkout<'a, T: 'a + AsRef<str>>(&'a self, target: T, source: &'a str, branch: &'a str) -> Box<dyn Action + 'a> {
Box::new(GitCheckout::new(target, source, branch, self.command_runner)).into_action(self.symbol_runner)
pub fn get_git_checkout<'a, T: 'a + AsRef<str>>(
&'a self,
target: T,
source: &'a str,
branch: &'a str,
) -> Box<dyn Action + 'a> {
Box::new(GitCheckout::new(
target,
source,
branch,
self.command_runner,
))
.into_action(self.symbol_runner)
}
pub fn get_owner<'a, F: 'a + AsRef<str>>(&'a self, file: F, user: &'a str) -> Box<dyn Action + 'a> {
pub fn get_owner<'a, F: 'a + AsRef<str>>(
&'a self,
file: F,
user: &'a str,
) -> Box<dyn Action + 'a> {
Box::new(Owner::new(file, user.into(), self.command_runner)).into_action(self.symbol_runner)
}
pub fn get_file<'a, F: 'a + Deref<Target=str>, Q: 'a + AsRef<Path>>(&'a self, path: Q, content: F) -> Box<dyn Action + 'a> {
pub fn get_file<'a, F: 'a + Deref<Target = str>, Q: 'a + AsRef<Path>>(
&'a self,
path: Q,
content: F,
) -> Box<dyn Action + 'a> {
Box::new(File::new(path, content)).into_action(self.symbol_runner)
}
}

47
src/symbols/file.rs

@ -6,21 +6,33 @@ use std::io::{Read, Write};
use std::ops::Deref;
use std::path::Path;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use resources::Resource;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct File<C, D> where C: Deref<Target=str>, D: AsRef<Path> {
pub struct File<C, D>
where
C: Deref<Target = str>,
D: AsRef<Path>,
{
path: D,
content: C
content: C,
}
impl<C, D> File<C, D> where C: Deref<Target=str>, D: AsRef<Path> {
impl<C, D> File<C, D>
where
C: Deref<Target = str>,
D: AsRef<Path>,
{
pub fn new(path: D, content: C) -> Self {
File { path, content }
}
}
impl<C, D> Symbol for File<C, D> where C: Deref<Target=str>, D: AsRef<Path> {
impl<C, D> Symbol for File<C, D>
where
C: Deref<Target = str>,
D: AsRef<Path>,
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let file = FsFile::open(self.path.as_ref());
// Check if file exists
@ -37,9 +49,9 @@ impl<C, D> Symbol for File<C, D> where C: Deref<Target=str>, D: AsRef<Path> {
loop {
match (file_content.next(), target_content.next()) {
(None, None) => return Ok(true),
(Some(Ok(a)), Some(b)) if a == b => {},
(Some(Ok(a)), Some(b)) if a == b => {}
(Some(Err(e)), _) => return Err(Box::new(e)),
(_, _) => return Ok(false)
(_, _) => return Ok(false),
}
}
}
@ -51,20 +63,33 @@ impl<C, D> Symbol for File<C, D> where C: Deref<Target=str>, D: AsRef<Path> {
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![ Resource::new("dir", Path::new(self.path.as_ref()).parent().unwrap().to_string_lossy() ) ]
vec![Resource::new(
"dir",
Path::new(self.path.as_ref())
.parent()
.unwrap()
.to_string_lossy(),
)]
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> where Self: 'a {
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<C, D> fmt::Display for File<C, D> where C: Deref<Target=str>, D: AsRef<Path> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
impl<C, D> fmt::Display for File<C, D>
where
C: Deref<Target = str>,
D: AsRef<Path>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "File {}", self.path.as_ref().display())
}
}

48
src/symbols/git/checkout.rs

@ -11,18 +11,29 @@ pub struct GitCheckout<'a, C: 'a + CommandRunner, T: AsRef<str>> {
target: T,
source: &'a str,
branch: &'a str,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner, T: AsRef<str>> GitCheckout<'a, C, T> {
pub fn new(target: T, source: &'a str, branch: &'a str, command_runner: &'a C) -> Self {
GitCheckout { target, source, branch, command_runner }
GitCheckout {
target,
source,
branch,
command_runner,
}
}
}
impl<'a, C: CommandRunner, T: AsRef<str>> fmt::Display for GitCheckout<'a, C, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Checkout {} (branch {}) into {}", self.source, self.branch, self.target.as_ref())
write!(
f,
"Checkout {} (branch {}) into {}",
self.source,
self.branch,
self.target.as_ref()
)
}
}
@ -54,7 +65,18 @@ impl<'a, C: CommandRunner, T: AsRef<str>> Symbol for GitCheckout<'a, C, T> {
fn execute(&self) -> Result<(), Box<dyn Error>> {
if !Path::new(self.target.as_ref()).exists() {
return self.command_runner.run_successfully("git", &["clone", "--depth", "1", "-b", self.branch, self.source, self.target.as_ref()]);
return self.command_runner.run_successfully(
"git",
&[
"clone",
"--depth",
"1",
"-b",
self.branch,
self.source,
self.target.as_ref(),
],
);
}
try!(self._run_in_target_repo(&["fetch", self.source, self.branch]));
try!(self._run_in_target_repo(&["merge", "FETCH_HEAD"]));
@ -62,22 +84,30 @@ impl<'a, C: CommandRunner, T: AsRef<str>> Symbol for GitCheckout<'a, C, T> {
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![ Resource::new("dir", Path::new(self.target.as_ref()).parent().unwrap().to_string_lossy()) ]
vec![Resource::new(
"dir",
Path::new(self.target.as_ref())
.parent()
.unwrap()
.to_string_lossy(),
)]
}
fn provides(&self) -> Option<Vec<Resource>> {
Some(vec![ Resource::new("dir", self.target.as_ref().to_string()) ])
Some(vec![Resource::new("dir", self.target.as_ref().to_string())])
}
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 {
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)]
mod test {
}
mod test {}

25
src/symbols/git/submodules.rs

@ -7,12 +7,15 @@ use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct GitSubmodules<'a, C: 'a + CommandRunner> {
target: &'a str,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> GitSubmodules<'a, C> {
pub fn new(target: &'a str, command_runner: &'a C) -> Self {
GitSubmodules { target, command_runner }
GitSubmodules {
target,
command_runner,
}
}
}
@ -35,8 +38,14 @@ impl<'a, C: CommandRunner> Symbol for GitSubmodules<'a, C> {
if !Path::new(self.target).exists() {
return Ok(false);
}
let output = try!(String::from_utf8(try!(self._run_in_target_repo(&["submodule", "status"]))));
Ok(output.lines().all(|line| line.is_empty() || line.starts_with(' ')))
let output = try!(String::from_utf8(try!(
self._run_in_target_repo(&["submodule", "status"])
)));
Ok(
output
.lines()
.all(|line| line.is_empty() || line.starts_with(' ')),
)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
@ -48,11 +57,13 @@ impl<'a, C: CommandRunner> Symbol for GitSubmodules<'a, C> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> where Self: 'b {
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)]
mod test {
}
mod test {}

87
src/symbols/hook.rs

@ -4,21 +4,39 @@ use std::fmt;
use resources::Resource;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct Hook<A, B> where A: Symbol, B: Symbol {
pub struct Hook<A, B>
where
A: Symbol,
B: Symbol,
{
a: A,
b: B
b: B,
}
// A and B are executed if either A or B are not reached
impl<A, B> Hook<A, B> where A: Symbol, B: Symbol {
impl<A, B> Hook<A, B>
where
A: Symbol,
B: Symbol,
{
pub fn new(a: A, b: B) -> Self {
Hook { a, b }
}
}
impl<A, B> Symbol for Hook<A, B> where A: Symbol, B: Symbol {
impl<A, B> Symbol for Hook<A, B>
where
A: Symbol,
B: Symbol,
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
self.a.target_reached().and_then(|reached| if reached { self.b.target_reached() } else { Ok(reached) })
self.a.target_reached().and_then(|reached| {
if reached {
self.b.target_reached()
} else {
Ok(reached)
}
})
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
@ -41,20 +59,31 @@ impl<A, B> Symbol for Hook<A, B> where A: Symbol, B: Symbol {
if let Some(provides) = self.b.provides() {
r.extend(provides.into_iter());
}
if r.is_empty() { None } else { Some(r) }
if r.is_empty() {
None
} else {
Some(r)
}
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> where Self: 'a {
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<A, B> fmt::Display for Hook<A, B> where A: Symbol, B: Symbol {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
impl<A, B> fmt::Display for Hook<A, B>
where
A: Symbol,
B: Symbol,
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "Hook {} and then {}", self.a, self.b)
}
}
@ -64,36 +93,58 @@ mod test {
use std::error::Error;
use std::fmt;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use symbols::hook::Hook;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
struct ErrSymbol(String);
impl Symbol for ErrSymbol {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> { Err(self.0.clone().into()) }
fn execute(&self) -> Result<(), Box<dyn Error>> { Err(self.0.clone().into()) }
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Err(self.0.clone().into())
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
Err(self.0.clone().into())
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> where Self: 'a {
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl fmt::Display for ErrSymbol { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ write!(f, "") } }
impl fmt::Display for ErrSymbol {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "")
}
}
struct OkSymbol(bool);
impl Symbol for OkSymbol {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> { Ok(self.0) }
fn execute(&self) -> Result<(), Box<dyn Error>> { Ok(()) }
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(self.0)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
Ok(())
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> where Self: 'a {
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl fmt::Display for OkSymbol { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ write!(f, "") } }
impl fmt::Display for OkSymbol {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "")
}
}
#[test]
fn first_target_reached_fails() {

31
src/symbols/list.rs

@ -5,7 +5,7 @@ use resources::Resource;
use symbols::{Action, Symbol, SymbolRunner};
pub struct List<'a> {
symbols: Vec<Box<dyn Symbol + 'a>>
symbols: Vec<Box<dyn Symbol + 'a>>,
}
impl<'a> List<'a> {
@ -35,7 +35,9 @@ impl<'a> Symbol for List<'a> {
let mut r = vec![];
for symbol in &self.symbols {
for req in symbol.get_prerequisites() {
if self.provides().map_or(true, |p| !p.contains(&req)) { r.push(req) }
if self.provides().map_or(true, |p| !p.contains(&req)) {
r.push(req)
}
}
}
r
@ -48,21 +50,34 @@ impl<'a> Symbol for List<'a> {
r.extend(provides.into_iter());
}
}
if r.is_empty() { None } else { Some(r) }
if r.is_empty() {
None
} else {
Some(r)
}
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolListAction::new(runner, &self.symbols))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> where Self: 'b {
Box::new(ListAction::new(self.symbols.into_iter().map(|s| s.into_action(runner)).collect()))
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(ListAction::new(
self
.symbols
.into_iter()
.map(|s| s.into_action(runner))
.collect(),
))
}
}
struct SymbolListAction<'a> {
runner: &'a dyn SymbolRunner,
symbols: &'a [Box<dyn Symbol + 'a>]
symbols: &'a [Box<dyn Symbol + 'a>],
}
impl<'a> SymbolListAction<'a> {
@ -81,7 +96,7 @@ impl<'a> Action for SymbolListAction<'a> {
}
pub struct ListAction<'a> {
actions: Vec<Box<dyn Action + 'a>>
actions: Vec<Box<dyn Action + 'a>>,
}
impl<'a> ListAction<'a> {
@ -100,7 +115,7 @@ impl<'a> Action for ListAction<'a> {
}
impl<'a> fmt::Display for List<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
try!(write!(f, "List [ "));
for symbol in &self.symbols {
try!(write!(f, "{} ", symbol));

33
src/symbols/mariadb/database.rs

@ -8,16 +8,22 @@ use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct MariaDBDatabase<'a, C: 'a + CommandRunner> {
db_name: Cow<'a, str>,
seed_file: Cow<'a, str>,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> MariaDBDatabase<'a, C> {
pub fn new(db_name: Cow<'a, str>, seed_file: Cow<'a, str>, command_runner: &'a C) -> Self {
MariaDBDatabase { db_name, seed_file, command_runner }
MariaDBDatabase {
db_name,
seed_file,
command_runner,
}
}
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
let b = try!(self.command_runner.get_output("mariadb", &["--skip-column-names", "-B", "-e", sql]));
let b = try!(self
.command_runner
.get_output("mariadb", &["--skip-column-names", "-B", "-e", sql]));
Ok(try!(String::from_utf8(b)))
}
}
@ -30,23 +36,34 @@ impl<'a, C: CommandRunner> fmt::Display for MariaDBDatabase<'a, C> {
impl<'a, C: CommandRunner> Symbol for MariaDBDatabase<'a, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(try!(self.run_sql(&format!("SHOW DATABASES LIKE '{}'", self.db_name))).trim_end() == self.db_name)
Ok(
try!(self.run_sql(&format!("SHOW DATABASES LIKE '{}'", self.db_name))).trim_end()
== self.db_name,
)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
try!(self.run_sql(&format!("CREATE DATABASE {}", self.db_name)));
self.command_runner.run_successfully("sh", &["-c", &format!("mariadb '{}' < {}", self.db_name, self.seed_file)])
self.command_runner.run_successfully(
"sh",
&[
"-c",
&format!("mariadb '{}' < {}", self.db_name, self.seed_file),
],
)
}
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 {
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)]
mod test {
}
mod test {}

45
src/symbols/mariadb/database_dump.rs

@ -3,22 +3,33 @@ use std::fmt;
use std::str::FromStr;
use command_runner::CommandRunner;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use storage::Storage;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct DatabaseDump<'a, N, C, S> where N: 'a + AsRef<str>, C: 'a + CommandRunner, S: Storage {
pub struct DatabaseDump<'a, N, C, S>
where
N: 'a + AsRef<str>,
C: 'a + CommandRunner,
S: Storage,
{
db_name: N,
storage: S,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, N: AsRef<str>, C: CommandRunner, S: Storage> DatabaseDump<'a, N, C, S> {
pub fn new(db_name: N, storage: S, command_runner: &'a C) -> Self {
DatabaseDump { db_name, storage, command_runner }
DatabaseDump {
db_name,
storage,
command_runner,
}
}
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
let b = try!(self.command_runner.get_output("mariadb", &["--skip-column-names", "-B", "-e", sql]));
let b = try!(self
.command_runner
.get_output("mariadb", &["--skip-column-names", "-B", "-e", sql]));
Ok(try!(String::from_utf8(b)))
}
}
@ -33,23 +44,37 @@ impl<'a, N: AsRef<str>, C: CommandRunner, S: Storage> Symbol for DatabaseDump<'a
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let dump_date = try!(self.storage.recent_date());
let modified_date = try!(self.run_sql(&format!("select UNIX_TIMESTAMP(MAX(UPDATE_TIME)) from information_schema.tables WHERE table_schema = '{}'", self.db_name.as_ref())));
if modified_date.trim_end() == "NULL" { return Ok(false); }
if modified_date.trim_end() == "NULL" {
return Ok(false);
}
Ok(try!(u64::from_str(modified_date.trim_end())) <= dump_date)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully("sh", &["-c", &format!("mysqldump '{}' > {}", self.db_name.as_ref(), self.storage.write_filename())])
self.command_runner.run_successfully(
"sh",
&[
"-c",
&format!(
"mysqldump '{}' > {}",
self.db_name.as_ref(),
self.storage.write_filename()
),
],
)
}
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 {
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)]
mod test {
}
mod test {}

2
src/symbols/mariadb/mod.rs

@ -1,6 +1,6 @@
mod database;
mod user;
mod database_dump;
mod user;
pub use self::database::MariaDBDatabase;
pub use self::database_dump::DatabaseDump;

35
src/symbols/mariadb/user.rs

@ -8,16 +8,21 @@ use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct MariaDBUser<'a, C: 'a + CommandRunner> {
user_name: Cow<'a, str>,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> MariaDBUser<'a, C> {
pub fn new(user_name: Cow<'a, str>, command_runner: &'a C) -> Self {
MariaDBUser { user_name, command_runner }
MariaDBUser {
user_name,
command_runner,
}
}
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
let b = try!(self.command_runner.get_output("mariadb", &["--skip-column-names", "-B", "-e", sql]));
let b = try!(self
.command_runner
.get_output("mariadb", &["--skip-column-names", "-B", "-e", sql]));
Ok(try!(String::from_utf8(b)))
}
}
@ -30,27 +35,39 @@ impl<'a, C: CommandRunner> fmt::Display for MariaDBUser<'a, C> {
impl<'a, C: CommandRunner> Symbol for MariaDBUser<'a, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(try!(self.run_sql(&format!("SELECT User FROM mysql.user WHERE User = '{}' AND plugin = 'unix_socket'", self.user_name))).trim_end() == self.user_name)
Ok(
try!(self.run_sql(&format!(
"SELECT User FROM mysql.user WHERE User = '{}' AND plugin = 'unix_socket'",
self.user_name
)))
.trim_end()
== self.user_name,
)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
try!(self.run_sql(&format!("GRANT ALL ON {0}.* TO {0} IDENTIFIED VIA unix_socket", self.user_name)));
try!(self.run_sql(&format!(
"GRANT ALL ON {0}.* TO {0} IDENTIFIED VIA unix_socket",
self.user_name
)));
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![ Resource::new("user", self.user_name.to_string()) ]
vec![Resource::new("user", self.user_name.to_string())]
}
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 {
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)]
mod test {
}
mod test {}

10
src/symbols/mod.rs

@ -1,6 +1,6 @@
use resources::Resource;
use std::error::Error;
use std::fmt::Display;
use resources::Resource;
pub trait Action {
fn run(&self) -> Result<(), Box<dyn Error>>;
@ -27,13 +27,15 @@ pub trait Symbol: Display {
None
}
fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>;
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> where Self: 'a;
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a;
}
// SymbolAction
pub struct SymbolAction<'a, S: Symbol + 'a> {
runner: &'a dyn SymbolRunner,
symbol: &'a S
symbol: &'a S,
}
impl<'a, S: Symbol> SymbolAction<'a, S> {
@ -50,7 +52,7 @@ impl<'a, S: Symbol> Action for SymbolAction<'a, S> {
pub struct OwnedSymbolAction<'a, S: Symbol + 'a> {
runner: &'a dyn SymbolRunner,
symbol: S
symbol: S,
}
impl<'a, S: Symbol + 'a> OwnedSymbolAction<'a, S> {

110
src/symbols/nginx/server.rs

@ -4,14 +4,14 @@ use std::io;
use std::ops::Deref;
use command_runner::CommandRunner;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use symbols::file::File as FileSymbol;
use resources::Resource;
use symbols::file::File as FileSymbol;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
#[derive(Debug)]
pub enum NginxServerError<E: Error> {
ExecError(E),
GenericError
GenericError,
}
impl From<io::Error> for NginxServerError<io::Error> {
@ -24,13 +24,13 @@ impl<E: Error> Error for NginxServerError<E> {
fn description(&self) -> &str {
match self {
NginxServerError::ExecError(ref e) => e.description(),
NginxServerError::GenericError => "Generic error"
NginxServerError::GenericError => "Generic error",
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
NginxServerError::ExecError(ref e) => Some(e),
_ => None
_ => None,
}
}
}
@ -41,7 +41,10 @@ impl<E: Error> fmt::Display for NginxServerError<E> {
}
}
pub struct NginxServer<'a, C: 'a + CommandRunner, T> where T: Deref<Target=str> {
pub struct NginxServer<'a, C: 'a + CommandRunner, T>
where
T: Deref<Target = str>,
{
command_runner: &'a C,
file: FileSymbol<T, String>,
}
@ -49,7 +52,8 @@ pub struct NginxServer<'a, C: 'a + CommandRunner, T> where T: Deref<Target=str>
use std::borrow::Cow;
pub fn server_config(domain: &str, content: &str) -> String {
format!("server {{
format!(
"server {{
listen 443 ssl http2;
server_name {0};
include \"snippets/acme-challenge.conf\";
@ -71,17 +75,25 @@ server {{
return 301 https://$host$request_uri;
}}
}}
", domain, content)
",
domain, content
)
}
pub fn php_server_config_snippet<'a>(socket_path: Cow<'a, str>, static_path: Cow<'a, str>) -> String {
format!("
pub fn php_server_config_snippet<'a>(
socket_path: Cow<'a, str>,
static_path: Cow<'a, str>,
) -> String {
format!(
"
root {};
index index.html index.php;
location ~ [^/]\\.php(/|$) {{
fastcgi_pass unix:{};
include \"snippets/fastcgi-php.conf\";
}}", static_path, socket_path)
}}",
static_path, socket_path
)
}
pub trait SocketSpec {
@ -110,14 +122,26 @@ impl SocketSpec for LocalTcpSocket {
impl<'a, C: CommandRunner> NginxServer<'a, C, String> {
pub fn new_redir(domain: &'a str, target: &'a str, command_runner: &'a C) -> Self {
let content = server_config(domain, &format!("location / {{
let content = server_config(
domain,
&format!(
"location / {{
return 301 $scheme://{}$request_uri;
}}", target));
}}",
target
),
);
NginxServer::new(domain, content, command_runner)
}
pub fn new_proxy<S: SocketSpec>(domain: &'a str, socket_path: S, static_path: &'a str, command_runner: &'a C) -> Self {
let proxy_content = format!("location / {{
pub fn new_proxy<S: SocketSpec>(
domain: &'a str,
socket_path: S,
static_path: &'a str,
command_runner: &'a C,
) -> Self {
let proxy_content = format!(
"location / {{
try_files $uri @proxy;
}}
@ -125,25 +149,44 @@ location @proxy {{
include fastcgi_params;
proxy_pass http://{};
proxy_redirect off;
}}", socket_path.to_nginx());
let content = server_config(domain, &format!("
}}",
socket_path.to_nginx()
);
let content = server_config(
domain,
&format!(
"
root {};
{}
", static_path, proxy_content));
",
static_path, proxy_content
),
);
NginxServer::new(domain, content, command_runner)
}
pub fn new_php(domain: &'a str, socket_path: Cow<'a, str>, static_path: Cow<'a, str>, command_runner: &'a C) -> Self {
pub fn new_php(
domain: &'a str,
socket_path: Cow<'a, str>,
static_path: Cow<'a, str>,
command_runner: &'a C,
) -> Self {
let content = server_config(domain, &php_server_config_snippet(socket_path, static_path));
NginxServer::new(domain, content, command_runner)
}
pub fn new_static(domain: &'a str, static_path: &'a str, command_runner: &'a C) -> Self {
let content = server_config(domain, &format!("
let content = server_config(
domain,
&format!(
"
root {};
try_files $uri $uri/ $uri.html =404;
", static_path));
",
static_path
),
);
NginxServer::new(domain, content, command_runner)
}
@ -151,12 +194,15 @@ location @proxy {{
let file_path = String::from("/etc/nginx/sites-enabled/") + domain;
NginxServer {
command_runner,
file: FileSymbol::new(file_path, content)
file: FileSymbol::new(file_path, content),
}
}
}
impl<'a, C: CommandRunner, T> Symbol for NginxServer<'a, C, T> where T: Deref<Target=str> {
impl<'a, C: CommandRunner, T> Symbol for NginxServer<'a, C, T>
where
T: Deref<Target = str>,
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !try!(self.file.target_reached()) {
return Ok(false);
@ -167,7 +213,9 @@ impl<'a, C: CommandRunner, T> Symbol for NginxServer<'a, C, T> where T: Deref<Ta
fn execute(&self) -> Result<(), Box<dyn Error>> {
try!(self.file.execute());
self.command_runner.run_successfully("systemctl", &["reload-or-restart", "nginx"])
self
.command_runner
.run_successfully("systemctl", &["reload-or-restart", "nginx"])
}
fn get_prerequisites(&self) -> Vec<Resource> {
@ -178,13 +226,19 @@ impl<'a, C: CommandRunner, T> Symbol for NginxServer<'a, C, T> where T: Deref<Ta
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> where Self: 'b {
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<'a, C: CommandRunner, T> fmt::Display for NginxServer<'a, C, T> where T: Deref<Target=str> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error>{
impl<'a, C: CommandRunner, T> fmt::Display for NginxServer<'a, C, T>
where
T: Deref<Target = str>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "Nginx server config")
}
}

8
src/symbols/noop.rs

@ -16,14 +16,16 @@ impl Symbol for NoopSymbol {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a> where Self: 'a {
fn into_action<'a>(self: Box<Self>, runner: &'a dyn SymbolRunner) -> Box<dyn Action + 'a>
where
Self: 'a,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl Display for NoopSymbol {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "Noop")
}
}

37
src/symbols/npm.rs

@ -7,12 +7,15 @@ use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct NpmInstall<'a, C: 'a + CommandRunner> {
target: &'a str,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> NpmInstall<'a, C> {
pub fn new(target: &'a str, command_runner: &'a C) -> Self {
NpmInstall { target, command_runner }
NpmInstall {
target,
command_runner,
}
}
}
@ -27,23 +30,41 @@ impl<'a, C: CommandRunner> Symbol for NpmInstall<'a, C> {
if !Path::new(self.target).exists() {
return Ok(false);
}
let result = try!(self.command_runner.run_with_args("sh", &["-c", &format!("cd '{}' && npm ls", self.target)]));
Ok(result.status.success() && !String::from_utf8(result.stdout).unwrap().contains("(empty)"))
let result = try!(self
.command_runner
.run_with_args("sh", &["-c", &format!("cd '{}' && npm ls", self.target)]));
Ok(
result.status.success()
&& !String::from_utf8(result.stdout)
.unwrap()
.contains("(empty)"),
)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully("sh", &["-c", &format!("cd '{}' && npm install --production --unsafe-perm", self.target)])
self.command_runner.run_successfully(
"sh",
&[
"-c",
&format!(
"cd '{}' && npm install --production --unsafe-perm",
self.target
),
],
)
}
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 {
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)]
mod test {
}
mod test {}

44
src/symbols/owner.rs

@ -1,4 +1,4 @@
use std::borrow::{ Borrow, Cow };
use std::borrow::{Borrow, Cow};
use std::error::Error;
use std::fmt;
use std::fs;
@ -11,19 +11,32 @@ use command_runner::CommandRunner;
use resources::Resource;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct Owner<'a, C: 'a + CommandRunner, D> where D: AsRef<str> {
pub struct Owner<'a, C: 'a + CommandRunner, D>
where
D: AsRef<str>,
{
path: D,
user_name: Cow<'a, str>,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner, D> Owner<'a, C, D> where D: AsRef<str> {
impl<'a, C: CommandRunner, D> Owner<'a, C, D>
where
D: AsRef<str>,
{
pub fn new(path: D, user_name: Cow<'a, str>, command_runner: &'a C) -> Self {
Owner { path, user_name, command_runner }
Owner {
path,
user_name,
command_runner,
}
}
}
impl<'a, C: CommandRunner, D> Symbol for Owner<'a, C, D> where D: AsRef<str> {
impl<'a, C: CommandRunner, D> Symbol for Owner<'a, C, D>
where
D: AsRef<str>,
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !Path::new(self.path.as_ref()).exists() {
return Ok(false);
@ -34,24 +47,33 @@ impl<'a, C: CommandRunner, D> Symbol for Owner<'a, C, D> where D: AsRef<str> {
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully("chown", &["-R", self.user_name.borrow(), self.path.as_ref()])
self.command_runner.run_successfully(
"chown",
&["-R", self.user_name.borrow(), self.path.as_ref()],
)
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![ Resource::new("user", self.user_name.to_string()) ]
vec![Resource::new("user", self.user_name.to_string())]
}
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 {
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<'a, C: CommandRunner, D> fmt::Display for Owner<'a, C, D> where D: AsRef<str> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
impl<'a, C: CommandRunner, D> fmt::Display for Owner<'a, C, D>
where
D: AsRef<str>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "Owner {} for {}", self.user_name, self.path.as_ref())
}
}

55
src/symbols/postgresql/database.rs

@ -8,16 +8,23 @@ use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct PostgreSQLDatabase<'a, C: 'a + CommandRunner> {
name: Cow<'a, str>,
seed_file: Cow<'a, str>,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> PostgreSQLDatabase<'a, C> {
pub fn new(name: Cow<'a, str>, seed_file: Cow<'a, str>, command_runner: &'a C) -> Self {
PostgreSQLDatabase { name, seed_file, command_runner }
PostgreSQLDatabase {
name,
seed_file,
command_runner,
}
}
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
let b = try!(self.command_runner.get_output("su", &["-", "postgres", "-c", &format!("psql -t -c \"{}\"", sql)]));
let b = try!(self.command_runner.get_output(
"su",
&["-", "postgres", "-c", &format!("psql -t -c \"{}\"", sql)]
));
Ok(try!(String::from_utf8(b)))
}
}
@ -30,24 +37,52 @@ impl<'a, C: CommandRunner> fmt::Display for PostgreSQLDatabase<'a, C> {
impl<'a, C: CommandRunner> Symbol for PostgreSQLDatabase<'a, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
Ok(try!(self.run_sql(&format!("SELECT datname FROM pg_database WHERE datname LIKE '{}'", self.name))).trim() == self.name)
Ok(
try!(self.run_sql(&format!(
"SELECT datname FROM pg_database WHERE datname LIKE '{}'",
self.name
)))
.trim()
== self.name,
)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully("su", &["-", "postgres", "-c", &format!("createuser {}", self.name)])?;
self.command_runner.run_successfully("su", &["-", "postgres", "-c", &format!("createdb -E UTF8 -T template0 -O {} {0}", self.name)])?;
self.command_runner.run_successfully("su", &["-", "postgres", "-c", &format!("psql '{}' < {}", self.name, self.seed_file)])
self.command_runner.run_successfully(
"su",
&["-", "postgres", "-c", &format!("createuser {}", self.name)],
)?;
self.command_runner.run_successfully(
"su",
&[
"-",
"postgres",
"-c",
&format!("createdb -E UTF8 -T template0 -O {} {0}", self.name),
],
)?;
self.command_runner.run_successfully(
"su",
&[
"-",
"postgres",
"-c",
&format!("psql '{}' < {}", self.name, self.seed_file),
],
)
}
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 {
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)]
mod test {
}
mod test {}

45
src/symbols/postgresql/database_dump.rs

@ -3,22 +3,33 @@ use std::fmt;
use std::str::FromStr;
use command_runner::CommandRunner;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use storage::Storage;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct DatabaseDump<'a, N, C, S> where N: 'a + AsRef<str>, C: 'a + CommandRunner, S: Storage {
pub struct DatabaseDump<'a, N, C, S>
where
N: 'a + AsRef<str>,
C: 'a + CommandRunner,
S: Storage,
{
db_name: N,
storage: S,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, N: AsRef<str>, C: CommandRunner, S: Storage> DatabaseDump<'a, N, C, S> {
pub fn new(db_name: N, storage: S, command_runner: &'a C) -> Self {
DatabaseDump { db_name, storage, command_runner }
DatabaseDump {
db_name,
storage,
command_runner,
}
}
fn run_sql(&self, sql: &str) -> Result<String, Box<dyn Error>> {
let b = try!(self.command_runner.get_output("mariadb", &["--skip-column-names", "-B", "-e", sql]));
let b = try!(self
.command_runner
.get_output("mariadb", &["--skip-column-names", "-B", "-e", sql]));
Ok(try!(String::from_utf8(b)))
}
}
@ -33,23 +44,37 @@ impl<'a, N: AsRef<str>, C: CommandRunner, S: Storage> Symbol for DatabaseDump<'a
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let dump_date = try!(self.storage.recent_date());
let modified_date = try!(self.run_sql(&format!("select UNIX_TIMESTAMP(MAX(UPDATE_TIME)) from information_schema.tables WHERE table_schema = '{}'", self.db_name.as_ref())));
if modified_date.trim_end() == "NULL" { return Ok(false); }
if modified_date.trim_end() == "NULL" {
return Ok(false);
}
Ok(try!(u64::from_str(modified_date.trim_end())) <= dump_date)
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully("sh", &["-c", &format!("mysqldump '{}' > {}", self.db_name.as_ref(), self.storage.write_filename())])
self.command_runner.run_successfully(
"sh",
&[
"-c",
&format!(
"mysqldump '{}' > {}",
self.db_name.as_ref(),
self.storage.write_filename()
),
],
)
}
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 {
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)]
mod test {
}
mod test {}

107
src/symbols/stored_directory.rs

@ -8,32 +8,52 @@ use std::str::FromStr;
use command_runner::CommandRunner;
use resources::Resource;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use storage::Storage;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
#[derive(Debug, PartialEq)]
pub enum StorageDirection { Load, Save }
pub enum StorageDirection {
Load,
Save,
}
pub struct StoredDirectory<'a, S, C: 'a + CommandRunner> where S: Storage {
pub struct StoredDirectory<'a, S, C: 'a + CommandRunner>
where
S: Storage,
{
path: Cow<'a, str>,
storage: S,
dir: StorageDirection,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, S, C: CommandRunner> StoredDirectory<'a, S, C> where S: Storage {
impl<'a, S, C: CommandRunner> StoredDirectory<'a, S, C>
where
S: Storage,
{
pub fn new(path: Cow<'a, str>, storage: S, dir: StorageDirection, command_runner: &'a C) -> Self {
StoredDirectory { path, storage, dir, command_runner }
StoredDirectory {
path,
storage,
dir,
command_runner,
}
}
}
impl<'a, S, C: CommandRunner> fmt::Display for StoredDirectory<'a, S, C> where S: Storage {
impl<'a, S, C: CommandRunner> fmt::Display for StoredDirectory<'a, S, C>
where
S: Storage,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Stored directory {} ({:?})", self.path, self.dir)
}
}
impl<'a, S, C: CommandRunner> Symbol for StoredDirectory<'a, S, C> where S: Storage {
impl<'a, S, C: CommandRunner> Symbol for StoredDirectory<'a, S, C>
where
S: Storage,
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let metadata = fs::metadata(self.path.as_ref());
// Check if dir exists
@ -45,35 +65,74 @@ impl<'a, S, C: CommandRunner> Symbol for StoredDirectory<'a, S, C> where S: Stor
};
}
if !metadata.unwrap().is_dir() {
return Err(Box::new(io::Error::new(io::ErrorKind::AlreadyExists, "Could not create a directory, non-directory file exists")));
return Err(Box::new(io::Error::new(
io::ErrorKind::AlreadyExists,
"Could not create a directory, non-directory file exists",
)));
}
let dump_date = try!(self.storage.recent_date());
let output = try!(self.command_runner.get_output("sh", &["-c", &format!("find {} -printf '%T@\\n' | sort -r | head -n1 | grep '^[0-9]\\+' -o", self.path)]));
let output = try!(self.command_runner.get_output(
"sh",
&[
"-c",
&format!(
"find {} -printf '%T@\\n' | sort -r | head -n1 | grep '^[0-9]\\+' -o",
self.path
)
]
));
let modified_date = try!(u64::from_str(try!(String::from_utf8(output)).trim_end()));
if if self.dir == StorageDirection::Save { modified_date > dump_date } else { dump_date > modified_date } {
let output = try!(self.command_runner.run_with_args("diff", &["-rq", &try!(self.storage.read_filename()), self.path.borrow()]));
if if self.dir == StorageDirection::Save {
modified_date > dump_date
} else {
dump_date > modified_date
} {
let output = try!(self.command_runner.run_with_args(
"diff",
&[
"-rq",
&try!(self.storage.read_filename()),
self.path.borrow()
]
));
match output.status.code() {
Some(0) => Ok(true),
Some(1) => Ok(false),
_ => Err(try!(String::from_utf8(output.stderr)).into())
_ => Err(try!(String::from_utf8(output.stderr)).into()),
}
} else { Ok(true) }
} else {
Ok(true)
}
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
if self.dir == StorageDirection::Load {
try!(self.command_runner.run_successfully("rm", &["-rf", self.path.borrow()]));
self.command_runner.run_successfully("cp", &["-a", &try!(self.storage.read_filename()), self.path.borrow()])
try!(self
.command_runner
.run_successfully("rm", &["-rf", self.path.borrow()]));
self.command_runner.run_successfully(
"cp",
&[
"-a",
&try!(self.storage.read_filename()),
self.path.borrow(),
],
)
} else {
self.command_runner.run_successfully("cp", &["-a", self.path.borrow(), &self.storage.write_filename()])
self.command_runner.run_successfully(
"cp",
&["-a", self.path.borrow(), &self.storage.write_filename()],
)
}
}
fn get_prerequisites(&self) -> Vec<Resource> {
if self.dir == StorageDirection::Save { return vec![]; }
if self.dir == StorageDirection::Save {
return vec![];
}
if let Some(parent) = Path::new(self.path.as_ref()).parent() {
vec![ Resource::new("dir", parent.to_string_lossy()) ]
vec![Resource::new("dir", parent.to_string_lossy())]
} else {
vec![]
}
@ -81,7 +140,7 @@ impl<'a, S, C: CommandRunner> Symbol for StoredDirectory<'a, S, C> where S: Stor
fn provides(&self) -> Option<Vec<Resource>> {
if self.dir == StorageDirection::Load {
Some(vec![ Resource::new("dir", self.path.to_string()) ])
Some(vec![Resource::new("dir", self.path.to_string())])
} else {
None
}
@ -91,11 +150,13 @@ impl<'a, S, C: CommandRunner> Symbol for StoredDirectory<'a, S, C> where S: Stor
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> where Self: 'b {
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)]
mod test {
}
mod test {}

119
src/symbols/systemd/node_js_user_service.rs

@ -1,22 +1,22 @@
use std::error::Error;
use std::fmt;
use std::io;
use std::fs;
use std::io;
use std::ops::Deref;
use std::process::Output;
use std::thread::sleep;
use std::time::Duration;
use std::ops::Deref;
use command_runner::{CommandRunner, SetuidCommandRunner};
use resources::Resource;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use symbols::file::File as FileSymbol;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
#[derive(Debug)]
pub enum NodeJsSystemdUserServiceError<E: Error> {
ActivationFailed(io::Result<Output>),
ExecError(E),
GenericError
GenericError,
}
impl From<io::Error> for NodeJsSystemdUserServiceError<io::Error> {
@ -30,13 +30,13 @@ impl<E: Error> Error for NodeJsSystemdUserServiceError<E> {
match self {
NodeJsSystemdUserServiceError::ExecError(ref e) => e.description(),
NodeJsSystemdUserServiceError::GenericError => "Generic error",
NodeJsSystemdUserServiceError::ActivationFailed(_) => "Activation of service failed"
NodeJsSystemdUserServiceError::ActivationFailed(_) => "Activation of service failed",
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
NodeJsSystemdUserServiceError::ExecError(ref e) => Some(e),
_ => None
_ => None,
}
}
}
@ -51,19 +51,37 @@ impl<E: Error> fmt::Display for NodeJsSystemdUserServiceError<E> {
}
}
pub struct NodeJsSystemdUserService<'a, C, R> where C: Deref<Target=str>, R: CommandRunner {
pub struct NodeJsSystemdUserService<'a, C, R>
where
C: Deref<Target = str>,
R: CommandRunner,
{
service_name: &'a str,
user_name: &'a str,
command_runner: R,
file: FileSymbol<C, String>
file: FileSymbol<C, String>,
}
impl<'a, R> NodeJsSystemdUserService<'a, String, SetuidCommandRunner<'a, R>> where R: CommandRunner {
pub fn new(home: &'a str, user_name: &'a str, service_name: &'a str, path: &'a str, command_runner: &'a R) -> Self {
let file_path = format!("{}/.config/systemd/user/{}.service", home.trim_end(), service_name);
impl<'a, R> NodeJsSystemdUserService<'a, String, SetuidCommandRunner<'a, R>>
where
R: CommandRunner,
{
pub fn new(
home: &'a str,
user_name: &'a str,
service_name: &'a str,
path: &'a str,
command_runner: &'a R,
) -> Self {
let file_path = format!(
"{}/.config/systemd/user/{}.service",
home.trim_end(),
service_name
);
let port = format!("/var/tmp/{}-{}.socket", user_name, service_name);
let content = format!("[Service]
let content = format!(
"[Service]
Environment=NODE_ENV=production
Environment=PORT={1}
ExecStartPre=/bin/rm -f {1}
@ -78,19 +96,24 @@ Restart=always
[Install]
WantedBy=default.target
", path, port);
",
path, port
);
NodeJsSystemdUserService {
service_name,
user_name,
command_runner: SetuidCommandRunner::new(user_name, command_runner),
file: FileSymbol::new(file_path, content)
file: FileSymbol::new(file_path, content),
}
}
}
impl<'a, C, R> NodeJsSystemdUserService<'a, C, R> where C: Deref<Target=str>, R: CommandRunner {
impl<'a, C, R> NodeJsSystemdUserService<'a, C, R>
where
C: Deref<Target = str>,
R: CommandRunner,
{
fn systemctl_wait_for_dbus(&self, args: &[&str]) -> Result<String, Box<dyn Error>> {
let mut tries = 5;
loop {
@ -102,7 +125,11 @@ impl<'a, C, R> NodeJsSystemdUserService<'a, C, R> where C: Deref<Target=str>, R:
return Err(stderr.into());
}
} else {
return Ok(try!(String::from_utf8(result.stdout)).trim_end().to_string());
return Ok(
try!(String::from_utf8(result.stdout))
.trim_end()
.to_string(),
);
}
tries -= 1;
if tries == 0 {
@ -114,18 +141,36 @@ impl<'a, C, R> NodeJsSystemdUserService<'a, C, R> where C: Deref<Target=str>, R:
fn check_if_service(&self) -> Result<bool, Box<dyn Error>> {
loop {
let active_state = try!(self.systemctl_wait_for_dbus(&["--user", "show", "--property", "ActiveState", self.service_name]));
let active_state = try!(self.systemctl_wait_for_dbus(&[
"--user",
"show",
"--property",
"ActiveState",
self.service_name
]));
match active_state.as_ref() {
"ActiveState=activating" => sleep(Duration::from_millis(500)),
"ActiveState=active" => return Ok(true),
"ActiveState=failed" => return Err(Box::new(NodeJsSystemdUserServiceError::ActivationFailed(self.command_runner.run_with_args("journalctl", &["--user", &format!("--user-unit={}", self.service_name)])) as NodeJsSystemdUserServiceError<io::Error>)),
_ => return Ok(false)
"ActiveState=failed" => {
return Err(Box::new(NodeJsSystemdUserServiceError::ActivationFailed(
self.command_runner.run_with_args(
"journalctl",
&["--user", &format!("--user-unit={}", self.service_name)],
),
)
as NodeJsSystemdUserServiceError<io::Error>))
}
_ => return Ok(false),
}
}
}
}
impl<'a, C, R> Symbol for NodeJsSystemdUserService<'a, C, R> where C: Deref<Target=str>, R: CommandRunner {
impl<'a, C, R> Symbol for NodeJsSystemdUserService<'a, C, R>
where
C: Deref<Target = str>,
R: CommandRunner,
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !(try!(self.file.target_reached())) {
return Ok(false);
@ -139,15 +184,22 @@ impl<'a, C, R> Symbol for NodeJsSystemdUserService<'a, C, R> where C: Deref<Targ
try!(self.systemctl_wait_for_dbus(&["--user", "restart", self.service_name]));
if !(try!(self.check_if_service())) {
return Err(Box::new(NodeJsSystemdUserServiceError::GenericError as NodeJsSystemdUserServiceError<io::Error>));
return Err(Box::new(
NodeJsSystemdUserServiceError::GenericError as NodeJsSystemdUserServiceError<io::Error>,
));
}
let file_name = format!("/var/tmp/{}-{}.socket", self.user_name, self.service_name);
fs::metadata(&file_name).map(|_| ()).map_err(|e| Box::new(e) as Box<dyn Error>)
fs::metadata(&file_name)
.map(|_| ())
.map_err(|e| Box::new(e) as Box<dyn Error>)
}
fn get_prerequisites(&self) -> Vec<Resource> {
let mut r = vec![ Resource::new("file", format!("/var/lib/systemd/linger/{}", self.user_name)) ];
let mut r = vec![Resource::new(
"file",
format!("/var/lib/systemd/linger/{}", self.user_name),
)];
r.extend(self.file.get_prerequisites().into_iter());
r
}
@ -156,13 +208,24 @@ impl<'a, C, R> Symbol for NodeJsSystemdUserService<'a, C, R> where C: Deref<Targ
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> where Self: 'b {
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<'a, C, R> fmt::Display for NodeJsSystemdUserService<'a, C, R> where C: Deref<Target=str>, R: CommandRunner {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error>{
write!(f, "Systemd Node.js user service unit for {}", self.service_name)
impl<'a, C, R> fmt::Display for NodeJsSystemdUserService<'a, C, R>
where
C: Deref<Target = str>,
R: CommandRunner,
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
f,
"Systemd Node.js user service unit for {}",
self.service_name
)
}
}

18
src/symbols/systemd/reload.rs

@ -6,12 +6,15 @@ use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct ReloadService<'a, C: 'a + CommandRunner> {
service: &'a str,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> ReloadService<'a, C> {
pub fn new(service: &'a str, command_runner: &'a C) -> Self {
ReloadService { service, command_runner }
ReloadService {
service,
command_runner,
}
}
}
@ -21,20 +24,25 @@ impl<'a, C: CommandRunner> Symbol for ReloadService<'a, C> {
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully("systemctl", &["reload-or-restart", self.service])
self
.command_runner
.run_successfully("systemctl", &["reload-or-restart", self.service])
}
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 {
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<'a, C: CommandRunner> fmt::Display for ReloadService<'a, C> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error>{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "Reload service {}", self.service)
}
}

26
src/symbols/systemd/user_session.rs

@ -1,4 +1,4 @@
use std::borrow::{ Borrow, Cow };
use std::borrow::{Borrow, Cow};
use std::error::Error;
use std::fmt;
use std::path::PathBuf;
@ -9,20 +9,20 @@ use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
#[derive(Debug)]
pub enum SystemdUserSessionError<E: Error> {
ExecError(E),
GenericError
GenericError,
}
impl<E: Error> Error for SystemdUserSessionError<E> {
fn description(&self) -> &str {
match self {
SystemdUserSessionError::ExecError(ref e) => e.description(),
SystemdUserSessionError::GenericError => "Generic error"
SystemdUserSessionError::GenericError => "Generic error",
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
SystemdUserSessionError::ExecError(ref e) => Some(e),
_ => None
_ => None,
}
}
}
@ -35,12 +35,15 @@ impl<E: Error> fmt::Display for SystemdUserSessionError<E> {
pub struct SystemdUserSession<'a, C: 'a + CommandRunner> {
user_name: Cow<'a, str>,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> SystemdUserSession<'a, C> {
pub fn new(user_name: Cow<'a, str>, command_runner: &'a C) -> Self {
SystemdUserSession { user_name, command_runner }
SystemdUserSession {
user_name,
command_runner,
}
}
}
@ -53,20 +56,25 @@ impl<'a, C: CommandRunner> Symbol for SystemdUserSession<'a, C> {
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully("loginctl", &["enable-linger", self.user_name.borrow()])
self
.command_runner
.run_successfully("loginctl", &["enable-linger", self.user_name.borrow()])
}
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 {
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<'a, C: CommandRunner> fmt::Display for SystemdUserSession<'a, C> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error>{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "Systemd user session for {}", self.user_name)
}
}

35
src/symbols/tls/csr.rs

@ -9,12 +9,15 @@ use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct TlsCsr<'a, C: 'a + CommandRunner> {
domain: Cow<'a, str>,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> TlsCsr<'a, C> {
pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self {
TlsCsr { domain, command_runner }
TlsCsr {
domain,
command_runner,
}
}
fn get_key_path(&self) -> String {
@ -38,12 +41,28 @@ impl<'a, C: CommandRunner> Symbol for TlsCsr<'a, C> {
return Ok(false);
}
let output = try!(self.command_runner.get_stderr("openssl", &["req", "-in", &self.get_csr_path(), "-noout", "-verify"]));
let output = try!(self.command_runner.get_stderr(
"openssl",
&["req", "-in", &self.get_csr_path(), "-noout", "-verify"]
));
Ok(output == b"verify OK\n")
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
try!(self.command_runner.run_successfully("openssl", &["req", "-new", "-sha256", "-key", &self.get_key_path(), "-out", &self.get_csr_path(), "-subj", &format!("/CN={}", self.domain)]));
try!(self.command_runner.run_successfully(
"openssl",
&[
"req",
"-new",
"-sha256",
"-key",
&self.get_key_path(),
"-out",
&self.get_csr_path(),
"-subj",
&format!("/CN={}", self.domain)
]
));
Ok(())
}
@ -55,11 +74,13 @@ impl<'a, C: CommandRunner> Symbol for TlsCsr<'a, C> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> where Self: 'b {
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)]
mod test {
}
mod test {}

30
src/symbols/tls/key.rs

@ -8,12 +8,15 @@ use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct TlsKey<'a, C: 'a + CommandRunner> {
domain: Cow<'a, str>,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> TlsKey<'a, C> {
pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self {
TlsKey { domain, command_runner }
TlsKey {
domain,
command_runner,
}
}
fn get_path(&self) -> String {
@ -37,23 +40,36 @@ impl<'a, C: CommandRunner> Symbol for TlsKey<'a, C> {
return Ok(false);
}
let output = try!(self.command_runner.get_output("openssl", &["rsa", "-in", &self.get_path(), "-noout", "-check", "-text"]));
let output = try!(self.command_runner.get_output(
"openssl",
&["rsa", "-in", &self.get_path(), "-noout", "-check", "-text"]
));
Ok(output.starts_with(&format!("Private-Key: ({} bit)\n", self.get_bytes()).as_bytes()))
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully("openssl", &["genrsa", "-out", &self.get_path(), &self.get_bytes().to_string()])
self.command_runner.run_successfully(
"openssl",
&[
"genrsa",
"-out",
&self.get_path(),
&self.get_bytes().to_string(),
],
)
}
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 {
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)]
mod test {
}
mod test {}

75
src/symbols/tls/self_signed_cert.rs

@ -9,12 +9,15 @@ use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct SelfSignedTlsCert<'a, C: 'a + CommandRunner> {
domain: Cow<'a, str>,
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> SelfSignedTlsCert<'a, C> {
pub fn new(domain: Cow<'a, str>, command_runner: &'a C) -> Self {
SelfSignedTlsCert { domain, command_runner }
SelfSignedTlsCert {
domain,
command_runner,
}
}
fn get_key_path(&self) -> String {
@ -32,29 +35,65 @@ impl<'a, C: CommandRunner> fmt::Display for SelfSignedTlsCert<'a, C> {
}
}
const DAYS_IN_SECONDS: u32 = 24*60*60;
const DAYS_IN_SECONDS: u32 = 24 * 60 * 60;
impl<'a, C: CommandRunner> Symbol for SelfSignedTlsCert<'a, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !Path::new(&self.get_cert_path()).exists() {
return Ok(false);
}
let output = try!(self.command_runner.run_with_args("openssl", &["x509", "-in", &self.get_cert_path(), "-noout", "-subject", "-checkend", &(30*DAYS_IN_SECONDS).to_string()]));
println!("{}", output.status.code().unwrap());
let output = try!(self.command_runner.run_with_args(
"openssl",
&[
"x509",
"-in",
&self.get_cert_path(),
"-noout",
"-subject",
"-checkend",
&(30 * DAYS_IN_SECONDS).to_string()
]
));
println!("{}", output.status.code().unwrap());
match output.status.code() {
Some(0) => Ok(output.stdout == format!("subject=CN = {}\nCertificate will not expire\n", self.domain).as_bytes()),
Some(_) => if output.stdout == format!("subject=CN = {}\nCertificate will expire\n", self.domain).as_bytes() {
Ok(false)
} else {
Err("Exit code non-zero, but wrong stdout".to_string().into())
},
_ => Err("Apparently killed by signal".to_string().into())
Some(0) => Ok(
output.stdout
== format!(
"subject=CN = {}\nCertificate will not expire\n",
self.domain
)
.as_bytes(),
),
Some(_) => {
if output.stdout
== format!("subject=CN = {}\nCertificate will expire\n", self.domain).as_bytes()
{
Ok(false)
} else {
Err("Exit code non-zero, but wrong stdout".to_string().into())
}
}
_ => Err("Apparently killed by signal".to_string().into()),
}
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.command_runner.run_successfully("openssl", &["req", "-x509", "-sha256", "-days", "90", "-key", &self.get_key_path(), "-out", &self.get_cert_path(), "-subj", &format!("/CN={}", self.domain)])
self.command_runner.run_successfully(
"openssl",
&[
"req",
"-x509",
"-sha256",
"-days",
"90",
"-key",
&self.get_key_path(),
"-out",
&self.get_cert_path(),
"-subj",
&format!("/CN={}", self.domain),
],
)
}
fn get_prerequisites(&self) -> Vec<Resource> {
@ -65,11 +104,13 @@ println!("{}", output.status.code().unwrap());
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> where Self: 'b {
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)]
mod test {
}
mod test {}

87
src/symbols/user.rs

@ -10,7 +10,7 @@ use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub enum UserAdderError {
AlreadyExists,
UnknownError,
ImplError(Box<dyn Error>)
ImplError(Box<dyn Error>),
}
impl Error for UserAdderError {
@ -18,13 +18,13 @@ impl Error for UserAdderError {
match self {
UserAdderError::AlreadyExists => "User already exists",
UserAdderError::UnknownError => "Unknown error",
UserAdderError::ImplError(_) => "User adding error"
UserAdderError::ImplError(_) => "User adding error",
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
UserAdderError::ImplError(ref e) => Some(e.as_ref()),
_ => None
_ => None,
}
}
}
@ -33,7 +33,7 @@ impl fmt::Display for UserAdderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.cause() {
Some(e) => write!(f, "{} (cause: {})", self.description(), e),
None => write!(f, "{}", self.description())
None => write!(f, "{}", self.description()),
}
}
}
@ -44,18 +44,18 @@ pub trait UserAdder {
#[derive(Debug, PartialEq)]
pub enum UserError {
GenericError
GenericError,
}
impl Error for UserError {
fn description(&self) -> &str {
match self {
UserError::GenericError => "Could not find out if user exists"
UserError::GenericError => "Could not find out if user exists",
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
_ => None
_ => None,
}
}
}
@ -64,7 +64,7 @@ impl fmt::Display for UserError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.cause() {
Some(e) => write!(f, "{} (cause: {})", self.description(), e),
None => write!(f, "{}", self.description())
None => write!(f, "{}", self.description()),
}
}
}
@ -72,12 +72,16 @@ impl fmt::Display for UserError {
pub struct User<'a, C: 'a + CommandRunner, A: 'a + UserAdder> {
user_name: Cow<'a, str>,
command_runner: &'a C,
user_adder: &'a A
user_adder: &'a A,
}
impl<'a, C: CommandRunner, A: 'a + UserAdder> User<'a, C, A> {
pub fn new(user_name: Cow<'a, str>, command_runner: &'a C, user_adder: &'a A) -> Self {
User { user_name, command_runner, user_adder }
User {
user_name,
command_runner,
user_adder,
}
}
}
@ -89,16 +93,21 @@ impl<'a, C: CommandRunner, A: 'a + UserAdder> fmt::Display for User<'a, C, A> {
impl<'a, C: CommandRunner, A: 'a + UserAdder> Symbol for User<'a, C, A> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
let output = try!(self.command_runner.run_with_args("getent", &["passwd", &*self.user_name]));
let output = try!(self
.command_runner
.run_with_args("getent", &["passwd", &*self.user_name]));
match output.status.code() {
Some(2) => Ok(false),
Some(0) => Ok(true),
_ => Err(Box::new(UserError::GenericError))
_ => Err(Box::new(UserError::GenericError)),
}
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self.user_adder.add_user(&*self.user_name).map_err(|e| Box::new(e) as Box<dyn Error>)
self
.user_adder
.add_user(&*self.user_name)
.map_err(|e| Box::new(e) as Box<dyn Error>)
}
fn provides(&self) -> Option<Vec<Resource>> {
@ -109,13 +118,16 @@ impl<'a, C: CommandRunner, A: 'a + UserAdder> Symbol for User<'a, C, A> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> where Self: 'b {
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
pub struct SystemUserAdder<'a, C: 'a + CommandRunner> {
command_runner: &'a C
command_runner: &'a C,
}
impl<'a, C: CommandRunner> SystemUserAdder<'a, C> {
@ -130,27 +142,26 @@ impl<'a, C: CommandRunner> UserAdder for SystemUserAdder<'a, C> {
"adduser",
&[
// "-m", // Necessary for Fedora, not accepted in Debian
"--system",
user_name
]);
"--system", user_name,
],
);
match output {
Ok(output) => match output.status.code() {
Some(0) => Ok(()),
Some(1) =>
{
println!("{:?}", output);
Err(UserAdderError::AlreadyExists)},
Some(_) =>
{
println!("{:?}", output);
Err(UserAdderError::UnknownError)
},
Some(1) => {
println!("{:?}", output);
Err(UserAdderError::AlreadyExists)
}
Some(_) => {
println!("{:?}", output);
Err(UserAdderError::UnknownError)
}
None => {
println!("{:?}", output);
Err(UserAdderError::UnknownError)
},
println!("{:?}", output);
Err(UserAdderError::UnknownError)
}
},
Err(e) => Err(UserAdderError::ImplError(Box::new(e)))
Err(e) => Err(UserAdderError::ImplError(Box::new(e))),
}
}
}
@ -161,10 +172,10 @@ mod test {
use std::fmt;
use command_runner::StdCommandRunner;
use symbols::Symbol;
use symbols::user::User;
use symbols::user::UserAdder;
use symbols::user::UserAdderError;
use symbols::Symbol;
#[derive(Debug, PartialEq)]
struct DummyError;
@ -190,13 +201,21 @@ mod test {
#[test]
fn test_target_reached_nonexisting() {
let symbol = User { user_name: "nonexisting".into(), command_runner: &StdCommandRunner, user_adder: &DummyUserAdder };
let symbol = User {
user_name: "nonexisting".into(),
command_runner: &StdCommandRunner,
user_adder: &DummyUserAdder,
};
assert_eq!(symbol.target_reached().unwrap(), false);
}
#[test]
fn test_target_reached_root() {
let symbol = User { user_name: "root".into(), command_runner: &StdCommandRunner, user_adder: &DummyUserAdder };
let symbol = User {
user_name: "root".into(),
command_runner: &StdCommandRunner,
user_adder: &DummyUserAdder,
};
assert_eq!(symbol.target_reached().unwrap(), true);
}
}

83
src/symbols/wordpress/plugin.rs

@ -8,26 +8,44 @@ use std::ops::Deref;
use std::path::{Path, PathBuf};
use command_runner::CommandRunner;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use resources::Resource;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct WordpressPlugin<'a, C, R> where C: Deref<Target=str>, R: 'a + CommandRunner {
pub struct WordpressPlugin<'a, C, R>
where
C: Deref<Target = str>,
R: 'a + CommandRunner,
{
base: C,
name: C,
command_runner: &'a R
command_runner: &'a R,
}
impl<'a, C, R> WordpressPlugin<'a, C, R> where C: Deref<Target=str>, R: CommandRunner {
impl<'a, C, R> WordpressPlugin<'a, C, R>
where
C: Deref<Target = str>,
R: CommandRunner,
{
pub fn new(base: C, name: C, command_runner: &'a R) -> Self {
WordpressPlugin { base, name, command_runner }
WordpressPlugin {
base,
name,
command_runner,
}
}
fn get_path(&self) -> PathBuf {
Path::new(&*self.base).join("wp-content/plugins").join(&*self.name)
Path::new(&*self.base)
.join("wp-content/plugins")
.join(&*self.name)
}
}
impl<'a, C, R> Symbol for WordpressPlugin<'a, C, R> where C: Deref<Target=str>, R: CommandRunner {
impl<'a, C, R> Symbol for WordpressPlugin<'a, C, R>
where
C: Deref<Target = str>,
R: CommandRunner,
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.get_path().exists() {
return Ok(false);
@ -43,7 +61,7 @@ impl<'a, C, R> Symbol for WordpressPlugin<'a, C, R> where C: Deref<Target=str>,
} else {
Err(Box::new(e))
};
},
}
Ok(file) => {
let reader = BufReader::new(file);
let regex = Regex::new("(?m)^(Plugin URI|Version): (.+)$")?;
@ -58,22 +76,45 @@ impl<'a, C, R> Symbol for WordpressPlugin<'a, C, R> where C: Deref<Target=str>,
}
}
}
let upstream = try!(self.command_runner.get_output("curl", &["--form", &format!(r###"plugins={{"plugins":{{"{0}/{0}.php":{{"Version":"{1}", "PluginURI":"{2}"}}}}}}"###, &*self.name, version, plugin_uri), "https://api.wordpress.org/plugins/update-check/1.1/"]));
let upstream = try!(self.command_runner.get_output(
"curl",
&[
"--form",
&format!(
r###"plugins={{"plugins":{{"{0}/{0}.php":{{"Version":"{1}", "PluginURI":"{2}"}}}}}}"###,
&*self.name, version, plugin_uri
),
"https://api.wordpress.org/plugins/update-check/1.1/"
]
));
Ok(try!(String::from_utf8(upstream)).contains(r###""plugins":[]"###))
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
let source = format!("https://downloads.wordpress.org/plugin/{}.zip", &*self.name);
let zip = format!("/tmp/{}.zip", &*self.name);
try!(self.command_runner.run_successfully("curl", &[source.as_ref() as &str, "-o", zip.as_ref()]));
try!(self.command_runner.run_successfully("rm", &["-rf", &self.get_path().to_string_lossy()]));
self.command_runner.run_successfully("unzip", &[zip.as_ref(), "-d".as_ref(), Path::new(&*self.base).join("wp-content/plugins").as_os_str()])
try!(self
.command_runner
.run_successfully("curl", &[source.as_ref() as &str, "-o", zip.as_ref()]));
try!(self
.command_runner
.run_successfully("rm", &["-rf", &self.get_path().to_string_lossy()]));
self.command_runner.run_successfully(
"unzip",
&[
zip.as_ref(),
"-d".as_ref(),
Path::new(&*self.base)
.join("wp-content/plugins")
.as_os_str(),
],
)
}
fn get_prerequisites(&self) -> Vec<Resource> {
match self.get_path().parent() {
Some(p) => vec![ Resource::new("dir", p.to_string_lossy()) ],
None => vec![]
Some(p) => vec![Resource::new("dir", p.to_string_lossy())],
None => vec![],
}
}
@ -81,14 +122,20 @@ impl<'a, C, R> Symbol for WordpressPlugin<'a, C, R> where C: Deref<Target=str>,
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> where Self: 'b {
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<'a, C, R> fmt::Display for WordpressPlugin<'a, C, R> where C: Deref<Target=str>, R: CommandRunner {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
impl<'a, C, R> fmt::Display for WordpressPlugin<'a, C, R>
where
C: Deref<Target = str>,
R: CommandRunner,
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "WordpressPlugin {}", &*self.name)
}
}

112
src/symbols/wordpress/translation.rs

@ -8,34 +8,66 @@ use std::io::{BufRead, BufReader};
use std::path::Path;
use command_runner::CommandRunner;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
use resources::Resource;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};
pub struct WordpressTranslation<'a, C, D, R> where C: AsRef<str>, D: AsRef<str>, R: 'a + CommandRunner {
pub struct WordpressTranslation<'a, C, D, R>
where
C: AsRef<str>,
D: AsRef<str>,
R: 'a + CommandRunner,
{
path: D,
version: &'a str,
locale: C,
command_runner: &'a R
command_runner: &'a R,
}
impl<'a, C, R> WordpressTranslation<'a, C, String, R> where C: AsRef<str>, R: CommandRunner {
impl<'a, C, R> WordpressTranslation<'a, C, String, R>
where
C: AsRef<str>,
R: CommandRunner,
{
pub fn new<D: AsRef<str>>(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self {
WordpressTranslation {
path: Path::new(path.as_ref()).join("wp-content/languages").to_string_lossy().to_string(),
path: Path::new(path.as_ref())
.join("wp-content/languages")
.to_string_lossy()
.to_string(),
version,
locale,
command_runner
command_runner,
}
}
}
impl<'a, C, D, R> WordpressTranslation<'a, C, D, R> where C: AsRef<str>, D: AsRef<str>, R: CommandRunner {
impl<'a, C, D, R> WordpressTranslation<'a, C, D, R>
where
C: AsRef<str>,
D: AsRef<str>,
R: CommandRunner,
{
fn get_pairs(&self) -> Vec<(String, String)> {
let version_x = self.version.trim_end_matches(|c: char| c.is_digit(10)).to_owned() + "x";
let version_x = self
.version
.trim_end_matches(|c: char| c.is_digit(10))
.to_owned()
+ "x";
let locale: &str = self.locale.as_ref();
let path_locale = if locale == "de_DE" { "de".to_owned() } else { locale.to_lowercase().replace('_', "-") };
let path_locale = if locale == "de_DE" {
"de".to_owned()
} else {
locale.to_lowercase().replace('_', "-")
};
let mut res = vec![];
for &(in_slug, out_slug) in [("", ""), ("cc/", "continents-cities-"), ("admin/", "admin-"), ("admin/network/", "admin-network-")].iter() {
for &(in_slug, out_slug) in [
("", ""),
("cc/", "continents-cities-"),
("admin/", "admin-"),
("admin/network/", "admin-network-"),
]
.iter()
{
for format in ["po", "mo"].iter() {
res.push((
format!("https://translate.wordpress.org/projects/wp/{}/{}{}/default/export-translations?format={}", version_x, in_slug, path_locale, format),
@ -47,7 +79,12 @@ impl<'a, C, D, R> WordpressTranslation<'a, C, D, R> where C: AsRef<str>, D: AsRe
}
}
impl<'a, C, D, R> Symbol for WordpressTranslation<'a, C, D, R> where C: AsRef<str>, D: AsRef<str>, R: CommandRunner {
impl<'a, C, D, R> Symbol for WordpressTranslation<'a, C, D, R>
where
C: AsRef<str>,
D: AsRef<str>,
R: CommandRunner,
{
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();
@ -60,45 +97,68 @@ impl<'a, C, D, R> Symbol for WordpressTranslation<'a, C, D, R> where C: AsRef<st
} else {
Err(Box::new(e))
};
},
Ok(file) => if target.ends_with(".po") {
let reader = BufReader::new(file);
for content in reader.lines() {
if let Some(match_result) = match_date.captures(&try!(content)) {
newest = max(newest, match_result[1].to_string());
break;
}
Ok(file) => {
if target.ends_with(".po") {
let reader = BufReader::new(file);
for content in reader.lines() {
if let Some(match_result) = match_date.captures(&try!(content)) {
newest = max(newest, match_result[1].to_string());
break;
}
}
}
}
}
}
let upstream = try!(self.command_runner.get_output("curl", &[&format!("https://api.wordpress.org/core/version-check/1.7/?version={}&locale={}", self.version, self.locale.as_ref())]));
Ok(try!(String::from_utf8(upstream)).contains(&format!(r###"language":"{}","version":"{}","updated":"{}"###, self.locale.as_ref(), self.version, newest)))
let upstream = try!(self.command_runner.get_output(
"curl",
&[&format!(
"https://api.wordpress.org/core/version-check/1.7/?version={}&locale={}",
self.version,
self.locale.as_ref()
)]
));
Ok(try!(String::from_utf8(upstream)).contains(&format!(
r###"language":"{}","version":"{}","updated":"{}"###,
self.locale.as_ref(),
self.version,
newest
)))
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
for (source, target) in self.get_pairs() {
try!(self.command_runner.run_successfully("curl", &["--compressed", "-o", &target, &source]));
try!(self
.command_runner
.run_successfully("curl", &["--compressed", "-o", &target, &source]));
}
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![ Resource::new("dir", self.path.as_ref()) ]
vec![Resource::new("dir", self.path.as_ref())]
}
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 {
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
where
Self: 'b,
{
Box::new(OwnedSymbolAction::new(runner, *self))
}
}
impl<'a, C, D, R> fmt::Display for WordpressTranslation<'a, C, D, R> where C: AsRef<str>, D: AsRef<str>, R: CommandRunner {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
impl<'a, C, D, R> fmt::Display for WordpressTranslation<'a, C, D, R>
where
C: AsRef<str>,
D: AsRef<str>,
R: CommandRunner,
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "WordpressTranslation {}", self.path.as_ref())
}
}

13
tests/file.rs

@ -1,12 +1,12 @@
extern crate schematics;
extern crate tempdir;
use schematics::symbols::file::File as FileSymbol;
use schematics::symbols::Symbol;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use tempdir::TempDir;
use schematics::symbols::Symbol;
use schematics::symbols::file::File as FileSymbol;
fn get_dir(content: Option<&str>) -> TempDir {
let tmp_dir = TempDir::new("unittest").expect("create temp dir");
@ -15,12 +15,17 @@ fn get_dir(content: Option<&str>) -> TempDir {
}
let file_path = tmp_dir.path().join("filename");
let mut tmp_file = File::create(file_path).expect("create temp file");
tmp_file.write_all(content.unwrap().as_bytes()).expect("write temp file");
tmp_file
.write_all(content.unwrap().as_bytes())
.expect("write temp file");
tmp_dir
}
fn get_symbol(path: &Path) -> FileSymbol<&str, String> {
FileSymbol::new(String::from(path.join("filename").to_str().unwrap()), "target content")
FileSymbol::new(
String::from(path.join("filename").to_str().unwrap()),
"target content",
)
}
// Normal cases

96
tests/storage.rs

@ -3,12 +3,12 @@ extern crate schematics;
extern crate tempdir;
use regex::Regex;
use schematics::storage::{SimpleStorage, Storage};
use std::fs::{create_dir, File};
use std::path::Path;
use tempdir::TempDir;
use schematics::storage::{SimpleStorage, Storage};
fn get_dir<'a, I: IntoIterator<Item=&'a &'a str>>(content: I) -> TempDir {
fn get_dir<'a, I: IntoIterator<Item = &'a &'a str>>(content: I) -> TempDir {
let tmp_dir = TempDir::new("unittest").expect("create temp dir");
let storage_path = tmp_dir.path().join("_filename");
create_dir(storage_path.clone()).unwrap();
@ -30,8 +30,15 @@ fn single_file() {
let dir = get_dir(&["12345"]);
let storage = get_storage(dir.path());
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
assert_eq!(dir.path().join("_filename").join("12345"), Path::new(&storage.read_filename().unwrap()));
assert!(
Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display()))
.unwrap()
.is_match(&storage.write_filename())
);
assert_eq!(
dir.path().join("_filename").join("12345"),
Path::new(&storage.read_filename().unwrap())
);
assert_eq!(storage.recent_date().unwrap(), 12345);
}
@ -40,8 +47,15 @@ fn two_files() {
let dir = get_dir(&["12345", "23456"]);
let storage = get_storage(dir.path());
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
assert_eq!(dir.path().join("_filename").join("23456"), Path::new(&storage.read_filename().unwrap()));
assert!(
Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display()))
.unwrap()
.is_match(&storage.write_filename())
);
assert_eq!(
dir.path().join("_filename").join("23456"),
Path::new(&storage.read_filename().unwrap())
);
assert_eq!(storage.recent_date().unwrap(), 23456);
}
@ -50,8 +64,15 @@ fn another_two_files() {
let dir = get_dir(&["23456", "12345"]);
let storage = get_storage(dir.path());
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
assert_eq!(dir.path().join("_filename").join("23456"), Path::new(&storage.read_filename().unwrap()));
assert!(
Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display()))
.unwrap()
.is_match(&storage.write_filename())
);
assert_eq!(
dir.path().join("_filename").join("23456"),
Path::new(&storage.read_filename().unwrap())
);
assert_eq!(storage.recent_date().unwrap(), 23456);
}
@ -60,8 +81,15 @@ fn three_files() {
let dir = get_dir(&["23456", "9", "12345"]);
let storage = get_storage(dir.path());
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
assert_eq!(dir.path().join("_filename").join("23456"), Path::new(&storage.read_filename().unwrap()));
assert!(
Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display()))
.unwrap()
.is_match(&storage.write_filename())
);
assert_eq!(
dir.path().join("_filename").join("23456"),
Path::new(&storage.read_filename().unwrap())
);
assert_eq!(storage.recent_date().unwrap(), 23456);
}
@ -72,11 +100,21 @@ fn empty_storage() {
let dir = TempDir::new("unittest").expect("create temp dir");
let storage = get_storage(dir.path());
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
assert!(
Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display()))
.unwrap()
.is_match(&storage.write_filename())
);
assert!(storage.read_filename().is_err());
assert_eq!(storage.read_filename().unwrap_err().description(), "entity not found");
assert_eq!(
storage.read_filename().unwrap_err().description(),
"entity not found"
);
assert!(storage.recent_date().is_err());
assert_eq!(storage.recent_date().unwrap_err().description(), "entity not found");
assert_eq!(
storage.recent_date().unwrap_err().description(),
"entity not found"
);
}
#[test]
@ -84,11 +122,21 @@ fn empty_storage_for_filename() {
let dir = get_dir(&[]);
let storage = get_storage(dir.path());
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
assert!(
Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display()))
.unwrap()
.is_match(&storage.write_filename())
);
assert!(storage.read_filename().is_err());
assert_eq!(storage.read_filename().unwrap_err().description(), "Not found");
assert_eq!(
storage.read_filename().unwrap_err().description(),
"Not found"
);
assert!(storage.recent_date().is_err());
assert_eq!(storage.recent_date().unwrap_err().description(), "Not found");
assert_eq!(
storage.recent_date().unwrap_err().description(),
"Not found"
);
}
#[test]
@ -96,9 +144,19 @@ fn bad_file() {
let dir = get_dir(&["abba"]);
let storage = get_storage(dir.path());
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
assert!(
Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display()))
.unwrap()
.is_match(&storage.write_filename())
);
assert!(storage.read_filename().is_err());
assert_eq!(storage.read_filename().unwrap_err().description(), "Not found");
assert_eq!(
storage.read_filename().unwrap_err().description(),
"Not found"
);
assert!(storage.recent_date().is_err());
assert_eq!(storage.recent_date().unwrap_err().description(), "Not found");
assert_eq!(
storage.recent_date().unwrap_err().description(),
"Not found"
);
}
Loading…
Cancel
Save