use crate::log::LogLevel; use crate::spec::Specification; use crate::tmpl; use crate::{crit, dbug, info, verb}; use std::error::Error; use std::io::Write; pub struct DeviceConfigBundle { pub hostname: String, pub configs: Vec, pub spec: Specification, pub platform: String, } pub struct ConfigVariant { pub name: String, pub configs: Vec, } pub struct Configuration { pub name: String, pub data: String, } impl DeviceConfigBundle { pub fn from_spec(spec: Specification) -> Result> { let mut context = Self::merge_context(&spec); let hostname = Self::get_hostname(&context, &spec); info!("Processing templates for '{hostname}'"); let structure_path = spec.tmplpath.join("structure.yaml"); let structure = tmpl::Structure::from_file(&structure_path) .map_err(|e| format!("Failed to parse {:?}: {e}", structure_path))?; let mut renderer = tera::Tera::default(); renderer.add_raw_templates(structure.load_template_data(&spec.tmplpath))?; dbug!("Rendering templates for {hostname}"); let mut failures = false; let mut configs = Vec::new(); for variant in &structure.variations { context.insert(variant, &true); let mut cfgs = Vec::new(); for template_name in &structure.files { dbug!(" | {template_name}.{variant}"); match renderer.render(template_name, &context) { Ok(data) => cfgs.push(Configuration::new(template_name, data)), Err(e) => { crit!("[!] {}", Error::source(&e).unwrap_or_else(|| &e)); renderer.add_raw_template(template_name, "")?; failures = true; } } } configs.push(ConfigVariant::new(variant, cfgs)); context.remove(variant); } match failures { true => Err(Box::::from("Rendering failed.")), false => Ok(Self { hostname, configs, spec, platform: structure.platform, }), } } fn get_hostname(context: &tera::Context, spec: &Specification) -> String { context .get("hostname") .and_then(|v| v.as_str()) .map(|s| s.to_string()) .unwrap_or_else(|| spec.components.get_hostname()) } fn merge_context(spec: &Specification) -> tera::Context { let mut context = tera::Context::new(); if let Some(data_map) = spec.compiled.get("data").and_then(|v| v.as_object()) { for (key, val) in data_map { context.insert(key, val); } } context } pub fn output_artifacts(self: Self) { info!("Writing Output:"); let device_outpath = std::path::Path::new(&self.spec.outpath).join(self.hostname); std::fs::create_dir_all(&device_outpath).ok(); for variant_configs in self.configs { let out_path = device_outpath.join(&variant_configs.name); std::fs::create_dir_all(&out_path).ok(); let merged_config_path = device_outpath.join(format!("all.{}.{}", variant_configs.name, self.platform)); let mut all_file: std::fs::File = std::fs::OpenOptions::new() .create(true) .append(true) .open(&merged_config_path) .expect("unable to open"); for config in &variant_configs.configs { let config_outpath = out_path.join(format!("{}.{}", config.name, self.platform)); verb!(" | {}", &config_outpath.display()); std::fs::write(&config_outpath, &config.data).ok(); all_file.write_all(&config.data.as_bytes()).ok(); } info!(" | {} ", &merged_config_path.display()); } let compiled_spec_path = device_outpath.join("context.yaml"); verb!(" | {}", &compiled_spec_path.display()); std::fs::write( &compiled_spec_path, yaml_serde::to_string(&self.spec.compiled).unwrap(), ) .ok(); } } impl ConfigVariant { pub fn new(name: &str, configs: Vec) -> Self { Self { name: String::from(name), configs, } } } impl Configuration { pub fn new(name: &str, data: String) -> Self { Self { name: String::from(name), data, } } }