1use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7use super::{OnChange, SecretAs};
8use crate::flex;
9
10#[derive(Debug, Clone, Default, PartialEq)]
13pub struct Environment {
14 pub vars: BTreeMap<String, String>,
16 pub secrets: BTreeMap<String, String>,
18 pub files: BTreeMap<String, String>,
22 pub on_change: BTreeMap<String, OnChange>,
24}
25
26impl Environment {
27 pub fn is_empty(&self) -> bool {
28 self.vars.is_empty() && self.secrets.is_empty() && self.files.is_empty()
29 }
30
31 pub fn file_path(key: &str) -> String {
33 format!("/run/secrets/{key}")
34 }
35
36 pub fn file_vars(&self) -> impl Iterator<Item = (String, String)> + '_ {
38 self.files
39 .iter()
40 .map(|(var, key)| (format!("{var}_FILE"), Self::file_path(key)))
41 }
42}
43
44impl std::ops::Deref for Environment {
46 type Target = BTreeMap<String, String>;
47 fn deref(&self) -> &Self::Target {
48 &self.vars
49 }
50}
51
52impl std::ops::DerefMut for Environment {
53 fn deref_mut(&mut self) -> &mut Self::Target {
54 &mut self.vars
55 }
56}
57
58impl<'a> IntoIterator for &'a Environment {
59 type Item = (&'a String, &'a String);
60 type IntoIter = std::collections::btree_map::Iter<'a, String, String>;
61 fn into_iter(self) -> Self::IntoIter {
62 self.vars.iter()
63 }
64}
65
66impl From<BTreeMap<String, String>> for Environment {
67 fn from(vars: BTreeMap<String, String>) -> Self {
68 Environment {
69 vars,
70 secrets: BTreeMap::new(),
71 files: BTreeMap::new(),
72 on_change: BTreeMap::new(),
73 }
74 }
75}
76
77impl Serialize for Environment {
78 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
79 use serde::ser::SerializeMap;
80 let mut m = s.serialize_map(None)?;
81 let mut keys: Vec<&String> = self
82 .vars
83 .keys()
84 .chain(self.secrets.keys())
85 .chain(self.files.keys())
86 .collect();
87 keys.sort();
88 keys.dedup();
89 for k in keys {
90 let (sec, file) = match (self.vars.get(k), self.secrets.get(k), self.files.get(k)) {
91 (Some(v), _, _) => {
92 m.serialize_entry(k, v)?;
93 continue;
94 }
95 (None, Some(sec), _) => (sec, false),
96 (None, None, Some(sec)) => (sec, true),
97 (None, None, None) => continue,
98 };
99 let mut v = BTreeMap::from([("secret", sec.as_str())]);
100 if let Some(o) = self.on_change.get(k) {
101 v.insert("on_change", o.as_str());
102 }
103 if file {
106 v.insert("as", "file");
107 }
108 m.serialize_entry(k, &v)?
109 }
110 m.end()
111 }
112}
113
114impl<'de> Deserialize<'de> for Environment {
115 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
116 use serde::de::Error as _;
117 let mut env = Environment::default();
118 match flex::EnvMapOrList::deserialize(d)? {
119 flex::EnvMapOrList::Map(m) => {
120 for (k, v) in m {
121 match v {
122 flex::EnvValue::Scalar(v) => {
123 env.vars.insert(k, v.into_string());
124 }
125 flex::EnvValue::Secret { secret, .. } if secret.is_empty() => {
126 return Err(D::Error::custom(format!(
127 "environment {k}: secret needs a top-level secret's name"
128 )));
129 }
130 flex::EnvValue::Secret {
131 secret,
132 on_change,
133 delivery,
134 } => {
135 if let Some(o) = on_change {
136 env.on_change.insert(k.clone(), o);
137 }
138 match delivery.unwrap_or_default() {
139 SecretAs::Env => env.secrets.insert(k, secret),
140 SecretAs::File => env.files.insert(k, secret),
141 };
142 }
143 }
144 }
145 }
146 flex::EnvMapOrList::List(l) => {
147 for item in l {
148 let Some((k, v)) = item.split_once('=') else {
149 return Err(D::Error::custom(format!(
150 "environment entry {item:?} has no value: write {item}=VALUE"
151 )));
152 };
153 env.vars.insert(k.to_string(), v.to_string());
154 }
155 }
156 }
157 Ok(env)
158 }
159}