1use std::collections::BTreeMap;
4use std::fmt;
5use std::time::SystemTime;
6
7use olai_stack_topology::EnvManifest;
8use serde::{Deserialize, Serialize};
9use sha2::{Digest, Sha256};
10
11macro_rules! id {
12 ($(#[$doc:meta])* $name:ident) => {
13 $(#[$doc])*
14 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
15 #[serde(transparent)]
16 pub struct $name(pub String);
17
18 impl $name {
19 pub(crate) fn generate() -> Self {
20 $name(uuid::Uuid::new_v4().to_string())
21 }
22 }
23
24 impl fmt::Display for $name {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 f.write_str(&self.0)
27 }
28 }
29 };
30}
31
32id!(
33 WorkspaceId
35);
36id!(
37 EnvironmentId
39);
40id!(
41 InstanceId
43);
44
45#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
48pub struct SecretRef {
49 pub provider: String,
51 pub key: String,
53}
54
55#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "snake_case")]
58pub enum VarValue {
59 Literal(String),
61 Secret(SecretRef),
63}
64
65impl fmt::Debug for VarValue {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 match self {
68 VarValue::Literal(_) => f.write_str("Literal(<redacted>)"),
70 VarValue::Secret(r) => f.debug_tuple("Secret").field(r).finish(),
71 }
72 }
73}
74
75#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(transparent)]
78pub struct VarSet(pub BTreeMap<String, VarValue>);
79
80impl VarSet {
81 pub fn new() -> Self {
83 Self::default()
84 }
85
86 pub fn set(&mut self, name: impl Into<String>, value: VarValue) -> &mut Self {
88 self.0.insert(name.into(), value);
89 self
90 }
91
92 pub fn get(&self, name: &str) -> Option<&VarValue> {
94 self.0.get(name)
95 }
96
97 pub fn remove(&mut self, name: &str) -> Option<VarValue> {
99 self.0.remove(name)
100 }
101
102 pub fn is_empty(&self) -> bool {
104 self.0.is_empty()
105 }
106}
107
108#[derive(Clone, Debug, PartialEq, Eq)]
110pub enum VarScope {
111 Workspace(WorkspaceId),
113 Environment(EnvironmentId),
115 Draft(EnvironmentId),
117}
118
119#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
123pub struct SourceBinding {
124 pub name: String,
126 #[serde(default)]
128 pub config: serde_json::Value,
129}
130
131impl SourceBinding {
132 pub fn new(name: impl Into<String>) -> Self {
134 SourceBinding {
135 name: name.into(),
136 config: serde_json::Value::Null,
137 }
138 }
139
140 pub fn with_config(mut self, config: serde_json::Value) -> Self {
142 self.config = config;
143 self
144 }
145}
146
147#[derive(Clone, Debug, PartialEq, Eq)]
150pub struct Workspace {
151 pub id: WorkspaceId,
153 pub name: String,
155 pub mesh: bool,
157 pub vars: VarSet,
159 pub sources: BTreeMap<String, serde_json::Value>,
162}
163
164#[derive(Clone, Debug, PartialEq, Eq)]
168pub struct Draft {
169 pub manifest: EnvManifest,
171 pub modules: BTreeMap<String, String>,
174 pub vars: VarSet,
176}
177
178impl Draft {
179 pub fn new(manifest: EnvManifest) -> Self {
181 Draft {
182 manifest,
183 modules: BTreeMap::new(),
184 vars: VarSet::new(),
185 }
186 }
187}
188
189#[derive(Clone, Debug, PartialEq, Eq)]
192pub struct Environment {
193 pub id: EnvironmentId,
195 pub workspace: WorkspaceId,
197 pub name: String,
199 pub draft: Draft,
201 pub vars: VarSet,
203 pub head: Option<u32>,
205}
206
207#[derive(Clone, Debug, PartialEq, Eq)]
212pub struct Revision {
213 pub(crate) environment: EnvironmentId,
214 pub(crate) number: u32,
215 pub(crate) manifest: EnvManifest,
216 pub(crate) manifest_toml: String,
217 pub(crate) modules: BTreeMap<String, String>,
218 pub(crate) vars: VarSet,
219 pub(crate) digest: String,
220 pub(crate) message: Option<String>,
221 pub(crate) created_at: SystemTime,
222}
223
224impl Revision {
225 pub fn environment(&self) -> &EnvironmentId {
227 &self.environment
228 }
229
230 pub fn number(&self) -> u32 {
232 self.number
233 }
234
235 pub fn manifest(&self) -> &EnvManifest {
237 &self.manifest
238 }
239
240 pub fn manifest_toml(&self) -> &str {
242 &self.manifest_toml
243 }
244
245 pub fn modules(&self) -> &BTreeMap<String, String> {
247 &self.modules
248 }
249
250 pub fn vars(&self) -> &VarSet {
252 &self.vars
253 }
254
255 pub fn digest(&self) -> &str {
257 &self.digest
258 }
259
260 pub fn message(&self) -> Option<&str> {
263 self.message.as_deref()
264 }
265
266 pub fn created_at(&self) -> SystemTime {
268 self.created_at
269 }
270}
271
272pub(crate) fn digest(
274 manifest_toml: &str,
275 modules: &BTreeMap<String, String>,
276 vars: &VarSet,
277) -> Result<String, serde_json::Error> {
278 #[derive(Serialize)]
279 struct Content<'a> {
280 manifest: &'a str,
281 modules: &'a BTreeMap<String, String>,
282 vars: &'a VarSet,
283 }
284 let bytes = serde_json::to_vec(&Content {
285 manifest: manifest_toml,
286 modules,
287 vars,
288 })?;
289 Ok(hex::encode(Sha256::digest(bytes)))
290}
291
292#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
294#[serde(rename_all = "snake_case")]
295pub enum Desired {
296 Running,
298 Stopped,
300}
301
302impl Desired {
303 pub(crate) fn as_str(self) -> &'static str {
304 match self {
305 Desired::Running => "running",
306 Desired::Stopped => "stopped",
307 }
308 }
309
310 pub(crate) fn parse(s: &str) -> Self {
311 if s == "running" {
312 Desired::Running
313 } else {
314 Desired::Stopped
315 }
316 }
317}
318
319#[derive(Clone, Debug, PartialEq, Eq)]
323pub struct Instance {
324 pub id: InstanceId,
326 pub environment: EnvironmentId,
328 pub revision: u32,
330 pub desired: Desired,
332 pub overrides: VarSet,
334 pub sources: Vec<SourceBinding>,
336 pub slot: Option<u16>,
338}
339
340#[derive(Clone, Debug, PartialEq, Eq)]
343pub struct SlotLease {
344 pub slot: u16,
346 pub instance: InstanceId,
348}