Then I've decided to contribute some code which hopefully can be useful.
It basically aggregates .yaml files and resolves references.
The idea is simple: render a .yaml file in a loop until the result is equal to the input, meaning that all references are resolved.
#![allow(unused_parens)]
use anyhow::{Context,Result, anyhow};
use clap::{arg, App, AppSettings};
use std::ffi::OsStr;
fn main() -> Result<()> {
let matches = App::new("mkvm")
.about("Make virtual machines easily!")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
App::new("vm")
.about("start virtual machine(s)")
.setting(AppSettings::ArgRequiredElseHelp)
.arg(arg!(names: <NAME> ... "Virtual machines to start").allow_invalid_utf8(true))
.arg(
arg!(files: [YAML])
.multiple_occurrences(true)
.allow_invalid_utf8(true)
.last(true),
),
)
.get_matches();
let args: Args = validate(&matches, "vm")?;
//XXX println!("Names: {:?}", args.names);
//XXX println!("Files: {:?}", args.files);
let yaml: String = args.render()?;
println!("{}", yaml);
Ok(())
}
struct Args<'a> {
names: Vec<&'a OsStr>,
files: Vec<&'a OsStr>,
}
fn validate<'a>(matches: &'a clap::ArgMatches, _subcommand: &str) -> Result<Args<'a>> {
let mut stdin_seen = false;
let (names, files) = match matches.subcommand() {
Some((_subcommand, sub_matches)) => {
let names: Vec<&'a OsStr> = sub_matches
.values_of_os("names").context("names of virtual machines is requered")?
.collect::<Vec<_>>();
let files: Vec<&'a OsStr> = sub_matches
.values_of_os("files").context("a list of file names is requered")?
.map(|path| if(path == "-" && stdin_seen) { Err(anyhow!("stdin specified multiple times")) } else { stdin_seen = true; Ok(path) })
.map(|path| path.unwrap())
.collect::<Vec<_>>();
(names, files)
}
_ => unreachable!(),
};
Ok(Args{ names, files, })
}
/////////////////////////////////////////////////////////////////////////////////////////////////
#![recursion_limit = "256"]
use handlebars::Handlebars;
use serde_yaml::Value;
trait Render {
fn render(&self) -> Result<String>;
}
impl Render for String {
fn render(&self) -> Result<String> {
let mut tmpl: String = self.clone();
let mut data: Value = serde_yaml::from_str(&tmpl)?;
let handlebars = Handlebars::new();
let mut count = 0;
loop {
let rendered = handlebars.render_template(&tmpl, &data).unwrap();
let exit = rendered == tmpl;
tmpl = rendered;
data = serde_yaml::from_str(&tmpl)?;
if exit { break; }
count = count +1;
if (count > 10) { break; } // some extra care to avoid infinite loop.
}
Ok(tmpl)
}
}
impl Render for Vec<&OsStr> {
fn render(&self) -> Result<String> {
Ok((*self)
.iter()
.map(|path| path.reader().unwrap())
.fold(String::new(), |mut acc, item| { acc.push_str("\n"); acc.push_str(&item); acc } )
.render()?)
}
}
impl<'a> Render for Args<'a> {
fn render(&self) -> Result<String> {
Ok((*self).files.render()?)
}
}
$ cargo run -- vm nginx mysql -- examples/simple/*.yaml
examples/simple/controller.yaml:
controller:
hostname: mars
domain: example.com
email: me@example.com
pubkey: "~/.ssh/id_ed25519_{{controller.hostname}}.{{controller.domain}}"
Hi! Thanks for your time! I am indeed going to take a look on adding a proper config in viska soon, and you code will be very useful even as inspiration.
Hi! I saw this item in your notes:
Then I've decided to contribute some code which hopefully can be useful. It basically aggregates .yaml files and resolves references. The idea is simple: render a .yaml file in a loop until the result is equal to the input, meaning that all references are resolved.
examples/simple/controller.yaml:
examples/simple/hypervisor.yaml:
It renders to:
I'm looking forward to give Viska a try soon! Thanks a lot :-)