orcs 0.0.8

Microservices monorepo orchestration tool
Documentation
use crate::{Error, Project, Stage, Template};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::{create_dir_all, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tracing::{debug, instrument};

pub const RECIPE_FOLDER: &str = "rcp";
pub const RECIPE_CFG_TEMPLATE: &str = "[stages.build]
actions = [
  \"echo Hello from {{{ recipe_name }}}\"
]

[stages.deploy]
actions = [
  \"echo Hello from {{{ recipe_name }}}\"
]
";

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Recipe {
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub stages: HashMap<String, Stage>,
}

impl Recipe {
    #[instrument(skip(project))]
    pub fn create(project: Arc<Project>, name: &str) -> Result<(), Error> {
        debug!("Create new recipe {}", name);
        if name.contains("..") {
            return Err(Error::CreateRecipeError(
                "illegal sequence in recipe name",
                name.to_string(),
            ));
        }

        // TODO: Add support for templates (git or local)

        let recipe_folder = Path::new(&project.path).join(RECIPE_FOLDER);

        // Create the recipe folder if it doesn't exist
        create_dir_all(&recipe_folder)?;

        // Create template
        let mut template_map = HashMap::new();
        template_map.insert(format!("{}.toml", name), RECIPE_CFG_TEMPLATE.to_string());
        let template = Template::from_map(template_map);

        // Render template
        let mut template_data = HashMap::new();
        template_data.insert("recipe_name", name);

        template.render(recipe_folder.as_path(), &template_data)?;

        // Done
        Ok(())
    }

    #[instrument(skip(project))]
    pub fn from_name(project: Arc<Project>, name: &str) -> Result<Arc<Self>, Error> {
        // Check if the service exists in the global store
        {
            if let Some(recipe) = project.recipes.read().unwrap().get(name) {
                return Ok(recipe.clone());
            }
        }

        debug!("Load recipe from name {}", name);

        // Store recipe in project cache
        let recipe = Arc::new(Self::from_path(get_recipe_path(&project.path, name))?);
        project
            .recipes
            .write()
            .unwrap()
            .insert(name.to_string(), recipe.clone());
        Ok(recipe)
    }

    fn from_path(path: PathBuf) -> Result<Self, Error> {
        let mut file = File::open(path)?;
        let mut data = String::new();
        file.read_to_string(&mut data)?;

        Self::from_toml(&data)
    }

    fn from_toml(data: &str) -> Result<Self, Error> {
        let service = toml::from_str(data)?;
        Ok(service)
    }
}

fn get_recipe_path<'a>(project_path: &'a str, name: &str) -> PathBuf {
    Path::new(project_path)
        .join(RECIPE_FOLDER)
        .join(format!("{}.toml", name))
}