1use serde::{Deserialize, Serialize};
8use std::path::PathBuf;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct VitrineConfig {
13 pub target: TargetEnvironment,
14 pub argocd: Option<ArgoCdConfig>,
15 pub evidence: EvidenceConfig,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct TargetEnvironment {
21 pub kubectl_context: String,
23
24 pub cloud_project: String,
26
27 pub terragrunt_root: PathBuf,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ArgoCdConfig {
34 pub cluster_terragrunt: PathBuf,
36
37 pub namespace: String,
39
40 pub applicationset_path: Option<PathBuf>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct EvidenceConfig {
47 pub output_path: PathBuf,
49
50 #[serde(default)]
52 pub include_full_plan: bool,
53
54 #[serde(default)]
56 pub functional_probes: Vec<FunctionalProbe>,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case")]
62pub enum ProbeLayer {
63 TfState,
65 CloudApi,
67 Functional,
69}
70
71impl ProbeLayer {
72 pub fn label(self) -> &'static str {
73 match self {
74 ProbeLayer::TfState => "TF state",
75 ProbeLayer::CloudApi => "Cloud API",
76 ProbeLayer::Functional => "Functional",
77 }
78 }
79}
80
81fn default_layer() -> ProbeLayer {
82 ProbeLayer::Functional
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct FunctionalProbe {
88 pub name: String,
90
91 pub command: String,
93
94 #[serde(default = "default_layer")]
96 pub layer: ProbeLayer,
97
98 pub expected_exit: Option<i32>,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct PatternAOverride {
105 pub chart_name: String,
107
108 pub feature_branch: String,
110}
111
112#[derive(thiserror::Error, Debug)]
113pub enum ConfigError {
114 #[error("failed to read config file: {0}")]
115 Io(#[from] std::io::Error),
116
117 #[error("failed to parse config TOML: {0}")]
118 Toml(#[from] toml::de::Error),
119
120 #[error("missing required field: {0}")]
121 MissingField(String),
122}
123
124impl VitrineConfig {
125 pub fn load(path: &std::path::Path) -> Result<Self, ConfigError> {
127 let text = std::fs::read_to_string(path)?;
128 let cfg: VitrineConfig = toml::from_str(&text)?;
129 Ok(cfg)
130 }
131
132 pub fn discover() -> Result<Option<PathBuf>, ConfigError> {
134 if let Ok(p) = std::env::var("VITRINE_CONFIG") {
135 return Ok(Some(PathBuf::from(p)));
136 }
137 let candidates = [".vitrine.toml", "vitrine.toml"];
138 for c in &candidates {
139 let p = PathBuf::from(c);
140 if p.exists() {
141 return Ok(Some(p));
142 }
143 }
144 Ok(None)
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn minimal_config_parses() {
154 let text = r#"
155[target]
156kubectl_context = "dbk-staging-europe-west3-gke"
157cloud_project = "dbk-staging-314422"
158terragrunt_root = "/path/to/terragrunt"
159
160[evidence]
161output_path = "evidence.md"
162"#;
163 let cfg: VitrineConfig = toml::from_str(text).unwrap();
164 assert_eq!(cfg.target.kubectl_context, "dbk-staging-europe-west3-gke");
165 assert!(cfg.argocd.is_none());
166 assert!(cfg.evidence.functional_probes.is_empty());
167 }
168}