1use std::collections::BTreeMap;
4use std::fmt;
5
6use olai_stack_topology::EnvManifest;
7use serde::{Deserialize, Serialize};
8use sha2::{Digest, Sha256};
9
10macro_rules! id {
11 ($(#[$doc:meta])* $name:ident) => {
12 $(#[$doc])*
13 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
14 #[serde(transparent)]
15 pub struct $name(pub String);
16
17 impl $name {
18 pub(crate) fn generate() -> Self {
19 $name(uuid::Uuid::new_v4().to_string())
20 }
21 }
22
23 impl fmt::Display for $name {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 f.write_str(&self.0)
26 }
27 }
28 };
29}
30
31id!(
32 WorkspaceId
34);
35id!(
36 EnvironmentId
38);
39id!(
40 InstanceId
42);
43
44#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
47pub struct SecretRef {
48 pub provider: String,
50 pub key: String,
52}
53
54#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "snake_case")]
57pub enum VarValue {
58 Literal(String),
60 Secret(SecretRef),
62}
63
64impl fmt::Debug for VarValue {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 VarValue::Literal(_) => f.write_str("Literal(<redacted>)"),
69 VarValue::Secret(r) => f.debug_tuple("Secret").field(r).finish(),
70 }
71 }
72}
73
74#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(transparent)]
77pub struct VarSet(pub BTreeMap<String, VarValue>);
78
79impl VarSet {
80 pub fn new() -> Self {
82 Self::default()
83 }
84
85 pub fn set(&mut self, name: impl Into<String>, value: VarValue) -> &mut Self {
87 self.0.insert(name.into(), value);
88 self
89 }
90
91 pub fn get(&self, name: &str) -> Option<&VarValue> {
93 self.0.get(name)
94 }
95
96 pub fn is_empty(&self) -> bool {
98 self.0.is_empty()
99 }
100}
101
102#[derive(Clone, Debug, PartialEq, Eq)]
105pub struct Workspace {
106 pub id: WorkspaceId,
108 pub name: String,
110 pub mesh: bool,
112 pub vars: VarSet,
114}
115
116#[derive(Clone, Debug, PartialEq, Eq)]
120pub struct Draft {
121 pub manifest: EnvManifest,
123 pub modules: BTreeMap<String, String>,
126 pub vars: VarSet,
128}
129
130impl Draft {
131 pub fn new(manifest: EnvManifest) -> Self {
133 Draft {
134 manifest,
135 modules: BTreeMap::new(),
136 vars: VarSet::new(),
137 }
138 }
139}
140
141#[derive(Clone, Debug, PartialEq, Eq)]
144pub struct Environment {
145 pub id: EnvironmentId,
147 pub workspace: WorkspaceId,
149 pub name: String,
151 pub draft: Draft,
153 pub vars: VarSet,
155 pub head: Option<u32>,
157}
158
159#[derive(Clone, Debug, PartialEq, Eq)]
163pub struct Revision {
164 pub(crate) environment: EnvironmentId,
165 pub(crate) number: u32,
166 pub(crate) manifest: EnvManifest,
167 pub(crate) manifest_toml: String,
168 pub(crate) modules: BTreeMap<String, String>,
169 pub(crate) vars: VarSet,
170 pub(crate) digest: String,
171}
172
173impl Revision {
174 pub fn environment(&self) -> &EnvironmentId {
176 &self.environment
177 }
178
179 pub fn number(&self) -> u32 {
181 self.number
182 }
183
184 pub fn manifest(&self) -> &EnvManifest {
186 &self.manifest
187 }
188
189 pub fn manifest_toml(&self) -> &str {
191 &self.manifest_toml
192 }
193
194 pub fn modules(&self) -> &BTreeMap<String, String> {
196 &self.modules
197 }
198
199 pub fn vars(&self) -> &VarSet {
201 &self.vars
202 }
203
204 pub fn digest(&self) -> &str {
206 &self.digest
207 }
208}
209
210pub(crate) fn digest(
212 manifest_toml: &str,
213 modules: &BTreeMap<String, String>,
214 vars: &VarSet,
215) -> Result<String, serde_json::Error> {
216 #[derive(Serialize)]
217 struct Content<'a> {
218 manifest: &'a str,
219 modules: &'a BTreeMap<String, String>,
220 vars: &'a VarSet,
221 }
222 let bytes = serde_json::to_vec(&Content {
223 manifest: manifest_toml,
224 modules,
225 vars,
226 })?;
227 Ok(hex::encode(Sha256::digest(bytes)))
228}
229
230#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
232#[serde(rename_all = "snake_case")]
233pub enum Desired {
234 Running,
236 Stopped,
238}
239
240impl Desired {
241 pub(crate) fn as_str(self) -> &'static str {
242 match self {
243 Desired::Running => "running",
244 Desired::Stopped => "stopped",
245 }
246 }
247
248 pub(crate) fn parse(s: &str) -> Self {
249 if s == "running" {
250 Desired::Running
251 } else {
252 Desired::Stopped
253 }
254 }
255}
256
257#[derive(Clone, Debug, PartialEq, Eq)]
261pub struct Instance {
262 pub id: InstanceId,
264 pub environment: EnvironmentId,
266 pub revision: u32,
268 pub desired: Desired,
270 pub overrides: VarSet,
272 pub sources: Vec<String>,
274 pub slot: Option<u16>,
276}
277
278#[derive(Clone, Debug, PartialEq, Eq)]
281pub struct SlotLease {
282 pub slot: u16,
284 pub instance: InstanceId,
286}