1use async_timing_util::unix_timestamp_ms;
2use derive_variants::{EnumVariants, ExtractVariant};
3use serde::{Deserialize, Serialize};
4use strum::{AsRefStr, Display, EnumString};
5use typeshare::typeshare;
6
7use crate::entities::{
8 all_logs_success, monitor_timestamp, MongoId, Operation, I64,
9};
10
11use super::{
12 alerter::Alerter, build::Build, builder::Builder,
13 deployment::Deployment, procedure::Procedure, repo::Repo,
14 server::Server, server_template::ServerTemplate, stack::Stack,
15 sync::ResourceSync, Version,
16};
17
18#[typeshare]
20#[derive(Serialize, Deserialize, Debug, Clone, Default)]
21#[cfg_attr(
22 feature = "mongo",
23 derive(mongo_indexed::derive::MongoIndexed)
24)]
25#[cfg_attr(feature = "mongo", doc_index({ "target.type": 1 }))]
26#[cfg_attr(feature = "mongo", sparse_doc_index({ "target.id": 1 }))]
27pub struct Update {
28 #[serde(
32 default,
33 rename = "_id",
34 skip_serializing_if = "String::is_empty",
35 with = "bson::serde_helpers::hex_string_as_object_id"
36 )]
37 pub id: MongoId,
38
39 #[cfg_attr(feature = "mongo", index)]
41 pub operation: Operation,
42
43 #[cfg_attr(feature = "mongo", index)]
45 pub start_ts: I64,
46
47 #[cfg_attr(feature = "mongo", index)]
49 pub success: bool,
50
51 #[cfg_attr(feature = "mongo", index)]
58 pub operator: String,
59
60 pub target: ResourceTarget,
62 pub logs: Vec<Log>,
64 pub end_ts: Option<I64>,
66 #[cfg_attr(feature = "mongo", index)]
71 pub status: UpdateStatus,
72 #[serde(default, skip_serializing_if = "Version::is_none")]
74 pub version: Version,
75 #[serde(default, skip_serializing_if = "String::is_empty")]
77 pub commit_hash: String,
78 #[serde(default, skip_serializing_if = "String::is_empty")]
80 pub other_data: String,
81}
82
83impl Update {
84 pub fn push_simple_log(
85 &mut self,
86 stage: &str,
87 msg: impl Into<String>,
88 ) {
89 self.logs.push(Log::simple(stage, msg.into()));
90 }
91
92 pub fn push_error_log(
93 &mut self,
94 stage: &str,
95 msg: impl Into<String>,
96 ) {
97 self.logs.push(Log::error(stage, msg.into()));
98 }
99
100 pub fn in_progress(&mut self) {
101 self.status = UpdateStatus::InProgress;
102 }
103
104 pub fn finalize(&mut self) {
105 self.success = all_logs_success(&self.logs);
106 self.end_ts = Some(monitor_timestamp());
107 self.status = UpdateStatus::Complete;
108 }
109}
110
111#[typeshare]
113#[derive(Serialize, Deserialize, Debug, Clone)]
114pub struct UpdateListItem {
115 pub id: String,
117 pub operation: Operation,
119 pub start_ts: I64,
121 pub success: bool,
123 pub username: String,
125 pub operator: String,
132 pub target: ResourceTarget,
134 pub status: UpdateStatus,
139 #[serde(default, skip_serializing_if = "Version::is_none")]
141 pub version: Version,
142 #[serde(default, skip_serializing_if = "String::is_empty")]
144 pub other_data: String,
145}
146
147#[typeshare]
149#[derive(Serialize, Deserialize, Debug, Clone, Default)]
150pub struct Log {
151 pub stage: String,
153 pub command: String,
155 pub stdout: String,
157 pub stderr: String,
159 pub success: bool,
161 pub start_ts: I64,
163 pub end_ts: I64,
165}
166
167impl Log {
168 pub fn simple(stage: &str, msg: String) -> Log {
169 let ts = unix_timestamp_ms() as i64;
170 Log {
171 stage: stage.to_string(),
172 stdout: msg,
173 success: true,
174 start_ts: ts,
175 end_ts: ts,
176 ..Default::default()
177 }
178 }
179
180 pub fn error(stage: &str, msg: String) -> Log {
181 let ts = unix_timestamp_ms() as i64;
182 Log {
183 stage: stage.to_string(),
184 stderr: msg,
185 start_ts: ts,
186 end_ts: ts,
187 success: false,
188 ..Default::default()
189 }
190 }
191
192 pub fn combined(&self) -> String {
194 match (self.stdout.is_empty(), self.stderr.is_empty()) {
195 (true, true) => {
196 format!("stdout: {}\n\nstderr: {}", self.stdout, self.stderr)
197 }
198 (true, false) => self.stdout.to_string(),
199 (false, true) => self.stderr.to_string(),
200 (false, false) => String::from("No log"),
201 }
202 }
203}
204
205#[typeshare]
207#[derive(
208 Debug,
209 Clone,
210 PartialEq,
211 Eq,
212 Hash,
213 Serialize,
214 Deserialize,
215 EnumVariants,
216)]
217#[variant_derive(
218 Debug,
219 Clone,
220 Copy,
221 PartialEq,
222 Eq,
223 PartialOrd,
224 Ord,
225 Hash,
226 Serialize,
227 Deserialize,
228 Display,
229 EnumString,
230 AsRefStr
231)]
232#[serde(tag = "type", content = "id")]
233pub enum ResourceTarget {
234 System(String),
235 Build(String),
236 Builder(String),
237 Deployment(String),
238 Server(String),
239 Repo(String),
240 Alerter(String),
241 Procedure(String),
242 ServerTemplate(String),
243 ResourceSync(String),
244 Stack(String),
245}
246
247impl ResourceTarget {
248 pub fn extract_variant_id(
249 &self,
250 ) -> (ResourceTargetVariant, &String) {
251 let id = match &self {
252 ResourceTarget::System(id) => id,
253 ResourceTarget::Build(id) => id,
254 ResourceTarget::Builder(id) => id,
255 ResourceTarget::Deployment(id) => id,
256 ResourceTarget::Server(id) => id,
257 ResourceTarget::Repo(id) => id,
258 ResourceTarget::Alerter(id) => id,
259 ResourceTarget::Procedure(id) => id,
260 ResourceTarget::ServerTemplate(id) => id,
261 ResourceTarget::ResourceSync(id) => id,
262 ResourceTarget::Stack(id) => id,
263 };
264 (self.extract_variant(), id)
265 }
266
267 pub fn system() -> ResourceTarget {
268 Self::System("system".to_string())
269 }
270}
271
272impl Default for ResourceTarget {
273 fn default() -> Self {
274 ResourceTarget::system()
275 }
276}
277
278impl From<&Build> for ResourceTarget {
279 fn from(build: &Build) -> Self {
280 Self::Build(build.id.clone())
281 }
282}
283
284impl From<&Deployment> for ResourceTarget {
285 fn from(deployment: &Deployment) -> Self {
286 Self::Deployment(deployment.id.clone())
287 }
288}
289
290impl From<&Server> for ResourceTarget {
291 fn from(server: &Server) -> Self {
292 Self::Server(server.id.clone())
293 }
294}
295
296impl From<&Repo> for ResourceTarget {
297 fn from(repo: &Repo) -> Self {
298 Self::Repo(repo.id.clone())
299 }
300}
301
302impl From<&Builder> for ResourceTarget {
303 fn from(builder: &Builder) -> Self {
304 Self::Builder(builder.id.clone())
305 }
306}
307
308impl From<&Alerter> for ResourceTarget {
309 fn from(alerter: &Alerter) -> Self {
310 Self::Alerter(alerter.id.clone())
311 }
312}
313
314impl From<&Procedure> for ResourceTarget {
315 fn from(procedure: &Procedure) -> Self {
316 Self::Procedure(procedure.id.clone())
317 }
318}
319
320impl From<&ServerTemplate> for ResourceTarget {
321 fn from(server_template: &ServerTemplate) -> Self {
322 Self::ServerTemplate(server_template.id.clone())
323 }
324}
325
326impl From<&ResourceSync> for ResourceTarget {
327 fn from(resource_sync: &ResourceSync) -> Self {
328 Self::ResourceSync(resource_sync.id.clone())
329 }
330}
331
332impl From<&Stack> for ResourceTarget {
333 fn from(resource_sync: &Stack) -> Self {
334 Self::Stack(resource_sync.id.clone())
335 }
336}
337
338#[typeshare]
340#[derive(
341 Serialize,
342 Deserialize,
343 Debug,
344 Display,
345 EnumString,
346 PartialEq,
347 Hash,
348 Eq,
349 Clone,
350 Copy,
351 Default,
352)]
353pub enum UpdateStatus {
354 Queued,
356 InProgress,
358 #[default]
360 Complete,
361}