1use std::collections::BTreeMap;
4use std::fmt;
5
6use olai_stack_state::{VarSet, VarValue};
7use serde::Serialize;
8
9use crate::secrets::{SecretError, SecretProviders, SecretValue};
10use crate::source::Overlay;
11
12#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
15#[serde(tag = "layer", content = "name", rename_all = "snake_case")]
16pub enum Layer {
17 CatalogDefaults,
19 RevisionManifest,
21 Placement,
23 ClientEnv,
25 WorkspaceVars,
27 EnvironmentVars,
29 RevisionVars,
31 Source(String),
33 InstanceOverrides,
35}
36
37#[derive(Clone, PartialEq, Eq)]
39pub enum EnvValue {
40 Plain(String),
42 Secret(SecretValue),
44}
45
46impl EnvValue {
47 pub fn expose(&self) -> &str {
49 match self {
50 EnvValue::Plain(v) => v,
51 EnvValue::Secret(s) => s.expose(),
52 }
53 }
54
55 pub fn is_secret(&self) -> bool {
57 matches!(self, EnvValue::Secret(_))
58 }
59}
60
61impl fmt::Debug for EnvValue {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 match self {
64 EnvValue::Plain(v) => f.debug_tuple("Plain").field(v).finish(),
65 EnvValue::Secret(_) => f.write_str("Secret(<redacted>)"),
66 }
67 }
68}
69
70#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct Resolved {
74 pub value: EnvValue,
76 pub provenance: Layer,
78 pub shadowed: Vec<Layer>,
80}
81
82#[derive(Clone, Debug)]
84pub enum LayerValue {
85 Plain(String),
87 SecretRef(olai_stack_state::SecretRef),
89 Secret(SecretValue),
91}
92
93#[derive(Clone, Debug, Default)]
96pub struct EnvLayers {
97 layers: BTreeMap<Layer, BTreeMap<String, LayerValue>>,
98 source_order: Vec<String>,
100}
101
102fn plain(map: &BTreeMap<String, String>) -> BTreeMap<String, LayerValue> {
103 map.iter()
104 .map(|(k, v)| (k.clone(), LayerValue::Plain(v.clone())))
105 .collect()
106}
107
108fn var_set(set: &VarSet) -> BTreeMap<String, LayerValue> {
109 set.0
110 .iter()
111 .map(|(k, v)| {
112 let v = match v {
113 VarValue::Literal(l) => LayerValue::Plain(l.clone()),
114 VarValue::Secret(r) => LayerValue::SecretRef(r.clone()),
115 };
116 (k.clone(), v)
117 })
118 .collect()
119}
120
121impl EnvLayers {
122 pub fn new() -> Self {
124 Self::default()
125 }
126
127 pub fn with_plain(mut self, layer: Layer, vars: &BTreeMap<String, String>) -> Self {
129 self.layers.entry(layer).or_default().extend(plain(vars));
130 self
131 }
132
133 pub fn with_secrets(mut self, layer: Layer, vars: &BTreeMap<String, String>) -> Self {
135 self.layers.entry(layer).or_default().extend(
136 vars.iter()
137 .map(|(k, v)| (k.clone(), LayerValue::Secret(SecretValue::new(v.clone())))),
138 );
139 self
140 }
141
142 pub fn with_vars(mut self, layer: Layer, vars: &VarSet) -> Self {
144 self.layers.entry(layer).or_default().extend(var_set(vars));
145 self
146 }
147
148 pub fn with_overlay(mut self, source: &str, overlay: &Overlay) -> Self {
150 let mut vars = plain(&overlay.vars);
151 vars.extend(plain(&overlay.endpoints));
152 vars.extend(
153 overlay
154 .credentials
155 .iter()
156 .map(|(k, v)| (k.clone(), LayerValue::Secret(v.clone()))),
157 );
158 self.layers
159 .entry(Layer::Source(source.to_owned()))
160 .or_default()
161 .extend(vars);
162 if !self.source_order.iter().any(|s| s == source) {
163 self.source_order.push(source.to_owned());
164 }
165 self
166 }
167
168 fn ordered(&self) -> Vec<(&Layer, &BTreeMap<String, LayerValue>)> {
169 let rank = |layer: &Layer| match layer {
170 Layer::Source(name) => (
171 7,
172 self.source_order
173 .iter()
174 .position(|s| s == name)
175 .unwrap_or(usize::MAX),
176 ),
177 Layer::InstanceOverrides => (8, 0),
178 other => (
179 [
180 Layer::CatalogDefaults,
181 Layer::RevisionManifest,
182 Layer::Placement,
183 Layer::ClientEnv,
184 Layer::WorkspaceVars,
185 Layer::EnvironmentVars,
186 Layer::RevisionVars,
187 ]
188 .iter()
189 .position(|l| l == other)
190 .unwrap_or(0),
191 0,
192 ),
193 };
194 let mut layers: Vec<_> = self.layers.iter().collect();
195 layers.sort_by_key(|(layer, _)| rank(layer));
196 layers
197 }
198
199 pub fn resolve(&self, providers: &SecretProviders) -> Result<EffectiveEnv, SecretError> {
204 let mut winners: BTreeMap<String, (Layer, &LayerValue, Vec<Layer>)> = BTreeMap::new();
205 let mut secret_keys: std::collections::BTreeSet<&str> = Default::default();
206 for (layer, vars) in self.ordered() {
207 for (key, value) in vars {
208 if !matches!(value, LayerValue::Plain(_)) {
209 secret_keys.insert(key);
210 }
211 match winners.get_mut(key) {
212 Some((winner, _, shadowed)) if *winner == Layer::Placement => {
213 shadowed.push(layer.clone());
214 }
215 Some((winner, current, shadowed)) => {
216 shadowed.push(std::mem::replace(winner, layer.clone()));
217 *current = value;
218 }
219 None => {
220 winners.insert(key.clone(), (layer.clone(), value, Vec::new()));
221 }
222 }
223 }
224 }
225 let mut vars = BTreeMap::new();
227 for (key, (provenance, value, shadowed)) in winners {
228 let value = match value {
229 LayerValue::Plain(v) if secret_keys.contains(key.as_str()) => {
230 EnvValue::Secret(SecretValue::new(v.clone()))
231 }
232 LayerValue::Plain(v) => EnvValue::Plain(v.clone()),
233 LayerValue::Secret(s) => EnvValue::Secret(s.clone()),
234 LayerValue::SecretRef(r) => EnvValue::Secret(providers.resolve(r)?),
235 };
236 vars.insert(
237 key,
238 Resolved {
239 value,
240 provenance,
241 shadowed,
242 },
243 );
244 }
245 Ok(EffectiveEnv { vars })
246 }
247}
248
249#[derive(Clone, Debug, Default, PartialEq, Eq)]
253pub struct EffectiveEnv {
254 vars: BTreeMap<String, Resolved>,
255}
256
257#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
259pub struct MaskedVar {
260 pub value: String,
262 pub secret: bool,
264 pub provenance: Layer,
266 #[serde(skip_serializing_if = "Vec::is_empty")]
268 pub shadowed: Vec<Layer>,
269}
270
271pub const MASK: &str = "••••••";
273
274impl EffectiveEnv {
275 pub fn vars(&self) -> &BTreeMap<String, Resolved> {
277 &self.vars
278 }
279
280 pub fn get(&self, key: &str) -> Option<&EnvValue> {
282 self.vars.get(key).map(|r| &r.value)
283 }
284
285 pub fn masked(&self) -> BTreeMap<String, MaskedVar> {
287 self.vars
288 .iter()
289 .map(|(k, r)| {
290 let secret = r.value.is_secret();
291 let value = if secret {
292 MASK.to_owned()
293 } else {
294 r.value.expose().to_owned()
295 };
296 (
297 k.clone(),
298 MaskedVar {
299 value,
300 secret,
301 provenance: r.provenance.clone(),
302 shadowed: r.shadowed.clone(),
303 },
304 )
305 })
306 .collect()
307 }
308
309 pub fn launch_vars(&self) -> BTreeMap<String, String> {
311 self.vars
312 .iter()
313 .map(|(k, r)| (k.clone(), r.value.expose().to_owned()))
314 .collect()
315 }
316}