diff --git a/.gitignore b/.gitignore index ea8c4bf..4d461a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/demo/out diff --git a/demo/spec/xyz/common/ex.yaml b/demo/spec/xyz/common/ex.yaml index 212095c..7ac8d5c 100644 --- a/demo/spec/xyz/common/ex.yaml +++ b/demo/spec/xyz/common/ex.yaml @@ -1,2 +1,3 @@ regional: - partition: us + partition: us + username: netadmin diff --git a/demo/spec/xyz/ex-edge-r1/common.yaml b/demo/spec/xyz/ex-edge-r1/common.yaml index dd23900..3f6bdb9 100644 --- a/demo/spec/xyz/ex-edge-r1/common.yaml +++ b/demo/spec/xyz/ex-edge-r1/common.yaml @@ -1,2 +1,4 @@ common: - layer: ex-edge-r + layer: ex-edge-r + protocol: bgp + uplink: et-0/0/0 diff --git a/demo/spec/xyz/ex-edge-r1/xyz1.common.yaml b/demo/spec/xyz/ex-edge-r1/xyz1.common.yaml index 043a24c..ba57437 100644 --- a/demo/spec/xyz/ex-edge-r1/xyz1.common.yaml +++ b/demo/spec/xyz/ex-edge-r1/xyz1.common.yaml @@ -1,2 +1,2 @@ zonal: - az: 1 + zone: 1 diff --git a/demo/spec/xyz/ex-edge-r1/xyz2.common.yaml b/demo/spec/xyz/ex-edge-r1/xyz2.common.yaml index 9770d50..b57026e 100644 --- a/demo/spec/xyz/ex-edge-r1/xyz2.common.yaml +++ b/demo/spec/xyz/ex-edge-r1/xyz2.common.yaml @@ -1,2 +1,2 @@ zonal: - az: 2 + zone: 2 diff --git a/demo/spec/xyz/ex-edge-r2/common.yaml b/demo/spec/xyz/ex-edge-r2/common.yaml index 71770a4..f87e6c8 100644 --- a/demo/spec/xyz/ex-edge-r2/common.yaml +++ b/demo/spec/xyz/ex-edge-r2/common.yaml @@ -1,3 +1,5 @@ common: - partition: eu - layer: ex-edge-r + partition: eu + layer: ex-edge-r + protocol: ospf + uplink: et-0/0/36 diff --git a/demo/tmpl/common/ex/system.tmpl b/demo/tmpl/common/ex/system.tmpl index 72148e5..5488bd5 100644 --- a/demo/tmpl/common/ex/system.tmpl +++ b/demo/tmpl/common/ex/system.tmpl @@ -1,4 +1,4 @@ system { - hostname {{ hostname }}; - location "{{ location }}"; + hostname {{ hostname }}; + location "{{ location }}"; } diff --git a/demo/tmpl/ex-edge-r/chassis.tmpl b/demo/tmpl/ex-edge-r/chassis.tmpl index deeb9e9..b016cc1 100644 --- a/demo/tmpl/ex-edge-r/chassis.tmpl +++ b/demo/tmpl/ex-edge-r/chassis.tmpl @@ -1,2 +1,5 @@ chassis { + users { + {{ username }}; + } } diff --git a/demo/tmpl/ex-edge-r/interfaces.tmpl b/demo/tmpl/ex-edge-r/interfaces.tmpl index 31f4c74..aab7ebd 100644 --- a/demo/tmpl/ex-edge-r/interfaces.tmpl +++ b/demo/tmpl/ex-edge-r/interfaces.tmpl @@ -1,2 +1,4 @@ interfaces { + {{ uplink }} { + } } diff --git a/demo/tmpl/ex-edge-r/protocols.tmpl b/demo/tmpl/ex-edge-r/protocols.tmpl index 162b1d4..e6aeae8 100644 --- a/demo/tmpl/ex-edge-r/protocols.tmpl +++ b/demo/tmpl/ex-edge-r/protocols.tmpl @@ -1,2 +1,4 @@ protocols { + {{ protocol }} { + } } diff --git a/src/log.rs b/src/log.rs index 439d545..bb6d24e 100644 --- a/src/log.rs +++ b/src/log.rs @@ -51,10 +51,7 @@ macro_rules! verb { macro_rules! dbug { ($current_level:expr, $($msg:expr),*) => { if LogLevel::Debug.value() >= $current_level.value() { - let message = format!($($msg),*); - for line in message.lines() { - println!(" |D| {}", line); - } + println!($($msg),*); } }; } diff --git a/src/main.rs b/src/main.rs index d7c35ca..9477a92 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,11 @@ mod specs; mod tmpls; use log::LogLevel; +use serde_yml; +use std::fs::{self, create_dir_all, write, OpenOptions}; +use std::io::Write; +use std::path::Path; +use tmpls::RenderedConfig; fn main() { let args: cli::Args = cli::parse_args(); @@ -19,13 +24,42 @@ fn main() { verb!( dbg, "Compiled Spec:\n{}", - serde_json::to_string_pretty(&spec.compiled).unwrap() + serde_json::to_string_pretty(&spec.compiled["data"]).unwrap() ); - info!(dbg, "Rendered Config:"); - for line in result { - if line != "\n" { - print!("{}", line) - } - } + output_rendered_configs(result, dbg) } } + +fn output_rendered_configs(rendered_config: RenderedConfig, dbg: LogLevel) { + info!(dbg, "Writing Output"); + let path: String = format!("out/{}", rendered_config.hostname); + if Path::new(&path).exists() { + fs::remove_dir_all(&path).ok(); + } + create_dir_all(&path).ok(); + let rendered_config_path = format!("{}/all.conf", path); + for rendered_template in rendered_config.configs { + let outpath = format!("{}/{}.tmpl", path, rendered_template.0); + verb!(dbg, " | {}", &outpath); + write(&outpath, &rendered_template.1).ok(); + append_to_file(&rendered_config_path, rendered_template.1).ok(); + } + let outpath = format!("{}/compiled.spec", path); + let spec: String = match serde_yml::to_string(&rendered_config.spec) { + Ok(yaml) => yaml, + Err(e) => { + eprintln!("Failed to convert to YAML: {}", e); + String::new() + } + }; + verb!(dbg, " | {}", &outpath); + write(&outpath, spec).ok(); + info!(dbg, " | {} ", &rendered_config_path); +} + +fn append_to_file(path: &str, content: String) -> std::io::Result<()> { + let mut file = OpenOptions::new().create(true).append(true).open(path)?; + + write!(file, "{}", content)?; + Ok(()) +} diff --git a/src/specs.rs b/src/specs.rs index 2da75c9..67415ef 100644 --- a/src/specs.rs +++ b/src/specs.rs @@ -1,4 +1,4 @@ -use crate::{info, verb, LogLevel}; +use crate::{dbug, info, verb, LogLevel}; use regex::Regex; use serde_json::{json, Map, Value}; use serde_yml; @@ -121,7 +121,7 @@ pub fn compile(pattern: &Regex, spec_path: &String, dbg: LogLevel) -> Vec { - verb!( + dbug!( dbg, "Compiled Spec for '{}'\n | {}\n | {}\n | {}\n | {}\n | {}", compiled_spec.get_hostname(), diff --git a/src/tmpls.rs b/src/tmpls.rs index 6976d26..6d35844 100644 --- a/src/tmpls.rs +++ b/src/tmpls.rs @@ -1,5 +1,5 @@ use crate::specs::Specification; -use crate::{verb, LogLevel}; +use crate::{info, verb, LogLevel}; use serde_json::Value; use serde_yml; use std::fs; @@ -10,10 +10,16 @@ struct TemplateConfig { files: Vec, } +pub struct RenderedConfig { + pub hostname: String, + pub configs: Vec<(String, String)>, + pub spec: Value, +} + pub fn process_templates( spec: &Specification, dbg: LogLevel, -) -> Result, Box> { +) -> Result> { // Create Tera context from spec.compiled let mut context = Context::new(); if let Value::Object(map) = &spec.compiled { @@ -24,6 +30,11 @@ pub fn process_templates( } } + let device_name = match context.get("hostname") { + Some(value) => value.as_str().unwrap_or("default_hostname"), + None => "default_hostname", + }; + // Get the base directory path let base_dir = format!("./tmpl/{}", spec.get_layer()); @@ -36,8 +47,8 @@ pub fn process_templates( let mut tera = Tera::default(); // Process each template - verb!(dbg, "Rendering Templates"); - let mut rendered_templates = Vec::new(); + info!(dbg, "Rendering {}", &device_name); + let mut configs = Vec::new(); for template_name in config.files { // Read the template file directly let template_path = format!("{}/{}.tmpl", base_dir, template_name); @@ -49,8 +60,12 @@ pub fn process_templates( // Render the template let rendered = tera.render(&template_name, &context)?; - rendered_templates.push(rendered); + configs.push((String::from(&template_name), rendered)); } - Ok(rendered_templates) + Ok(RenderedConfig { + hostname: String::from(device_name), + configs, + spec: spec.compiled.clone(), + }) }