use crate::components::{Component, ComponentRegistry, HelmComponent};
use crate::config::ProjectConfig;
use crate::resources::{ChartRef, HelmChart};
use crate::Result;
use std::path::PathBuf;
pub struct Generator {
registry: ComponentRegistry,
config: ProjectConfig,
}
impl Generator {
pub fn new(config: ProjectConfig) -> Self {
let search_paths = Self::build_search_paths(&config);
let registry = ComponentRegistry::new(search_paths);
Self { registry, config }
}
fn build_search_paths(config: &ProjectConfig) -> Vec<PathBuf> {
config.get_components_search_paths().to_vec()
}
pub fn find_component(&self, api_version: &str, kind: &str) -> Result<Option<Component>> {
self.registry.find_component(api_version, kind)
}
pub fn instantiate_component(
&self,
component: &Component,
name: &str,
values: &serde_json::Value,
) -> Result<HelmChart> {
match component {
Component::Helm(helm_component) => Self::instantiate_helm_component(helm_component, name, values),
}
}
fn instantiate_helm_component(
component: &HelmComponent,
name: &str,
values: &serde_json::Value,
) -> Result<HelmChart> {
let chart_ref = ChartRef {
name: Some(component.path.to_string_lossy().to_string()),
..Default::default()
};
let mut helm_chart = HelmChart::new(name, chart_ref);
helm_chart.api_version.clone_from(&component.api_version);
helm_chart.kind.clone_from(&component.kind);
helm_chart = helm_chart.with_values(values.clone());
Ok(helm_chart)
}
pub fn list_components(&self) -> Result<Vec<Component>> {
self.registry.list_components()
}
pub fn generate(&self) -> Result<Vec<String>> {
Ok(vec![])
}
pub fn registry(&self) -> &ComponentRegistry {
&self.registry
}
pub fn config(&self) -> &ProjectConfig {
&self.config
}
}
impl Default for Generator {
fn default() -> Self {
Self::new(ProjectConfig::load(None).unwrap_or_else(|_| ProjectConfig {
file: None,
config: crate::config::ProjectFile::default(),
}))
}
}
impl std::fmt::Debug for Generator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Generator")
.field("registry", &self.registry)
.field("config_file", &self.config.file)
.finish()
}
}
pub fn instantiate_component(component: &Component, name: &str, values: &serde_json::Value) -> Result<HelmChart> {
match component {
Component::Helm(helm_component) => instantiate_helm_component(helm_component, name, values),
}
}
fn instantiate_helm_component(component: &HelmComponent, name: &str, values: &serde_json::Value) -> Result<HelmChart> {
let chart_ref = ChartRef {
name: Some(component.path.to_string_lossy().to_string()),
..Default::default()
};
let mut helm_chart = HelmChart::new(name, chart_ref);
helm_chart.api_version.clone_from(&component.api_version);
helm_chart.kind.clone_from(&component.kind);
helm_chart = helm_chart.with_values(values.clone());
Ok(helm_chart)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
fn create_test_config(base: &std::path::Path) -> ProjectConfig {
let config_path = base.join("nyl.toml");
fs::write(&config_path, "[project]\ncomponents_search_paths = [\"components\"]\n").unwrap();
ProjectConfig::load(Some(config_path)).unwrap()
}
fn create_test_component(base: &std::path::Path, api_version: &str, kind: &str) {
let component_dir = base.join("components").join(api_version).join(kind);
fs::create_dir_all(&component_dir).unwrap();
let chart_yaml = component_dir.join("Chart.yaml");
fs::write(
chart_yaml,
format!("apiVersion: v2\nname: {}\nversion: 1.0.0\n", kind.to_lowercase()),
)
.unwrap();
}
#[test]
fn test_generator_new() {
let temp = TempDir::new().unwrap();
let config = create_test_config(temp.path());
let generator = Generator::new(config);
assert!(generator.config().file.is_some());
}
#[test]
fn test_generator_find_component() {
let temp = TempDir::new().unwrap();
create_test_component(temp.path(), "v1.example.io", "WebApp");
let config = create_test_config(temp.path());
let generator = Generator::new(config);
let component = generator.find_component("v1.example.io", "WebApp").unwrap();
assert!(component.is_some());
}
#[test]
fn test_generator_instantiate_component() {
let temp = TempDir::new().unwrap();
create_test_component(temp.path(), "v1.example.io", "WebApp");
let config = create_test_config(temp.path());
let generator = Generator::new(config);
let component = generator.find_component("v1.example.io", "WebApp").unwrap().unwrap();
let values = serde_json::json!({
"replicas": 3,
"image": "nginx:latest"
});
let helm_chart = generator
.instantiate_component(&component, "my-webapp", &values)
.unwrap();
assert_eq!(helm_chart.metadata.name, "my-webapp");
assert_eq!(helm_chart.api_version, "v1.example.io");
assert_eq!(helm_chart.kind, "WebApp");
assert_eq!(helm_chart.spec.values["replicas"], 3);
assert_eq!(helm_chart.release_name(), "my-webapp");
}
#[test]
fn test_generator_list_components() {
let temp = TempDir::new().unwrap();
create_test_component(temp.path(), "v1.example.io", "WebApp");
create_test_component(temp.path(), "v1.example.io", "Database");
let config = create_test_config(temp.path());
let generator = Generator::new(config);
let components = generator.list_components().unwrap();
assert_eq!(components.len(), 2);
}
#[test]
fn test_instantiate_component_helper() {
let helm_component = HelmComponent {
path: PathBuf::from("/test/charts/myapp"),
api_version: "v1.test.io".to_string(),
kind: "TestApp".to_string(),
};
let component = Component::Helm(helm_component);
let values = serde_json::json!({
"replicas": 2
});
let helm_chart = instantiate_component(&component, "test-instance", &values).unwrap();
assert_eq!(helm_chart.metadata.name, "test-instance");
assert_eq!(helm_chart.api_version, "v1.test.io");
assert_eq!(helm_chart.kind, "TestApp");
assert_eq!(helm_chart.spec.values["replicas"], 2);
}
#[test]
fn test_instantiate_helm_component_direct() {
let helm_component = HelmComponent {
path: PathBuf::from("/charts/webapp"),
api_version: "v2.apps.io".to_string(),
kind: "WebApplication".to_string(),
};
let values = serde_json::json!({
"port": 8080,
"environment": "production"
});
let helm_chart = instantiate_helm_component(&helm_component, "prod-app", &values).unwrap();
assert_eq!(helm_chart.metadata.name, "prod-app");
assert_eq!(helm_chart.api_version, "v2.apps.io");
assert_eq!(helm_chart.kind, "WebApplication");
assert_eq!(helm_chart.spec.chart.name, Some("/charts/webapp".to_string()));
assert_eq!(helm_chart.spec.values["port"], 8080);
assert_eq!(helm_chart.spec.values["environment"], "production");
}
#[test]
fn test_generator_default() {
let generator = Generator::default();
assert!(generator.config().file.is_none());
}
#[test]
fn test_build_search_paths() {
let temp = TempDir::new().unwrap();
let config_path = temp.path().join("nyl.toml");
let toml = format!(
r"[project]
components_search_paths = ['{}']
",
temp.path().join("lib").display(),
);
fs::write(&config_path, toml).unwrap();
let config = ProjectConfig::load(Some(config_path)).unwrap();
let paths = Generator::build_search_paths(&config);
assert_eq!(paths.len(), 1);
assert!(paths[0].ends_with("lib"));
}
}