Platform restructured into crates/, added AI service and detector,
migrated control-center-ui to Leptos 0.8
222 lines
6.4 KiB
Rust
222 lines
6.4 KiB
Rust
//! Centralized configuration management for all platform services
|
|
//!
|
|
//! The ConfigManager handles loading, validating, and caching configurations
|
|
//! for all platform services. It provides fail-fast validation on startup
|
|
//! to prevent cascading crashes.
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::RwLock;
|
|
|
|
use anyhow::Result;
|
|
use serde_json::Value;
|
|
use tracing::{debug, error, info};
|
|
|
|
/// Central config manager for all platform services
|
|
///
|
|
/// This struct manages loading and validating configs for all platform
|
|
/// services. It caches loaded configs to avoid repeated parsing and provides
|
|
/// fail-fast validation on startup.
|
|
pub struct ConfigManager {
|
|
/// Cache of loaded configs (JSON representation)
|
|
/// Key format: "{service_name}.{mode}" (e.g., "orchestrator.solo")
|
|
cache: RwLock<HashMap<String, Value>>,
|
|
|
|
/// Current deployment mode
|
|
mode: String,
|
|
}
|
|
|
|
impl ConfigManager {
|
|
/// Creates a new ConfigManager with the current deployment mode
|
|
pub fn new() -> Self {
|
|
let mode = std::env::var("PROVISIONING_MODE").unwrap_or_else(|_| "solo".to_string());
|
|
|
|
Self {
|
|
cache: RwLock::new(HashMap::new()),
|
|
mode,
|
|
}
|
|
}
|
|
|
|
/// Creates a ConfigManager with a specific mode
|
|
pub fn with_mode<S: Into<String>>(mode: S) -> Self {
|
|
Self {
|
|
cache: RwLock::new(HashMap::new()),
|
|
mode: mode.into(),
|
|
}
|
|
}
|
|
|
|
/// Loads a service config as JSON (generic, without type information)
|
|
///
|
|
/// This is useful for validation without knowing the concrete type.
|
|
pub fn load_as_json(&self, service_name: &str) -> Result<Value> {
|
|
let cache_key = format!("{}.{}", service_name, self.mode);
|
|
|
|
// Check cache first
|
|
{
|
|
let cache_read = self.cache.read().unwrap();
|
|
if let Some(cached) = cache_read.get(&cache_key) {
|
|
debug!("Config cache hit for {}", cache_key);
|
|
return Ok(cached.clone());
|
|
}
|
|
}
|
|
|
|
// Cache miss - load from disk
|
|
debug!("Config cache miss for {} - loading from disk", cache_key);
|
|
|
|
let path =
|
|
platform_config::find_config_file(service_name, &self.mode).ok_or_else(|| {
|
|
anyhow::anyhow!("Config file not found for {}.{}", service_name, self.mode)
|
|
})?;
|
|
|
|
let json_value: Value = platform_config::format::load_config(&path)
|
|
.map_err(|e| anyhow::anyhow!("Failed to load config for {}: {}", service_name, e))?;
|
|
|
|
// Store in cache
|
|
{
|
|
let mut cache_write = self.cache.write().unwrap();
|
|
cache_write.insert(cache_key.clone(), json_value.clone());
|
|
}
|
|
|
|
Ok(json_value)
|
|
}
|
|
|
|
/// Validates that ALL known platform services have valid configs
|
|
///
|
|
/// This is the main validation entry point for pre-flight checks.
|
|
/// Returns Ok if all configs are valid, Err with detailed failure info
|
|
/// otherwise.
|
|
pub fn validate_all_services(&self) -> Result<()> {
|
|
let services = vec![
|
|
"orchestrator",
|
|
"control-center",
|
|
"mcp-server",
|
|
"ai-service",
|
|
"extension-registry",
|
|
"rag",
|
|
"vault-service",
|
|
"provisioning-daemon",
|
|
];
|
|
|
|
info!(
|
|
"Validating configs for {} services (mode: {})",
|
|
services.len(),
|
|
self.mode
|
|
);
|
|
|
|
let mut errors = Vec::new();
|
|
|
|
for service in services {
|
|
match self.load_as_json(service) {
|
|
Ok(_) => {
|
|
debug!("✅ {}.{} config valid", service, self.mode);
|
|
}
|
|
Err(e) => {
|
|
let error_msg = format!("❌ {}.{} config invalid: {}", service, self.mode, e);
|
|
error!("{}", error_msg);
|
|
errors.push(error_msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
if !errors.is_empty() {
|
|
let summary = format!(
|
|
"Config validation failed for {} services:\n{}",
|
|
errors.len(),
|
|
errors.join("\n")
|
|
);
|
|
error!("{}", summary);
|
|
return Err(anyhow::anyhow!("{}", summary));
|
|
}
|
|
|
|
info!("✅ All service configs validated successfully");
|
|
Ok(())
|
|
}
|
|
|
|
/// Validates a subset of services
|
|
pub fn validate_services(&self, services: &[&str]) -> Result<()> {
|
|
debug!(
|
|
"Validating {} specific services (mode: {})",
|
|
services.len(),
|
|
self.mode
|
|
);
|
|
|
|
let mut errors = Vec::new();
|
|
|
|
for service in services {
|
|
match self.load_as_json(service) {
|
|
Ok(_) => {
|
|
debug!("✅ {}.{} config valid", service, self.mode);
|
|
}
|
|
Err(e) => {
|
|
errors.push(format!("❌ {}: {}", service, e));
|
|
}
|
|
}
|
|
}
|
|
|
|
if !errors.is_empty() {
|
|
let summary = errors.join("\n");
|
|
error!("{}", summary);
|
|
return Err(anyhow::anyhow!("Validation failed:\n{}", summary));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Clears the entire config cache
|
|
pub fn clear_cache(&self) {
|
|
let mut cache = self.cache.write().unwrap();
|
|
cache.clear();
|
|
debug!("Config cache cleared");
|
|
}
|
|
|
|
/// Invalidates cache for a specific service
|
|
pub fn invalidate_service(&self, service_name: &str) {
|
|
let cache_key = format!("{}.{}", service_name, self.mode);
|
|
let mut cache = self.cache.write().unwrap();
|
|
cache.remove(&cache_key);
|
|
debug!("Invalidated cache for {}", cache_key);
|
|
}
|
|
|
|
/// Gets the current deployment mode
|
|
pub fn mode(&self) -> &str {
|
|
&self.mode
|
|
}
|
|
|
|
/// Gets the number of cached configs
|
|
pub fn cache_size(&self) -> usize {
|
|
self.cache.read().unwrap().len()
|
|
}
|
|
}
|
|
|
|
impl Default for ConfigManager {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_config_manager_creation() {
|
|
let mgr = ConfigManager::new();
|
|
assert!(!mgr.mode().is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_with_mode() {
|
|
let mgr = ConfigManager::with_mode("test");
|
|
assert_eq!(mgr.mode(), "test");
|
|
}
|
|
|
|
#[test]
|
|
fn test_cache_operations() {
|
|
let mgr = ConfigManager::new();
|
|
assert_eq!(mgr.cache_size(), 0);
|
|
|
|
// Clear doesn't fail even when empty
|
|
mgr.clear_cache();
|
|
assert_eq!(mgr.cache_size(), 0);
|
|
}
|
|
}
|