Files
cuddle-clusters/crates/cuddle-clusters/src/catalog/cuddle_vars.rs
kjuulh 932959bc5c
All checks were successful
continuous-integration/drone/push Build is passing
feat: add support for raw reading of variables
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-11-20 16:12:47 +01:00

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(),
)
}
}