All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
198 lines
6.4 KiB
Rust
198 lines
6.4 KiB
Rust
use std::{collections::HashMap, ops::Deref, path::Path};
|
|
|
|
use anyhow::Context;
|
|
use futures::{future::BoxFuture, FutureExt};
|
|
|
|
use crate::Component;
|
|
|
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
|
pub enum CuddleVariable {
|
|
Object(Box<CuddleVariables>),
|
|
Array(Vec<CuddleVariable>),
|
|
String(String),
|
|
}
|
|
|
|
impl TryFrom<serde_yaml::Value> for CuddleVariable {
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(value: serde_yaml::Value) -> Result<Self, Self::Error> {
|
|
match value {
|
|
serde_yaml::Value::Sequence(sequence) => {
|
|
let mut items = Vec::new();
|
|
|
|
for item in sequence {
|
|
items.push(item.try_into()?)
|
|
}
|
|
|
|
Ok(Self::Array(items))
|
|
}
|
|
serde_yaml::Value::Mapping(mapping) => {
|
|
let obj: CuddleVariables = mapping.try_into()?;
|
|
|
|
Ok(Self::Object(Box::new(obj)))
|
|
}
|
|
serde_yaml::Value::String(s) => Ok(Self::String(s)),
|
|
serde_yaml::Value::Number(num) => Ok(Self::String(num.to_string())),
|
|
serde_yaml::Value::Bool(bool) => Ok(Self::String(bool.to_string())),
|
|
serde_yaml::Value::Null => Ok(Self::Object(Box::default())),
|
|
_ => Err(anyhow::anyhow!("cannot handle type of serde value")),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Debug, Clone, Default)]
|
|
pub struct CuddleVariables(pub HashMap<String, CuddleVariable>);
|
|
|
|
impl TryFrom<serde_yaml::Mapping> for CuddleVariables {
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(value: serde_yaml::Mapping) -> Result<Self, Self::Error> {
|
|
let mut variables = CuddleVariables(HashMap::default());
|
|
|
|
for (k, v) in value {
|
|
let var: CuddleVariable = v.try_into()?;
|
|
|
|
variables.0.insert(
|
|
k.as_str()
|
|
.ok_or(anyhow::anyhow!("key cannot be anything else than a string"))?
|
|
.to_string(),
|
|
var,
|
|
);
|
|
}
|
|
|
|
Ok(variables)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<serde_yaml::Value> for CuddleVariables {
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(value: serde_yaml::Value) -> Result<Self, Self::Error> {
|
|
match value {
|
|
serde_yaml::Value::Null => Ok(Self::default()),
|
|
serde_yaml::Value::Bool(_) => anyhow::bail!("cannot handle bool"),
|
|
serde_yaml::Value::Number(_) => anyhow::bail!("cannot handle number"),
|
|
serde_yaml::Value::String(_) => anyhow::bail!("cannot handle string"),
|
|
serde_yaml::Value::Sequence(_) => anyhow::bail!("cannot handle sequence"),
|
|
serde_yaml::Value::Mapping(mapping) => mapping.try_into(),
|
|
serde_yaml::Value::Tagged(_) => anyhow::bail!("cannot handle tagged"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
|
pub struct CuddleVars {
|
|
pub variables: CuddleVariables,
|
|
}
|
|
|
|
const PARENT_PLAN_PREFIX: &str = ".cuddle/base";
|
|
const CUDDLE_FILE: &str = "cuddle.yaml";
|
|
|
|
impl CuddleVars {
|
|
pub async fn new(path: &Path) -> anyhow::Result<CuddleVars> {
|
|
let variables = load_cuddle_file(path).await?;
|
|
|
|
Ok(Self { variables })
|
|
}
|
|
}
|
|
|
|
pub fn load_cuddle_file(path: &Path) -> BoxFuture<'static, anyhow::Result<CuddleVariables>> {
|
|
let path = path.to_path_buf();
|
|
|
|
async move {
|
|
let cuddle_file = path.join(CUDDLE_FILE);
|
|
let cuddle_file_content = tokio::fs::read(cuddle_file)
|
|
.await
|
|
.context(format!("failed to read cuddle file: {}", path.display()))?;
|
|
let cuddle_serde: serde_yaml::Value = serde_yaml::from_slice(&cuddle_file_content)?;
|
|
match cuddle_serde.get("vars") {
|
|
Some(vars) => {
|
|
let parent_file = path.join(PARENT_PLAN_PREFIX);
|
|
if parent_file.exists() {
|
|
// FIXME: Merge parent map
|
|
//let parent_plan = load_cuddle_file(&parent_file).await?;
|
|
//Err(anyhow::anyhow!("not implemented yet"))
|
|
|
|
Ok(CuddleVariables::try_from(vars.clone())?)
|
|
} else {
|
|
Ok(CuddleVariables::try_from(vars.clone())?)
|
|
}
|
|
}
|
|
None => Err(anyhow::anyhow!(
|
|
"failed to find variables section in cuddle.yaml"
|
|
)),
|
|
}
|
|
}
|
|
.boxed()
|
|
}
|
|
|
|
impl Component for CuddleVars {
|
|
fn name(&self) -> String {
|
|
"cuddle/vars".into()
|
|
}
|
|
|
|
fn validate(&self, _value: &serde_yaml::Value) -> anyhow::Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
fn render_value(
|
|
&self,
|
|
environment: &str,
|
|
_value: &serde_yaml::Value,
|
|
) -> Option<anyhow::Result<minijinja::Value>> {
|
|
Some(Ok(minijinja::Value::from_object(self.variables.clone())))
|
|
}
|
|
}
|
|
|
|
impl minijinja::value::Object for CuddleVariables {
|
|
fn get_value(self: &std::sync::Arc<Self>, key: &minijinja::Value) -> Option<minijinja::Value> {
|
|
if let Some(key) = key.as_str() {
|
|
tracing::trace!("looking up key: {}", key);
|
|
|
|
if let Some(val) = self.0.get(key) {
|
|
match val {
|
|
CuddleVariable::Object(object) => {
|
|
let obj = object.deref().clone();
|
|
|
|
return Some(minijinja::Value::from_object(obj));
|
|
}
|
|
CuddleVariable::Array(arr) => {
|
|
return Some(minijinja::Value::from_object(MiniJinjaList(arr.clone())))
|
|
}
|
|
CuddleVariable::String(str) => {
|
|
return Some(minijinja::Value::from_safe_string(str.to_owned()))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct MiniJinjaList(Vec<CuddleVariable>);
|
|
|
|
impl minijinja::value::Object for MiniJinjaList {
|
|
fn enumerate(self: &std::sync::Arc<Self>) -> minijinja::value::Enumerator {
|
|
minijinja::value::Enumerator::Values(
|
|
self.0
|
|
.iter()
|
|
.map(|i| match i {
|
|
CuddleVariable::Object(object) => {
|
|
let obj = object.deref().clone();
|
|
|
|
minijinja::Value::from_object(obj)
|
|
}
|
|
CuddleVariable::Array(arr) => {
|
|
minijinja::Value::from_object(MiniJinjaList(arr.clone()))
|
|
}
|
|
CuddleVariable::String(str) => {
|
|
minijinja::Value::from_safe_string(str.to_owned())
|
|
}
|
|
})
|
|
.collect(),
|
|
)
|
|
}
|
|
}
|