mod ai;
mod cluster;
mod service;
use std::path::Path;
use crate::error::{OrcaError, Result};
pub use crate::backup::{BackupConfig, BackupTarget};
pub use ai::{AiAlertConfig, AiConfig, AlertDeliveryChannels, AutoRemediateConfig};
pub use cluster::NetworkConfig;
pub use cluster::{
AlertChannelConfig, ApiToken, ClusterConfig, ClusterMeta, FallbackConfig, NodeConfig,
NodeGpuConfig, ObservabilityConfig, Role,
};
pub use service::{BuildConfig, ProbeConfig, ServiceConfig, ServicesConfig};
impl ClusterConfig {
pub fn load(path: &Path) -> Result<Self> {
let content = std::fs::read_to_string(path)
.map_err(|e| OrcaError::Config(format!("failed to read {}: {e}", path.display())))?;
let mut config: Self = toml::from_str(&content)
.map_err(|e| OrcaError::Config(format!("failed to parse {}: {e}", path.display())))?;
config.resolve_secrets();
Ok(config)
}
fn resolve_secrets(&mut self) {
let store = match crate::secrets::SecretStore::open(crate::secrets::default_path()) {
Ok(s) => s,
Err(_) => return,
};
let resolve = |s: &mut Option<String>| {
if let Some(val) = s
&& val.contains("${secrets.")
{
let as_map = std::collections::HashMap::from([("_".to_string(), val.clone())]);
let resolved = store.resolve_env(&as_map);
*val = resolved.get("_").cloned().unwrap_or_default();
}
};
if let Some(ai) = &mut self.ai {
resolve(&mut ai.api_key);
resolve(&mut ai.endpoint);
}
if let Some(net) = &mut self.network {
resolve(&mut net.setup_key);
}
}
}
impl ServicesConfig {
pub fn load(path: &Path) -> Result<Self> {
let content = std::fs::read_to_string(path)
.map_err(|e| OrcaError::Config(format!("failed to read {}: {e}", path.display())))?;
toml::from_str(&content)
.map_err(|e| OrcaError::Config(format!("failed to parse {}: {e}", path.display())))
}
pub fn load_dir(dir: &Path) -> Result<Self> {
let mut all_services = Vec::new();
let entries = std::fs::read_dir(dir)
.map_err(|e| OrcaError::Config(format!("failed to read {}: {e}", dir.display())))?;
let mut subdirs: Vec<_> = entries
.filter_map(|e| e.ok())
.filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
.collect();
subdirs.sort_by_key(|e| e.file_name());
for entry in subdirs {
let svc_file = entry.path().join("service.toml");
if svc_file.exists() {
let mut config = Self::load(&svc_file)?;
let project_name = entry.file_name().to_string_lossy().to_string();
for svc in &mut config.service {
svc.project = Some(project_name.clone());
if svc.network.is_none() {
svc.network = Some(project_name.clone());
}
}
all_services.extend(config.service);
}
}
if all_services.is_empty() {
return Err(OrcaError::Config(format!(
"no service.toml files found in {}",
dir.display()
)));
}
Ok(ServicesConfig {
service: all_services,
})
}
}
#[cfg(test)]
#[path = "tests_parse.rs"]
mod tests_parse;
#[cfg(test)]
#[path = "tests_load.rs"]
mod tests_load;
#[cfg(test)]
#[path = "tests_secrets.rs"]
mod tests_secrets;