mirror of
https://codeberg.org/Toasterson/solstice-ci.git
synced 2026-04-11 05:40:41 +00:00
This commit adds: - A unified configuration system (`AppConfig`) that aggregates KDL files and environment variables with precedence handling. - Example KDL configuration files for the orchestrator and forge-integration modules. - Updates to orchestrator and forge-integration to load and apply configurations from `AppConfig`. - Improved AMQP and database configuration with overlays from CLI, environment, or KDL. - Deprecated `TODO.txt` as it's now represented in the configuration examples.
97 lines
4 KiB
Rust
97 lines
4 KiB
Rust
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
use miette::{IntoDiagnostic as _, Result};
|
|
use kdl::{KdlDocument, KdlValue};
|
|
|
|
/// Internal application configuration aggregated from env and KDL.
|
|
#[derive(Clone, Debug)]
|
|
pub struct AppConfig {
|
|
pub grpc_addr: Option<String>,
|
|
pub http_addr: Option<String>,
|
|
pub database_url: Option<String>,
|
|
pub otlp_endpoint: Option<String>,
|
|
pub mq: crate::mq::MqConfig,
|
|
}
|
|
|
|
impl AppConfig {
|
|
/// Load config by reading env vars and KDL files without mutating the environment.
|
|
/// Precedence: KDL (lowest) < Environment < CLI (applied by callers).
|
|
pub fn load(service: &str) -> Result<Self> {
|
|
let kdl_map = load_kdl_kv(service)?;
|
|
|
|
let grpc_addr = std::env::var("GRPC_ADDR").ok().or_else(|| kdl_map.get("GRPC_ADDR").cloned());
|
|
let http_addr = std::env::var("HTTP_ADDR").ok().or_else(|| kdl_map.get("HTTP_ADDR").cloned());
|
|
let database_url = std::env::var("DATABASE_URL").ok().or_else(|| kdl_map.get("DATABASE_URL").cloned());
|
|
let otlp_endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok().or_else(|| kdl_map.get("OTEL_EXPORTER_OTLP_ENDPOINT").cloned());
|
|
|
|
// Build MQ config from env with KDL fallbacks, then defaults
|
|
let url = std::env::var("AMQP_URL")
|
|
.ok()
|
|
.or_else(|| kdl_map.get("AMQP_URL").cloned())
|
|
.unwrap_or_else(|| "amqp://127.0.0.1:5672/%2f".into());
|
|
let exchange = std::env::var("AMQP_EXCHANGE")
|
|
.ok()
|
|
.or_else(|| kdl_map.get("AMQP_EXCHANGE").cloned())
|
|
.unwrap_or_else(|| "solstice.jobs".into());
|
|
let routing_key = std::env::var("AMQP_ROUTING_KEY")
|
|
.ok()
|
|
.or_else(|| kdl_map.get("AMQP_ROUTING_KEY").cloned())
|
|
.unwrap_or_else(|| "jobrequest.v1".into());
|
|
let queue = std::env::var("AMQP_QUEUE")
|
|
.ok()
|
|
.or_else(|| kdl_map.get("AMQP_QUEUE").cloned())
|
|
.unwrap_or_else(|| "solstice.jobs.v1".into());
|
|
let dlx = std::env::var("AMQP_DLX")
|
|
.ok()
|
|
.or_else(|| kdl_map.get("AMQP_DLX").cloned())
|
|
.unwrap_or_else(|| "solstice.dlx".into());
|
|
let dlq = std::env::var("AMQP_DLQ")
|
|
.ok()
|
|
.or_else(|| kdl_map.get("AMQP_DLQ").cloned())
|
|
.unwrap_or_else(|| "solstice.jobs.v1.dlq".into());
|
|
let prefetch = std::env::var("AMQP_PREFETCH")
|
|
.ok()
|
|
.and_then(|s| s.parse().ok())
|
|
.or_else(|| kdl_map.get("AMQP_PREFETCH").and_then(|s| s.parse().ok()))
|
|
.unwrap_or(64u16);
|
|
|
|
let mq = crate::mq::MqConfig { url, exchange, routing_key, queue, dlx, dlq, prefetch };
|
|
|
|
Ok(Self { grpc_addr, http_addr, database_url, otlp_endpoint, mq })
|
|
}
|
|
}
|
|
|
|
/// Load KDL files into a simple key/value map of strings.
|
|
fn load_kdl_kv(service: &str) -> Result<HashMap<String, String>> {
|
|
let global = PathBuf::from("/etc/solstice/solstice.kdl");
|
|
let svc = PathBuf::from(format!("/etc/solstice/{}.kdl", service));
|
|
let mut map = HashMap::new();
|
|
for path in [global, svc] {
|
|
if !path.exists() { continue; }
|
|
let s = std::fs::read_to_string(&path).into_diagnostic()?;
|
|
let doc: KdlDocument = s.parse().into_diagnostic()?;
|
|
for node in doc.nodes() {
|
|
let key = node.name().value().to_string();
|
|
// Prefer first argument, otherwise `value` property; skip nulls
|
|
let value_str = if let Some(entry) = node.entries().first() {
|
|
kdl_value_to_string(entry.value())
|
|
} else if let Some(v) = node.get("value") {
|
|
kdl_value_to_string(v)
|
|
} else {
|
|
None
|
|
};
|
|
if let Some(v) = value_str {
|
|
// Only insert if not already set by a previous file (global lowest precedence)
|
|
map.entry(key).or_insert(v);
|
|
}
|
|
}
|
|
}
|
|
Ok(map)
|
|
}
|
|
|
|
fn kdl_value_to_string(v: &KdlValue) -> Option<String> {
|
|
match v {
|
|
KdlValue::Null => None,
|
|
_ => Some(v.to_string()),
|
|
}
|
|
}
|