feat: add actual example

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2024-05-21 22:58:08 +02:00
parent 461f8acb73
commit f27d538b38
Signed by: kjuulh
GPG Key ID: 57B6E1465221F912
9 changed files with 216 additions and 24 deletions

View File

@ -1 +1,2 @@
pub mod cluster_vars;
pub mod cuddle_vars;

View File

@ -0,0 +1,94 @@
use std::collections::HashMap;
use crate::Component;
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum ClusterVariable {
String(String),
}
#[derive(PartialEq, Eq, Debug, Clone, Default)]
pub struct ClusterEnv {
vars: HashMap<String, ClusterVariable>,
}
#[derive(PartialEq, Eq, Debug, Clone, Default)]
pub struct ClusterVariables {
env: ClusterEnv,
name: String,
namespace: String,
}
#[derive(Default)]
pub struct ClusterVars {}
impl Component for ClusterVars {
fn name(&self) -> String {
"cluster/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>> {
let mut vars = ClusterVariables::default();
// TODO: actually extract values
if let Some(mapping) = value.as_mapping() {
if let Some(env) = mapping.get("env") {
if let Some(env_entries) = env.as_mapping() {
for (name, value) in env_entries {
if let (Some(name), Some(value)) = (name.as_str(), value.as_str()) {
vars.env
.vars
.insert(name.to_string(), ClusterVariable::String(value.into()));
}
}
}
}
}
vars.name = environment.into();
vars.namespace = environment.into();
Some(Ok(minijinja::Value::from_object(vars)))
}
}
impl minijinja::value::Object for ClusterVariables {
fn get_value(self: &std::sync::Arc<Self>, key: &minijinja::Value) -> Option<minijinja::Value> {
let out = match key.as_str()? {
"env" => minijinja::Value::from_object(self.env.clone()),
"name" => minijinja::Value::from_safe_string(self.name.clone()),
"namespace" => minijinja::Value::from_safe_string(self.namespace.clone()),
_ => return None,
};
Some(out)
}
}
impl minijinja::value::Object for ClusterEnv {
fn get_value(self: &std::sync::Arc<Self>, key: &minijinja::Value) -> Option<minijinja::Value> {
let var = self.vars.get(key.as_str()?)?;
match var {
ClusterVariable::String(s) => Some(minijinja::Value::from_safe_string(s.clone())),
}
}
fn enumerate(self: &std::sync::Arc<Self>) -> minijinja::value::Enumerator {
minijinja::value::Enumerator::Values(
self.vars
.keys()
.map(|key| minijinja::Value::from_safe_string(key.clone()))
.collect::<Vec<_>>(),
)
}
}

View File

@ -134,7 +134,11 @@ impl Component for CuddleVars {
Ok(())
}
fn render_value(&self, _value: &serde_yaml::Value) -> Option<anyhow::Result<minijinja::Value>> {
fn render_value(
&self,
environment: &str,
_value: &serde_yaml::Value,
) -> Option<anyhow::Result<minijinja::Value>> {
Some(Ok(minijinja::Value::from_object(self.variables.clone())))
}
}

View File

@ -7,12 +7,20 @@ pub trait Component {
Ok(())
}
fn render_value(&self, _value: &serde_yaml::Value) -> Option<anyhow::Result<minijinja::Value>> {
fn render_value(
&self,
environment: &str,
_value: &serde_yaml::Value,
) -> Option<anyhow::Result<minijinja::Value>> {
None
}
/// First return is name, second is contents
fn render(&self, _value: &serde_yaml::Value) -> Option<anyhow::Result<(String, String)>> {
fn render(
&self,
environment: &str,
_value: &serde_yaml::Value,
) -> Option<anyhow::Result<(String, String)>> {
None
}
}

View File

@ -101,7 +101,7 @@ async fn read_cuddle_section(path: &Path) -> anyhow::Result<CuddleClusters> {
Ok(cuddle_clusters)
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
struct TemplateFiles {
templates: HashMap<String, PathBuf>,
raw: HashMap<String, PathBuf>,
@ -124,9 +124,8 @@ fn load_template_files(path: &Path) -> BoxFuture<'static, anyhow::Result<Templat
let template_path = path.join(TEMPLATES_PATH_PREFIX);
let path = path.to_path_buf();
tracing::info!("reading folder: {}", path.display());
async move {
if template_path.exists() {
let templates = read_dir(&template_path)
.await?
.into_iter()
@ -138,13 +137,20 @@ fn load_template_files(path: &Path) -> BoxFuture<'static, anyhow::Result<Templat
let mut template_files = TemplateFiles { templates, raw };
let nested_path = path.join(CUDDLE_PLAN_PATH_PREFIX);
tracing::info!("trying to read: {}", nested_path.display());
if nested_path.exists() {
let nested = load_template_files(&nested_path).await?;
template_files.merge(nested);
}
Ok(template_files)
} else {
let nested_path = path.join(CUDDLE_PLAN_PATH_PREFIX);
if nested_path.exists() {
let nested = load_template_files(&nested_path).await?;
Ok(nested)
} else {
Ok(TemplateFiles::default())
}
}
}
.boxed()
}
@ -245,7 +251,7 @@ async fn process_template_file(
for component in components {
let name = component.name();
if let Some(value) = component.render_value(value) {
if let Some(value) = component.render_value(environment, value) {
let value = value?;
variables.insert(name.replace("/", "_"), value);

View File

@ -3,7 +3,10 @@ pub mod common;
mod can_run_for_env;
mod cuddle_vars;
use cuddle_clusters::catalog::cuddle_vars::CuddleVars;
use cuddle_clusters::{
catalog::{cluster_vars::ClusterVars, cuddle_vars::CuddleVars},
IntoComponent,
};
use crate::common::{run_test, run_test_with_components};
@ -60,3 +63,19 @@ async fn with_cuddle_vars() -> anyhow::Result<()> {
Ok(())
}
#[tokio::test]
async fn with_actual_deployment() -> anyhow::Result<()> {
let current_dir = std::env::current_dir()?.join("tests/with_cuddle_vars");
run_test_with_components(
"with_actual_deployment",
vec![
CuddleVars::new(&current_dir).await?.into_component(),
ClusterVars::default().into_component(),
],
)
.await?;
Ok(())
}

View File

@ -0,0 +1,15 @@
vars:
service: service
some:
nested:
item: something
array:
- item: item
cuddle/clusters:
dev:
env:
something: thing
nested.item: item

View File

@ -0,0 +1,8 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: service-config
data:
environment:
NESTED_ITEM: item
SOMETHING: thing

View File

@ -0,0 +1,37 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: service
cluster: dev
name: service
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: service
cluster: dev
template:
metadata:
labels:
app: service
cluster: dev
spec:
containers:
- args:
- serve
command:
- service
image: kasperhermansen/service:main-1715336504
name: service
envFrom:
- configMapRef:
name: service-config
ports:
- containerPort: 3000
name: external-http
- containerPort: 3001
name: internal-http
- containerPort: 3002
name: internal-grpc