use crate::config::types::{GlobalConfig, ModuleConfig, SolarboatConfig};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct ResolvedModuleConfig {
pub ignore_workspaces: Vec<String>,
pub var_files: Vec<String>,
}
impl Default for ResolvedModuleConfig {
fn default() -> Self {
Self {
ignore_workspaces: Vec::new(),
var_files: Vec::new(),
}
}
}
pub struct ConfigResolver {
config: Option<SolarboatConfig>,
config_dir: PathBuf,
}
impl ConfigResolver {
pub fn new(config: Option<SolarboatConfig>, config_dir: PathBuf) -> Self {
Self { config, config_dir }
}
pub fn resolve_module_config(
&self,
module_path: &str,
cli_ignore_workspaces: Option<&[String]>,
) -> ResolvedModuleConfig {
let module_config = self.get_module_config(module_path);
let global_config = self.get_global_config();
let mut resolved = ResolvedModuleConfig::default();
resolved.ignore_workspaces = self.resolve_ignore_workspaces(
cli_ignore_workspaces,
&module_config.ignore_workspaces,
&global_config.ignore_workspaces,
);
resolved.var_files = Vec::new();
resolved
}
pub fn get_workspace_var_files(
&self,
module_path: &str,
workspace: &str,
cli_var_files: Option<&[String]>,
) -> Vec<String> {
let mut var_files = Vec::new();
if let Some(cli_var_files) = cli_var_files {
var_files.extend(cli_var_files.to_vec());
}
let workspace_var_files = self.resolve_workspace_var_files(
module_path,
workspace,
);
var_files.extend(workspace_var_files);
var_files = self.resolve_var_file_paths(&var_files, module_path);
var_files
}
fn resolve_ignore_workspaces(
&self,
cli_ignore: Option<&[String]>,
module_ignore: &[String],
global_ignore: &[String],
) -> Vec<String> {
if let Some(cli_ignore) = cli_ignore {
return cli_ignore.to_vec();
}
if !module_ignore.is_empty() {
return module_ignore.to_vec();
}
global_ignore.to_vec()
}
fn resolve_workspace_var_files(&self, module_path: &str, workspace: &str) -> Vec<String> {
let module_config = self.get_module_config(module_path);
let global_config = self.get_global_config();
if let Some(module_workspace_files) = &module_config.workspace_var_files {
if module_workspace_files.has_workspace(workspace) {
return module_workspace_files.get_workspace_files(workspace);
}
}
if let Some(global_workspace_files) = &global_config.workspace_var_files {
if global_workspace_files.has_workspace(workspace) {
return global_workspace_files.get_workspace_files(workspace);
}
}
Vec::new()
}
fn resolve_var_file_paths(&self, var_files: &[String], module_path: &str) -> Vec<String> {
var_files
.iter()
.map(|var_file| {
if Path::new(var_file).is_absolute() {
var_file.clone()
} else {
let module_dir = self.config_dir.join(module_path);
module_dir.join(var_file).to_string_lossy().to_string()
}
})
.collect()
}
fn get_module_config(&self, module_path: &str) -> ModuleConfig {
let normalized_path = self.normalize_module_path(module_path);
self.config
.as_ref()
.and_then(|config| config.modules.get(&normalized_path))
.cloned()
.unwrap_or_default()
}
fn normalize_module_path(&self, module_path: &str) -> String {
let module_path = Path::new(module_path);
if module_path.is_absolute() {
if let Ok(relative_path) = module_path.strip_prefix(&self.config_dir) {
return relative_path.to_string_lossy().to_string();
}
}
module_path.to_string_lossy().to_string()
}
fn get_global_config(&self) -> GlobalConfig {
self.config
.as_ref()
.map(|config| config.global.clone())
.unwrap_or_default()
}
pub fn should_ignore_workspace(
&self,
module_path: &str,
workspace: &str,
cli_ignore_workspaces: Option<&[String]>,
) -> bool {
let resolved_config = self.resolve_module_config(module_path, cli_ignore_workspaces);
resolved_config.ignore_workspaces.contains(&workspace.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::WorkspaceVarFiles;
use std::collections::HashMap;
use crate::config::types::SolarboatConfig;
fn create_test_config() -> SolarboatConfig {
let mut global_workspace_files = WorkspaceVarFiles {
workspaces: HashMap::new(),
};
global_workspace_files.workspaces.insert("prod".to_string(), vec!["global-prod.tfvars".to_string()]);
let mut module_workspace_files = WorkspaceVarFiles {
workspaces: HashMap::new(),
};
module_workspace_files.workspaces.insert("prod".to_string(), vec!["module-prod.tfvars".to_string()]);
let mut modules = HashMap::new();
modules.insert(
"infrastructure/networking".to_string(),
ModuleConfig {
ignore_workspaces: vec!["dev".to_string()],
workspace_var_files: Some(module_workspace_files),
},
);
SolarboatConfig {
global: GlobalConfig {
ignore_workspaces: vec!["test".to_string()],
workspace_var_files: Some(global_workspace_files),
},
modules,
}
}
#[test]
fn test_resolve_module_config() {
let config = create_test_config();
let resolver = ConfigResolver::new(Some(config), PathBuf::from("/tmp"));
let resolved = resolver.resolve_module_config(
"infrastructure/networking",
None,
);
assert_eq!(resolved.ignore_workspaces, vec!["dev"]);
assert_eq!(resolved.var_files, Vec::<String>::new()); }
#[test]
fn test_cli_overrides_config() {
let config = create_test_config();
let resolver = ConfigResolver::new(Some(config), PathBuf::from("/tmp"));
let resolved = resolver.resolve_module_config(
"infrastructure/networking",
Some(&["cli-ignore".to_string()]),
);
assert_eq!(resolved.ignore_workspaces, vec!["cli-ignore"]);
assert_eq!(resolved.var_files, Vec::<String>::new()); }
#[test]
fn test_workspace_var_files() {
let config = create_test_config();
let resolver = ConfigResolver::new(Some(config), PathBuf::from("/tmp"));
let var_files = resolver.get_workspace_var_files(
"infrastructure/networking",
"prod",
None,
);
assert!(var_files.contains(&"/tmp/infrastructure/networking/module-prod.tfvars".to_string()));
}
#[test]
fn test_should_ignore_workspace() {
let config = create_test_config();
let resolver = ConfigResolver::new(Some(config), PathBuf::from("/tmp"));
assert!(resolver.should_ignore_workspace("infrastructure/networking", "dev", None));
assert!(!resolver.should_ignore_workspace("infrastructure/networking", "prod", None));
}
}