Files
skyforge/src/render.rs

143 lines
4.6 KiB
Rust

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<ConfigVariant>,
pub spec: Specification,
pub platform: String,
}
pub struct ConfigVariant {
pub name: String,
pub configs: Vec<Configuration>,
}
pub struct Configuration {
pub name: String,
pub data: String,
}
impl DeviceConfigBundle {
pub fn from_spec(spec: Specification) -> Result<Self, Box<dyn std::error::Error>> {
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::<dyn Error>::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<Configuration>) -> Self {
Self {
name: String::from(name),
configs,
}
}
}
impl Configuration {
pub fn new(name: &str, data: String) -> Self {
Self {
name: String::from(name),
data,
}
}
}