use std::collections::BTreeMap;
use std::fmt;
use olai_stack_state::{VarSet, VarValue};
use serde::Serialize;
use crate::secrets::{SecretError, SecretProviders, SecretValue};
use crate::source::Overlay;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(tag = "layer", content = "name", rename_all = "snake_case")]
pub enum Layer {
CatalogDefaults,
RevisionManifest,
Placement,
ClientEnv,
WorkspaceVars,
EnvironmentVars,
RevisionVars,
Source(String),
InstanceOverrides,
}
#[derive(Clone, PartialEq, Eq)]
pub enum EnvValue {
Plain(String),
Secret(SecretValue),
}
impl EnvValue {
pub fn expose(&self) -> &str {
match self {
EnvValue::Plain(v) => v,
EnvValue::Secret(s) => s.expose(),
}
}
pub fn is_secret(&self) -> bool {
matches!(self, EnvValue::Secret(_))
}
}
impl fmt::Debug for EnvValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EnvValue::Plain(v) => f.debug_tuple("Plain").field(v).finish(),
EnvValue::Secret(_) => f.write_str("Secret(<redacted>)"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Resolved {
pub value: EnvValue,
pub provenance: Layer,
pub shadowed: Vec<Layer>,
}
#[derive(Clone, Debug)]
pub enum LayerValue {
Plain(String),
SecretRef(olai_stack_state::SecretRef),
Secret(SecretValue),
}
#[derive(Clone, Debug, Default)]
pub struct EnvLayers {
layers: BTreeMap<Layer, BTreeMap<String, LayerValue>>,
source_order: Vec<String>,
}
fn plain(map: &BTreeMap<String, String>) -> BTreeMap<String, LayerValue> {
map.iter()
.map(|(k, v)| (k.clone(), LayerValue::Plain(v.clone())))
.collect()
}
fn var_set(set: &VarSet) -> BTreeMap<String, LayerValue> {
set.0
.iter()
.map(|(k, v)| {
let v = match v {
VarValue::Literal(l) => LayerValue::Plain(l.clone()),
VarValue::Secret(r) => LayerValue::SecretRef(r.clone()),
};
(k.clone(), v)
})
.collect()
}
impl EnvLayers {
pub fn new() -> Self {
Self::default()
}
pub fn with_plain(mut self, layer: Layer, vars: &BTreeMap<String, String>) -> Self {
self.layers.entry(layer).or_default().extend(plain(vars));
self
}
pub fn with_secrets(mut self, layer: Layer, vars: &BTreeMap<String, String>) -> Self {
self.layers.entry(layer).or_default().extend(
vars.iter()
.map(|(k, v)| (k.clone(), LayerValue::Secret(SecretValue::new(v.clone())))),
);
self
}
pub fn with_vars(mut self, layer: Layer, vars: &VarSet) -> Self {
self.layers.entry(layer).or_default().extend(var_set(vars));
self
}
pub fn with_overlay(mut self, source: &str, overlay: &Overlay) -> Self {
let mut vars = plain(&overlay.vars);
vars.extend(plain(&overlay.endpoints));
vars.extend(
overlay
.credentials
.iter()
.map(|(k, v)| (k.clone(), LayerValue::Secret(v.clone()))),
);
self.layers
.entry(Layer::Source(source.to_owned()))
.or_default()
.extend(vars);
if !self.source_order.iter().any(|s| s == source) {
self.source_order.push(source.to_owned());
}
self
}
fn ordered(&self) -> Vec<(&Layer, &BTreeMap<String, LayerValue>)> {
let rank = |layer: &Layer| match layer {
Layer::Source(name) => (
7,
self.source_order
.iter()
.position(|s| s == name)
.unwrap_or(usize::MAX),
),
Layer::InstanceOverrides => (8, 0),
other => (
[
Layer::CatalogDefaults,
Layer::RevisionManifest,
Layer::Placement,
Layer::ClientEnv,
Layer::WorkspaceVars,
Layer::EnvironmentVars,
Layer::RevisionVars,
]
.iter()
.position(|l| l == other)
.unwrap_or(0),
0,
),
};
let mut layers: Vec<_> = self.layers.iter().collect();
layers.sort_by_key(|(layer, _)| rank(layer));
layers
}
pub fn resolve(&self, providers: &SecretProviders) -> Result<EffectiveEnv, SecretError> {
let mut winners: BTreeMap<String, (Layer, &LayerValue, Vec<Layer>)> = BTreeMap::new();
let mut secret_keys: std::collections::BTreeSet<&str> = Default::default();
for (layer, vars) in self.ordered() {
for (key, value) in vars {
if !matches!(value, LayerValue::Plain(_)) {
secret_keys.insert(key);
}
match winners.get_mut(key) {
Some((winner, _, shadowed)) if *winner == Layer::Placement => {
shadowed.push(layer.clone());
}
Some((winner, current, shadowed)) => {
shadowed.push(std::mem::replace(winner, layer.clone()));
*current = value;
}
None => {
winners.insert(key.clone(), (layer.clone(), value, Vec::new()));
}
}
}
}
let mut vars = BTreeMap::new();
for (key, (provenance, value, shadowed)) in winners {
let value = match value {
LayerValue::Plain(v) if secret_keys.contains(key.as_str()) => {
EnvValue::Secret(SecretValue::new(v.clone()))
}
LayerValue::Plain(v) => EnvValue::Plain(v.clone()),
LayerValue::Secret(s) => EnvValue::Secret(s.clone()),
LayerValue::SecretRef(r) => EnvValue::Secret(providers.resolve(r)?),
};
vars.insert(
key,
Resolved {
value,
provenance,
shadowed,
},
);
}
Ok(EffectiveEnv { vars })
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct EffectiveEnv {
vars: BTreeMap<String, Resolved>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct MaskedVar {
pub value: String,
pub secret: bool,
pub provenance: Layer,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub shadowed: Vec<Layer>,
}
pub const MASK: &str = "••••••";
impl EffectiveEnv {
pub fn vars(&self) -> &BTreeMap<String, Resolved> {
&self.vars
}
pub fn get(&self, key: &str) -> Option<&EnvValue> {
self.vars.get(key).map(|r| &r.value)
}
pub fn masked(&self) -> BTreeMap<String, MaskedVar> {
self.vars
.iter()
.map(|(k, r)| {
let secret = r.value.is_secret();
let value = if secret {
MASK.to_owned()
} else {
r.value.expose().to_owned()
};
(
k.clone(),
MaskedVar {
value,
secret,
provenance: r.provenance.clone(),
shadowed: r.shadowed.clone(),
},
)
})
.collect()
}
pub fn launch_vars(&self) -> BTreeMap<String, String> {
self.vars
.iter()
.map(|(k, r)| (k.clone(), r.value.expose().to_owned()))
.collect()
}
}