Skip to main content

olai_stack_runtime/
effective.rs

1//! [`EffectiveEnv`]: an instance's environment, from every layer, with provenance.
2
3use 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/// Where a variable's value comes from, lowest precedence first. A higher layer overrides a
13/// lower one, except over [`Placement`](Layer::Placement), whose keys nothing overrides.
14#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
15#[serde(tag = "layer", content = "name", rename_all = "snake_case")]
16pub enum Layer {
17    /// The catalog's defaults (e.g. the in-stack development credentials).
18    CatalogDefaults,
19    /// What the revision's manifest plans to (the plan's injected environment).
20    RevisionManifest,
21    /// The instance's placement: reserved keys such as the compose project.
22    Placement,
23    /// How a client reaches the stack (the plan's client environment).
24    ClientEnv,
25    /// The workspace's vars.
26    WorkspaceVars,
27    /// The environment's vars.
28    EnvironmentVars,
29    /// The revision's vars.
30    RevisionVars,
31    /// A source's overlay, by source name.
32    Source(String),
33    /// The instance's overrides.
34    InstanceOverrides,
35}
36
37/// One variable's value: plain, or a secret.
38#[derive(Clone, PartialEq, Eq)]
39pub enum EnvValue {
40    /// A plain value.
41    Plain(String),
42    /// A secret's value, masked wherever shown.
43    Secret(SecretValue),
44}
45
46impl EnvValue {
47    /// The value.
48    pub fn expose(&self) -> &str {
49        match self {
50            EnvValue::Plain(v) => v,
51            EnvValue::Secret(s) => s.expose(),
52        }
53    }
54
55    /// Whether it is a secret.
56    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/// A variable as resolved: its value, the layer it came from, and the lower layers it
71/// overrode (or, for a placement key, the layers that tried and were ignored).
72#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct Resolved {
74    /// The value.
75    pub value: EnvValue,
76    /// The layer that set it.
77    pub provenance: Layer,
78    /// The other layers that set it, lowest first.
79    pub shadowed: Vec<Layer>,
80}
81
82/// What one layer sets, before secrets are resolved.
83#[derive(Clone, Debug)]
84pub enum LayerValue {
85    /// A plain value.
86    Plain(String),
87    /// A value to resolve with a [`SecretProviders`].
88    SecretRef(olai_stack_state::SecretRef),
89    /// A secret value already resolved (a source's credential).
90    Secret(SecretValue),
91}
92
93/// The layers an [`EffectiveEnv`] is resolved from, each a map of variables. Build it with
94/// the `with_*` methods in any order; resolution applies [`Layer`] order.
95#[derive(Clone, Debug, Default)]
96pub struct EnvLayers {
97    layers: BTreeMap<Layer, BTreeMap<String, LayerValue>>,
98    /// The order sources apply in (their [`Layer::Source`] keys sort by name otherwise).
99    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    /// No layers.
123    pub fn new() -> Self {
124        Self::default()
125    }
126
127    /// Set a plain layer.
128    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    /// Set a layer of secret values (e.g. the catalog's default credentials).
134    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    /// Set a layer from a store [`VarSet`] (literals and secret references).
143    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    /// Add a source's overlay, after the sources added before it.
149    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    /// Resolve every variable: the highest layer setting it wins (a placement key can't be
200    /// overridden), then secret references are resolved with `providers`. Any reference that
201    /// can't be resolved is an error; nothing falls back. A key some layer holds as a secret
202    /// stays masked even where a higher layer sets it as a plain literal.
203    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        // Secrets last, once every layer has had its say.
226        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/// An instance's resolved environment: every variable with its value and provenance. The
250/// same resolution feeds what a UI shows ([`masked`](Self::masked)) and what the instance
251/// launches with ([`launch_vars`](Self::launch_vars)).
252#[derive(Clone, Debug, Default, PartialEq, Eq)]
253pub struct EffectiveEnv {
254    vars: BTreeMap<String, Resolved>,
255}
256
257/// One row of [`EffectiveEnv::masked`].
258#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
259pub struct MaskedVar {
260    /// The value, or `"••••••"` for a secret.
261    pub value: String,
262    /// Whether it is a secret.
263    pub secret: bool,
264    /// The layer that set it.
265    pub provenance: Layer,
266    /// The other layers that set it, lowest first.
267    #[serde(skip_serializing_if = "Vec::is_empty")]
268    pub shadowed: Vec<Layer>,
269}
270
271/// What a masked secret shows as.
272pub const MASK: &str = "••••••";
273
274impl EffectiveEnv {
275    /// Every variable, by name.
276    pub fn vars(&self) -> &BTreeMap<String, Resolved> {
277        &self.vars
278    }
279
280    /// The value of `key`.
281    pub fn get(&self, key: &str) -> Option<&EnvValue> {
282        self.vars.get(key).map(|r| &r.value)
283    }
284
285    /// Every variable with secrets masked, for showing.
286    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    /// Every variable's value, for the launch environment.
310    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}