A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

284 lines
7.9 KiB

use std::cell::RefCell;
use std::error::Error;
use std::fmt;
use loggers::Logger;
use repository::SymbolRepository;
use symbols::{Symbol, SymbolRunner};
#[derive(Debug)]
pub enum SymbolRunError {
Symbol(Box<Error>),
ExecuteDidNotReach(())
}
impl Error for SymbolRunError {
fn description(&self) -> &str {
match self {
&SymbolRunError::Symbol(_) => "Symbol execution error",
&SymbolRunError::ExecuteDidNotReach(_) => "Target not reached after executing symbol"
}
}
fn cause(&self) -> Option<&Error> {
match self {
&SymbolRunError::Symbol(ref e) => Some(&**e),
&SymbolRunError::ExecuteDidNotReach(_) => None
}
}
}
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())
}
}
}
pub struct InitializingSymbolRunner<L: Logger> {
logger: RefCell<L>
}
impl<L: Logger> InitializingSymbolRunner<L> {
pub fn new(logger: L) -> Self {
Self { logger: RefCell::new(logger) }
}
}
impl<L: Logger> SymbolRunner for InitializingSymbolRunner<L> {
fn run_symbol(&self, symbol: &Symbol) -> Result<(), Box<Error>>
{
let mut logger = self.logger.borrow_mut();
let target_reached = try!(symbol.target_reached());
if target_reached {
logger.write(format!("{} already reached", symbol).as_str());
} else {
logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str());
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());
if !target_reached {
return Err(Box::new(SymbolRunError::ExecuteDidNotReach(())));
}
}
Ok(())
}
}
pub struct DrySymbolRunner<L: Logger> {
logger: RefCell<L>
}
impl<L: Logger> DrySymbolRunner<L> {
pub fn new(logger: L) -> Self {
Self { logger: RefCell::new(logger) }
}
}
impl<L: Logger> SymbolRunner for DrySymbolRunner<L> {
fn run_symbol(&self, symbol: &Symbol) -> Result<(), Box<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());
if !target_reached {
logger.write(format!("Would execute {}", symbol).as_str());
}
Ok(())
}
}
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 {
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: &Symbol) -> Result<(), Box<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());
}
}
res
}
}
use std::collections::HashSet;
use resources::Resource;
pub struct NonRepeatingSymbolRunner<'a, R> where R: 'a + SymbolRunner {
upstream: &'a R,
done: RefCell<HashSet<Resource>>
}
impl<'a, R> NonRepeatingSymbolRunner<'a, R> where R: SymbolRunner {
pub fn new(symbol_runner: &'a R) -> Self {
NonRepeatingSymbolRunner{ upstream: symbol_runner, done: RefCell::new(HashSet::new()) }
}
}
impl<'a, R> SymbolRunner for NonRepeatingSymbolRunner<'a, R> where R: SymbolRunner {
fn run_symbol(&self, symbol: &Symbol) -> Result<(), Box<Error>>
{
if let Some(resources) = symbol.provides() {
let mut done = self.done.borrow_mut();
let mut has_to_run = false;
for resource in resources {
if !done.contains(&resource) {
has_to_run = true;
done.insert(resource.clone());
}
}
if !has_to_run {
return Ok(());
}
}
self.upstream.run_symbol(&*symbol)
}
}
use std::marker::PhantomData;
pub struct RequirementsResolvingSymbolRunner<'a, 's, R: 'a + SymbolRunner, G: 'a + SymbolRepository<'s>>(&'a R, &'a G, PhantomData<Box<Symbol + 's>>);
impl<'a, 's, R, G> RequirementsResolvingSymbolRunner<'a, 's, R, G> where R: SymbolRunner, G: SymbolRepository<'s> {
pub fn new(symbol_runner: &'a R, symbol_repo: &'a G) -> Self {
RequirementsResolvingSymbolRunner(symbol_runner, symbol_repo, PhantomData)
}
}
impl<'a, 's, R, G> SymbolRunner for RequirementsResolvingSymbolRunner<'a, 's, R, G> where R: SymbolRunner, G: SymbolRepository<'s> {
fn run_symbol(&self, symbol: &Symbol) -> Result<(), Box<Error>>
{
for resource in symbol.get_prerequisites() {
if let Some(dep) = self.1.get_symbol(&resource) {
try!(dep.as_action(self).run());
}
}
self.0.run_symbol(&*symbol)
}
}
// FIXME: Add ExpectingSymbolRunner
#[cfg(test)]
mod test {
use std::cell::RefCell;
use std::error::Error;
use std::fmt;
use loggers::Logger;
use symbols::Symbol;
use schema::SymbolRunner;
use schema::InitializingSymbolRunner;
#[derive(Debug, PartialEq, Clone)]
enum DummySymbolError {
Error(())
}
impl Error for DummySymbolError {
fn description(&self) -> &str {
return "Description";
}
}
impl fmt::Display for DummySymbolError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Dummy symbol error")
}
}
struct DummySymbol<'a> {
_execute: &'a Fn() -> Result<(), Box<Error>>,
_target_reached: &'a Fn() -> Result<bool, Box<Error>>
}
impl<'a> Symbol for DummySymbol<'a> {
fn target_reached(&self) -> Result<bool, Box<Error>> {
(self._target_reached)()
}
fn execute(&self) -> Result<(), Box<Error>> {
(self._execute)()
}
}
impl<'a> fmt::Display for DummySymbol<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Dummy symbol")
}
}
impl<'a> DummySymbol<'a> {
fn new(target_reached: &'a Fn() -> Result<bool, Box<Error>>, execute: &'a Fn() -> Result<(), Box<Error>>) -> DummySymbol<'a> {
DummySymbol { _target_reached: target_reached, _execute: execute }
}
}
struct DummyLogger {
log: Vec<String>
}
impl DummyLogger {
fn new() -> DummyLogger {
DummyLogger { log: Vec::new() }
}
}
impl Logger for DummyLogger {
fn write(&mut self, line: &str) {
self.log.push(From::from(line));
}
fn debug(&mut self, line: &str) {
self.log.push(From::from(line));
}
}
#[test]
fn nothing_needed_to_be_done() {
let result = InitializingSymbolRunner.run_symbol( &mut DummyLogger::new(), &DummySymbol::new(&|| Ok(true), &|| Ok(())) );
assert!(result.is_ok());
}
#[test]
fn everything_is_ok() {
let first = RefCell::new(true);
let result = InitializingSymbolRunner.run_symbol( &mut DummyLogger::new(), &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.run_symbol( &mut DummyLogger::new(), &DummySymbol::new(&|| Ok(false), &|| Ok(())));
assert_eq!(
result.unwrap_err().description(),
"Target not reached after executing symbol"
);
}
#[test]
fn executing_did_not_work() {
let err = InitializingSymbolRunner.run_symbol( &mut DummyLogger::new(), &DummySymbol::new(&|| Ok(false), &|| Err(Box::new(DummySymbolError::Error(()))) )).unwrap_err();
assert_eq!(err.description(), "Description");
}
}