diff --git a/crates/cuddle-clusters/src/catalog.rs b/crates/cuddle-clusters/src/catalog.rs index e6ccdfb..26cb49b 100644 --- a/crates/cuddle-clusters/src/catalog.rs +++ b/crates/cuddle-clusters/src/catalog.rs @@ -1 +1,2 @@ +pub mod cluster_vars; pub mod cuddle_vars; diff --git a/crates/cuddle-clusters/src/catalog/cluster_vars.rs b/crates/cuddle-clusters/src/catalog/cluster_vars.rs new file mode 100644 index 0000000..b19d1ab --- /dev/null +++ b/crates/cuddle-clusters/src/catalog/cluster_vars.rs @@ -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, +} + +#[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> { + 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, key: &minijinja::Value) -> Option { + 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, key: &minijinja::Value) -> Option { + 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) -> minijinja::value::Enumerator { + minijinja::value::Enumerator::Values( + self.vars + .keys() + .map(|key| minijinja::Value::from_safe_string(key.clone())) + .collect::>(), + ) + } +} diff --git a/crates/cuddle-clusters/src/catalog/cuddle_vars.rs b/crates/cuddle-clusters/src/catalog/cuddle_vars.rs index 73d89bf..4fbc26d 100644 --- a/crates/cuddle-clusters/src/catalog/cuddle_vars.rs +++ b/crates/cuddle-clusters/src/catalog/cuddle_vars.rs @@ -134,7 +134,11 @@ impl Component for CuddleVars { Ok(()) } - fn render_value(&self, _value: &serde_yaml::Value) -> Option> { + fn render_value( + &self, + environment: &str, + _value: &serde_yaml::Value, + ) -> Option> { Some(Ok(minijinja::Value::from_object(self.variables.clone()))) } } diff --git a/crates/cuddle-clusters/src/components.rs b/crates/cuddle-clusters/src/components.rs index ac30479..5cc5c65 100644 --- a/crates/cuddle-clusters/src/components.rs +++ b/crates/cuddle-clusters/src/components.rs @@ -7,12 +7,20 @@ pub trait Component { Ok(()) } - fn render_value(&self, _value: &serde_yaml::Value) -> Option> { + fn render_value( + &self, + environment: &str, + _value: &serde_yaml::Value, + ) -> Option> { None } /// First return is name, second is contents - fn render(&self, _value: &serde_yaml::Value) -> Option> { + fn render( + &self, + environment: &str, + _value: &serde_yaml::Value, + ) -> Option> { None } } diff --git a/crates/cuddle-clusters/src/process.rs b/crates/cuddle-clusters/src/process.rs index 3f7383b..83f162b 100644 --- a/crates/cuddle-clusters/src/process.rs +++ b/crates/cuddle-clusters/src/process.rs @@ -101,7 +101,7 @@ async fn read_cuddle_section(path: &Path) -> anyhow::Result { Ok(cuddle_clusters) } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] struct TemplateFiles { templates: HashMap, raw: HashMap, @@ -124,27 +124,33 @@ fn load_template_files(path: &Path) -> BoxFuture<'static, anyhow::Result 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(¤t_dir).await?.into_component(), + ClusterVars::default().into_component(), + ], + ) + .await?; + + Ok(()) +} diff --git a/crates/cuddle-clusters/tests/with_actual_deployment/cuddle.yaml b/crates/cuddle-clusters/tests/with_actual_deployment/cuddle.yaml new file mode 100644 index 0000000..cc48aac --- /dev/null +++ b/crates/cuddle-clusters/tests/with_actual_deployment/cuddle.yaml @@ -0,0 +1,15 @@ +vars: + service: service + some: + nested: + item: something + array: + - item: item + + +cuddle/clusters: + dev: + env: + something: thing + nested.item: item + diff --git a/crates/cuddle-clusters/tests/with_actual_deployment/expected/dev/configmap.yaml b/crates/cuddle-clusters/tests/with_actual_deployment/expected/dev/configmap.yaml new file mode 100644 index 0000000..60f6ab4 --- /dev/null +++ b/crates/cuddle-clusters/tests/with_actual_deployment/expected/dev/configmap.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: service-config +data: + environment: + NESTED_ITEM: item + SOMETHING: thing \ No newline at end of file diff --git a/crates/cuddle-clusters/tests/with_actual_deployment/expected/dev/deployment.yaml b/crates/cuddle-clusters/tests/with_actual_deployment/expected/dev/deployment.yaml new file mode 100644 index 0000000..3a2643e --- /dev/null +++ b/crates/cuddle-clusters/tests/with_actual_deployment/expected/dev/deployment.yaml @@ -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 \ No newline at end of file