Browse Source

Update

master
Adrian Heine 5 years ago
parent
commit
8882712925
  1. 1
      Cargo.toml
  2. 1
      src/lib.rs
  3. 43
      src/storage.rs
  4. 136
      src/symbols/if_already_present.rs
  5. 6
      src/symbols/mariadb/database.rs
  6. 55
      src/symbols/mariadb/database_dump.rs
  7. 2
      src/symbols/mariadb/mod.rs
  8. 1
      src/symbols/mod.rs
  9. 104
      tests/storage.rs

1
Cargo.toml

@ -8,3 +8,4 @@ users = "0.5.0"
[dev-dependencies]
tempdir = "0.3"
regex = "0.2"

1
src/lib.rs

@ -28,3 +28,4 @@ pub mod symbols;
pub mod schema;
pub mod repository;
pub mod resources;
pub mod storage;

43
src/storage.rs

@ -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

@ -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(), ());
}
}

6
src/symbols/mariadb/database.rs

@ -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

@ -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 {
}

2
src/symbols/mariadb/mod.rs

@ -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;

1
src/symbols/mod.rs

@ -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

@ -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…
Cancel
Save