Update
This commit is contained in:
parent
764937e034
commit
8882712925
9 changed files with 346 additions and 3 deletions
|
|
@ -8,3 +8,4 @@ users = "0.5.0"
|
|||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3"
|
||||
regex = "0.2"
|
||||
|
|
|
|||
|
|
@ -28,3 +28,4 @@ pub mod symbols;
|
|||
pub mod schema;
|
||||
pub mod repository;
|
||||
pub mod resources;
|
||||
pub mod storage;
|
||||
|
|
|
|||
43
src/storage.rs
Normal file
43
src/storage.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use std::error::Error;
|
||||
use std::fs::read_dir;
|
||||
use std::str::FromStr;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
pub trait Storage {
|
||||
fn write_filename(&self) -> String;
|
||||
fn read_filename(&self) -> Result<String, Box<Error>>;
|
||||
fn recent_date(&self) -> Result<u64, Box<Error>>;
|
||||
}
|
||||
|
||||
pub struct SimpleStorage(String, String);
|
||||
|
||||
impl SimpleStorage {
|
||||
pub fn new(base: String, filename: String) -> Self {
|
||||
SimpleStorage(base, filename)
|
||||
}
|
||||
|
||||
fn get_path(&self, date: Option<u64>) -> String {
|
||||
match date {
|
||||
Some(d) => format!("{}/_{}/{}", self.0, self.1, d),
|
||||
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()))
|
||||
}
|
||||
|
||||
fn read_filename(&self) -> Result<String, Box<Error>> {
|
||||
Ok(self.get_path(Some(try!(self.recent_date()))))
|
||||
}
|
||||
|
||||
fn recent_date(&self) -> Result<u64, Box<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("Not found".to_string().into())
|
||||
}
|
||||
}
|
||||
136
src/symbols/if_already_present.rs
Normal file
136
src/symbols/if_already_present.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use resources::Resource;
|
||||
use symbols::Symbol;
|
||||
|
||||
pub struct IfAlreadyPresent<A, B> where A: Symbol, B: Symbol {
|
||||
a: A,
|
||||
b: B
|
||||
}
|
||||
|
||||
impl<A, B> IfAlreadyPresent<A, B> where A: Symbol, B: Symbol {
|
||||
pub fn new(a: A, b: B) -> Self {
|
||||
IfAlreadyPresent { a: a, b: b }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> Symbol for IfAlreadyPresent<A, B> where A: Symbol, B: Symbol {
|
||||
fn target_reached(&self) -> Result<bool, Box<Error>> {
|
||||
self.a.target_reached().and_then(|reached| if reached { self.b.target_reached() } else { Ok(reached) })
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<Error>> {
|
||||
// Execute a & b if a is not reached
|
||||
// Execute b if a was reached and b isn't
|
||||
if !try!(self.a.target_reached()) {
|
||||
try!(self.a.execute());
|
||||
self.b.execute()
|
||||
} else if !try!(self.b.target_reached()) {
|
||||
self.b.execute()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_prerequisites(&self) -> Vec<Resource> {
|
||||
let mut r = vec![];
|
||||
r.extend(self.a.get_prerequisites().into_iter());
|
||||
r.extend(self.b.get_prerequisites().into_iter());
|
||||
r
|
||||
}
|
||||
|
||||
fn provides(&self) -> Option<Vec<Resource>> {
|
||||
let mut r = vec![];
|
||||
if let Some(provides) = self.a.provides() {
|
||||
r.extend(provides.into_iter());
|
||||
}
|
||||
if let Some(provides) = self.b.provides() {
|
||||
r.extend(provides.into_iter());
|
||||
}
|
||||
if r.len() > 0 { Some(r) } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> fmt::Display for IfAlreadyPresent<A, B> where A: Symbol, B: Symbol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
|
||||
write!(f, "IfAlreadyPresent {} and then {}", self.a, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use symbols::Symbol;
|
||||
use symbols::if_already_present::IfAlreadyPresent;
|
||||
|
||||
struct ErrSymbol(String);
|
||||
impl Symbol for ErrSymbol {
|
||||
fn target_reached(&self) -> Result<bool, Box<Error>> { Err(self.0.clone().into()) }
|
||||
fn execute(&self) -> Result<(), Box<Error>> { Err(self.0.clone().into()) }
|
||||
}
|
||||
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<Error>> { Ok(self.0) }
|
||||
fn execute(&self) -> Result<(), Box<Error>> { Ok(()) }
|
||||
}
|
||||
impl fmt::Display for OkSymbol { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ write!(f, "") } }
|
||||
|
||||
#[test]
|
||||
fn first_target_reached_fails() {
|
||||
let res = IfAlreadyPresent::new(ErrSymbol("first".into()), ErrSymbol("second".into())).target_reached();
|
||||
assert_eq!(res.unwrap_err().description(), "first");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_target_not_reached() {
|
||||
let res = IfAlreadyPresent::new(OkSymbol(false), ErrSymbol("second".into())).target_reached();
|
||||
assert_eq!(res.unwrap(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_target_reached_fails() {
|
||||
let res = IfAlreadyPresent::new(OkSymbol(true), ErrSymbol("second".into())).target_reached();
|
||||
assert_eq!(res.unwrap_err().description(), "second");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_target_not_reached() {
|
||||
let res = IfAlreadyPresent::new(OkSymbol(true), OkSymbol(false)).target_reached();
|
||||
assert_eq!(res.unwrap(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn everything_reached() {
|
||||
let res = IfAlreadyPresent::new(OkSymbol(true), OkSymbol(true)).target_reached();
|
||||
assert_eq!(res.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_execute_fails() {
|
||||
let res = IfAlreadyPresent::new(ErrSymbol("first".into()), ErrSymbol("second".into())).execute();
|
||||
assert_eq!(res.unwrap_err().description(), "first");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_execute_fails_but_isnt_run() {
|
||||
let res = IfAlreadyPresent::new(OkSymbol(false), ErrSymbol("second".into())).execute();
|
||||
assert_eq!(res.unwrap(), ());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_execute_fails() {
|
||||
let res = IfAlreadyPresent::new(OkSymbol(true), ErrSymbol("second".into())).execute();
|
||||
assert_eq!(res.unwrap_err().description(), "second");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn everything_executes() {
|
||||
let res = IfAlreadyPresent::new(OkSymbol(true), OkSymbol(true)).execute();
|
||||
assert_eq!(res.unwrap(), ());
|
||||
}
|
||||
}
|
||||
|
|
@ -7,15 +7,15 @@ use symbols::Symbol;
|
|||
|
||||
pub struct MariaDBDatabase<'a> {
|
||||
db_name: Cow<'a, str>,
|
||||
seed_file: &'a str,
|
||||
seed_file: Cow<'a, str>,
|
||||
command_runner: &'a CommandRunner
|
||||
}
|
||||
|
||||
impl<'a> MariaDBDatabase<'a> {
|
||||
pub fn new(db_name: Cow<'a, str>, command_runner: &'a CommandRunner) -> MariaDBDatabase<'a> {
|
||||
pub fn new(db_name: Cow<'a, str>, seed_file: Cow<'a, str>, command_runner: &'a CommandRunner) -> MariaDBDatabase<'a> {
|
||||
MariaDBDatabase {
|
||||
db_name: db_name,
|
||||
seed_file: "/root/seedfile.sql",
|
||||
seed_file: seed_file,
|
||||
command_runner: command_runner
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
src/symbols/mariadb/database_dump.rs
Normal file
55
src/symbols/mariadb/database_dump.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use command_runner::CommandRunner;
|
||||
use symbols::Symbol;
|
||||
use storage::Storage;
|
||||
|
||||
pub struct DatabaseDump<'a, S> where S: Storage {
|
||||
db_name: Cow<'a, str>,
|
||||
storage: S,
|
||||
command_runner: &'a CommandRunner
|
||||
}
|
||||
|
||||
impl<'a, S> DatabaseDump<'a, S> where S: Storage {
|
||||
pub fn new(db_name: Cow<'a, str>, storage: S, command_runner: &'a CommandRunner) -> Self {
|
||||
DatabaseDump {
|
||||
db_name: db_name,
|
||||
storage: storage,
|
||||
command_runner: command_runner
|
||||
}
|
||||
}
|
||||
|
||||
fn run_sql(&self, sql: &str) -> Result<String, Box<Error>> {
|
||||
let output = try!(self.command_runner.run_with_args("mariadb", &["--skip-column-names", "-B", "-e", sql]));
|
||||
if output.status.code() != Some(0) {
|
||||
return Err(try!(String::from_utf8(output.stderr)).into());
|
||||
}
|
||||
Ok(try!(String::from_utf8(output.stdout)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S> fmt::Display for DatabaseDump<'a, S> where S: Storage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "MariaDB Database {}", self.db_name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S> Symbol for DatabaseDump<'a, S> where S: Storage {
|
||||
fn target_reached(&self) -> Result<bool, Box<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)));
|
||||
Ok(try!(u64::from_str(modified_date.trim_right())) <= dump_date)
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), Box<Error>> {
|
||||
try!(self.command_runner.run_with_args("sh", &["-c", &format!("mysqldump '{}' > {}", self.db_name, self.storage.write_filename())]));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
mod database;
|
||||
mod user;
|
||||
mod database_dump;
|
||||
|
||||
pub use self::database::MariaDBDatabase;
|
||||
pub use self::database_dump::DatabaseDump;
|
||||
pub use self::user::MariaDBUser;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ pub mod dir;
|
|||
pub mod file;
|
||||
pub mod git;
|
||||
pub mod hook;
|
||||
pub mod if_already_present;
|
||||
pub mod list;
|
||||
pub mod mariadb;
|
||||
pub mod nginx;
|
||||
|
|
|
|||
104
tests/storage.rs
Normal file
104
tests/storage.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
extern crate regex;
|
||||
extern crate schematics;
|
||||
extern crate tempdir;
|
||||
|
||||
use regex::Regex;
|
||||
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 {
|
||||
let tmp_dir = TempDir::new("unittest").expect("create temp dir");
|
||||
let storage_path = tmp_dir.path().join("_filename");
|
||||
create_dir(storage_path.clone()).unwrap();
|
||||
for path in content {
|
||||
let file_path = storage_path.join(path);
|
||||
File::create(file_path).expect("create temp file");
|
||||
}
|
||||
tmp_dir
|
||||
}
|
||||
|
||||
fn get_storage(path: &Path) -> SimpleStorage {
|
||||
SimpleStorage::new(path.to_str().unwrap().into(), "filename".into())
|
||||
}
|
||||
|
||||
// Normal cases
|
||||
|
||||
#[test]
|
||||
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_eq!(storage.recent_date().unwrap(), 12345);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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_eq!(storage.recent_date().unwrap(), 23456);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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_eq!(storage.recent_date().unwrap(), 23456);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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_eq!(storage.recent_date().unwrap(), 23456);
|
||||
}
|
||||
|
||||
// Bad cases
|
||||
|
||||
#[test]
|
||||
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!(storage.read_filename().is_err());
|
||||
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");
|
||||
}
|
||||
|
||||
#[test]
|
||||
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!(storage.read_filename().is_err());
|
||||
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");
|
||||
}
|
||||
|
||||
#[test]
|
||||
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!(storage.read_filename().is_err());
|
||||
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");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue