trappo 0.1.0

Fast and convenient remote server automation and deployment tool
Documentation
pub mod deployer;

use steps::{Step, core::*, git::*, rollback::*};
use config::steps::{StepConfig, StepPosition::*};
use std::cell::RefCell;

pub struct Recipe {
    pub name : String,
    pub steps: Vec<Box<Step>>,
    pub rollback_steps: Vec<Box<Step>>
}

impl Recipe {

    pub fn build() -> RecipeBuilder {
        RecipeBuilder {
            recipe: Some(RefCell::new(Recipe::default()))
        }
    }

    fn get_step_index(&self, step_name: &str) -> Option<usize> {
        let mut node_index: Option<usize> = None;

        let mut index: usize = 0;
        for step in &self.steps {
            if step.get_name() == step_name {
                node_index = Some(index);
                break;
            }
            index += 1;
        }

        node_index
    }
}

impl Default for Recipe {
    fn default() -> Recipe {
        Recipe {
            name: "Anonymous Recipe".into(),
            steps: Vec::new(),
            rollback_steps: Vec::new()
        }
    }
}

pub struct RecipeBuilder {
    recipe: Option<RefCell<Recipe>>
}

impl RecipeBuilder {
    ///Set recipe name
    pub fn name(&mut self, name: &str) -> &mut Self {
        if let Some(ref mut recipe) = self.recipe {
            recipe.borrow_mut().name = name.into()
        }

        self
    }

    ///Set core steps
    pub fn with_core_steps(&mut self) -> &mut Self {
        if let Some(ref mut recipe) = self.recipe {
            let mut recipe_ref = recipe.borrow_mut();
            recipe_ref.steps.push(Box::new(InitStep));
            recipe_ref.steps.push(Box::new(GitClone));
            recipe_ref.steps.push(Box::new(LinkFiles));
            recipe_ref.steps.push(Box::new(LinkDirs));
            recipe_ref.steps.push(Box::new(SymlinkCurrent));
            recipe_ref.steps.push(Box::new(CleanUpReleases));
        }

        self
    }

    /// Adds default rollback steps to the recipe.
    /// These steps perform the basic rollback operation which should be enough for most deployments.
    /// It falls back to the previous release and cleans up any files which were generated by current release if any.
    pub fn with_core_rollback_steps(&mut self) -> &mut Self {
        if let Some(ref mut recipe) = self.recipe {
            let mut recipe_ref = recipe.borrow_mut();
            recipe_ref.rollback_steps.push(Box::new(CanRollBack));
            recipe_ref.rollback_steps.push(Box::new(RemoveCurrentRelease));
            recipe_ref.rollback_steps.push(Box::new(SymlinkPreviousRelease));
        }

        self
    }

    /// Adds steps to the recipe in arbitrary positions using a vector of `StepConfig` structs.
    /// `StepConfig` structs are loaded from the `steps.toml` configuration file.
    ///
    ///panics if reference steps are not found in the inner vector
    pub fn with_config_steps(&mut self, config_steps: Vec<StepConfig>) -> &mut Self {

        if let Some(ref mut recipe) = self.recipe {
            for step_config in config_steps.into_iter() {
                let mut step_index = recipe.borrow().get_step_index(&step_config.ref_step)
                    .expect(&format!("Step '{}' doesn't exist in the recipe", &step_config.ref_step));

                if let After = step_config.position { step_index += 1 };
                recipe.borrow_mut().steps.insert(step_index, Box::new(RawCmdStep::from(step_config)));
            };
        }
        self
    }

    ///Add step after another
    ///
    ///panics if step is not found in the inner vector
    pub fn with_step_after<T: 'static +  Step>(&mut self, subject_name: &str, extra_step: T) -> &mut Self {
        if let Some(ref mut recipe) = self.recipe {
            let step_index = recipe.borrow().get_step_index(subject_name)
                .expect(&format!("Step '{}' doesn't exist in the recipe", subject_name));

            recipe.borrow_mut().steps.insert(step_index + 1, Box::new(extra_step));
        }

        self
    }

    ///Add step before another
    ///
    ///panics if step is not found in the inner vector
    pub fn with_step_before<T: 'static +  Step>(&mut self, subject_name: &str, extra_step: T) -> &mut Self {
        if let Some(ref mut recipe) = self.recipe {
            let step_index = recipe.borrow().get_step_index(subject_name)
                .expect(&format!("Step '{}' doesn't exist in the recipe", subject_name));

            recipe.borrow_mut().steps.insert(step_index, Box::new(extra_step));
        }

        self
    }

    ///Consumes the recipe and returns it, leaving None as the value of the internal recipe property
    ///
    ///panics if recipe is attempted to be build multiple times.
    pub fn finish(&mut self) -> Recipe {
        let recipe = self.recipe.take().expect("Cannot reuse recipe builder");
        recipe.into_inner()
    }
}