1use anyhow::{Context, Result};
2use serde_yaml;
3use std::path::Path;
4use std::collections::HashMap;
5
6use crate::models::RemoteExecutionConfig;
7use crate::vars::VariableManager;
8
9pub struct ConfigManager;
11
12impl ConfigManager {
13 pub fn from_yaml_file_raw<P: AsRef<Path>>(path: P) -> Result<RemoteExecutionConfig> {
15 let content = std::fs::read_to_string(path)
16 .context("Failed to read YAML configuration file")?;
17
18 Self::from_yaml_str_raw(&content)
19 }
20
21 pub fn from_yaml_str_raw(yaml_content: &str) -> Result<RemoteExecutionConfig> {
23 let config: RemoteExecutionConfig = serde_yaml::from_str(yaml_content)
24 .context("Failed to parse YAML configuration")?;
25
26 Ok(config)
27 }
28
29 pub fn from_yaml_file_with_variables<P: AsRef<Path>>(path: P, variable_manager: &VariableManager) -> Result<RemoteExecutionConfig> {
31 let content = std::fs::read_to_string(path)
32 .context("Failed to read YAML configuration file")?;
33
34 Self::from_yaml_str_with_variables(&content, variable_manager)
35 }
36
37 pub fn from_yaml_str_with_variables(yaml_content: &str, variable_manager: &VariableManager) -> Result<RemoteExecutionConfig> {
39 let replaced_content = variable_manager.replace_variables(yaml_content);
41
42 let config: RemoteExecutionConfig = serde_yaml::from_str(&replaced_content)
44 .context("Failed to parse YAML configuration after variable replacement")?;
45
46 Ok(config)
47 }
48
49 pub fn extract_initial_variables(yaml_content: &str) -> Result<Option<HashMap<String, String>>> {
51 let yaml_value: serde_yaml::Value = serde_yaml::from_str(yaml_content)
52 .context("Failed to parse YAML for variable extraction")?;
53
54 let initial_variables = if let Some(vars) = yaml_value.get("variables") {
55 if let Ok(vars_map) = serde_yaml::from_value::<HashMap<String, String>>(vars.clone()) {
56 Some(vars_map)
57 } else {
58 None
59 }
60 } else {
61 None
62 };
63
64 Ok(initial_variables)
65 }
66
67 pub fn from_yaml_file<P: AsRef<Path>>(path: P) -> Result<RemoteExecutionConfig> {
69 let content = std::fs::read_to_string(path)
70 .context("Failed to read YAML configuration file")?;
71
72 Self::from_yaml_str(&content)
73 }
74
75 pub fn from_yaml_str(yaml_content: &str) -> Result<RemoteExecutionConfig> {
77 let initial_variables = Self::extract_initial_variables(yaml_content)?;
79
80 let variable_manager = VariableManager::new(initial_variables);
82
83 Self::from_yaml_str_with_variables(yaml_content, &variable_manager)
85 }
86
87 pub fn validate_config(config: &RemoteExecutionConfig) -> Result<()> {
89 if config.clients.is_empty() {
91 return Err(anyhow::anyhow!("No clients configured"));
92 }
93
94 if config.pipelines.is_empty() {
96 return Err(anyhow::anyhow!("No pipelines configured"));
97 }
98
99 for pipeline in &config.pipelines {
101 if pipeline.steps.is_empty() {
102 return Err(anyhow::anyhow!("Pipeline '{}' has no steps", pipeline.name));
103 }
104
105 for step in &pipeline.steps {
106 if !step.servers.is_empty() {
108 for server in &step.servers {
110 if !config.clients.contains_key(server) {
111 return Err(anyhow::anyhow!("Server '{}' referenced in step '{}' not found in clients",
112 server, step.name));
113 }
114 }
115 }
116 }
117 }
118
119 Ok(())
120 }
121}