use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VitrineConfig {
pub target: TargetEnvironment,
pub argocd: Option<ArgoCdConfig>,
pub evidence: EvidenceConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TargetEnvironment {
pub kubectl_context: String,
pub cloud_project: String,
pub terragrunt_root: PathBuf,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArgoCdConfig {
pub cluster_terragrunt: PathBuf,
pub namespace: String,
pub applicationset_path: Option<PathBuf>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvidenceConfig {
pub output_path: PathBuf,
#[serde(default)]
pub include_full_plan: bool,
#[serde(default)]
pub functional_probes: Vec<FunctionalProbe>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProbeLayer {
TfState,
CloudApi,
Functional,
}
impl ProbeLayer {
pub fn label(self) -> &'static str {
match self {
ProbeLayer::TfState => "TF state",
ProbeLayer::CloudApi => "Cloud API",
ProbeLayer::Functional => "Functional",
}
}
}
fn default_layer() -> ProbeLayer {
ProbeLayer::Functional
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionalProbe {
pub name: String,
pub command: String,
#[serde(default = "default_layer")]
pub layer: ProbeLayer,
pub expected_exit: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatternAOverride {
pub chart_name: String,
pub feature_branch: String,
}
#[derive(thiserror::Error, Debug)]
pub enum ConfigError {
#[error("failed to read config file: {0}")]
Io(#[from] std::io::Error),
#[error("failed to parse config TOML: {0}")]
Toml(#[from] toml::de::Error),
#[error("missing required field: {0}")]
MissingField(String),
}
impl VitrineConfig {
pub fn load(path: &std::path::Path) -> Result<Self, ConfigError> {
let text = std::fs::read_to_string(path)?;
let cfg: VitrineConfig = toml::from_str(&text)?;
Ok(cfg)
}
pub fn discover() -> Result<Option<PathBuf>, ConfigError> {
if let Ok(p) = std::env::var("VITRINE_CONFIG") {
return Ok(Some(PathBuf::from(p)));
}
let candidates = [".vitrine.toml", "vitrine.toml"];
for c in &candidates {
let p = PathBuf::from(c);
if p.exists() {
return Ok(Some(p));
}
}
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn minimal_config_parses() {
let text = r#"
[target]
kubectl_context = "dbk-staging-europe-west3-gke"
cloud_project = "dbk-staging-314422"
terragrunt_root = "/path/to/terragrunt"
[evidence]
output_path = "evidence.md"
"#;
let cfg: VitrineConfig = toml::from_str(text).unwrap();
assert_eq!(cfg.target.kubectl_context, "dbk-staging-europe-west3-gke");
assert!(cfg.argocd.is_none());
assert!(cfg.evidence.functional_probes.is_empty());
}
}