monitor_client/entities/
update.rs

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/// Represents an action performed by Monitor.
19#[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  /// The Mongo ID of the update.
29  /// This field is de/serialized from/to JSON as
30  /// `{ "_id": { "$oid": "..." }, ...(rest of serialized Update) }`
31  #[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  /// The operation performed
40  #[cfg_attr(feature = "mongo", index)]
41  pub operation: Operation,
42
43  /// The time the operation started
44  #[cfg_attr(feature = "mongo", index)]
45  pub start_ts: I64,
46
47  /// Whether the operation was successful
48  #[cfg_attr(feature = "mongo", index)]
49  pub success: bool,
50
51  /// The user id that triggered the update.
52  ///
53  /// Also can take these values for operations triggered automatically:
54  /// - `Procedure`: The operation was triggered as part of a procedure run
55  /// - `Github`: The operation was triggered by a github webhook
56  /// - `Auto Redeploy`: The operation (always `Deploy`) was triggered by an attached build finishing.
57  #[cfg_attr(feature = "mongo", index)]
58  pub operator: String,
59
60  /// The target resource to which this update refers
61  pub target: ResourceTarget,
62  /// Logs produced as the operation is performed
63  pub logs: Vec<Log>,
64  /// The time the operation completed.
65  pub end_ts: Option<I64>,
66  /// The status of the update
67  /// - `Queued`
68  /// - `InProgress`
69  /// - `Complete`
70  #[cfg_attr(feature = "mongo", index)]
71  pub status: UpdateStatus,
72  /// An optional version on the update, ie build version or deployed version.
73  #[serde(default, skip_serializing_if = "Version::is_none")]
74  pub version: Version,
75  /// An optional commit hash associated with the update, ie cloned hash or deployed hash.
76  #[serde(default, skip_serializing_if = "String::is_empty")]
77  pub commit_hash: String,
78  /// Some unstructured, operation specific data. Not for general usage.
79  #[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/// Minimal representation of an action performed by Monitor.
112#[typeshare]
113#[derive(Serialize, Deserialize, Debug, Clone)]
114pub struct UpdateListItem {
115  /// The id of the update
116  pub id: String,
117  /// Which operation was run
118  pub operation: Operation,
119  /// The starting time of the operation
120  pub start_ts: I64,
121  /// Whether the operation was successful
122  pub success: bool,
123  /// The username of the user performing update
124  pub username: String,
125  /// The user id that triggered the update.
126  ///
127  /// Also can take these values for operations triggered automatically:
128  /// - `Procedure`: The operation was triggered as part of a procedure run
129  /// - `Github`: The operation was triggered by a github webhook
130  /// - `Auto Redeploy`: The operation (always `Deploy`) was triggered by an attached build finishing.
131  pub operator: String,
132  /// The target resource to which this update refers
133  pub target: ResourceTarget,
134  /// The status of the update
135  /// - `Queued`
136  /// - `InProgress`
137  /// - `Complete`
138  pub status: UpdateStatus,
139  /// An optional version on the update, ie build version or deployed version.
140  #[serde(default, skip_serializing_if = "Version::is_none")]
141  pub version: Version,
142  /// Some unstructured, operation specific data. Not for general usage.
143  #[serde(default, skip_serializing_if = "String::is_empty")]
144  pub other_data: String,
145}
146
147/// Represents the output of some command being run
148#[typeshare]
149#[derive(Serialize, Deserialize, Debug, Clone, Default)]
150pub struct Log {
151  /// A label for the log
152  pub stage: String,
153  /// The command which was executed
154  pub command: String,
155  /// The output of the command in the standard channel
156  pub stdout: String,
157  /// The output of the command in the error channel
158  pub stderr: String,
159  /// Whether the command run was successful
160  pub success: bool,
161  /// The start time of the command execution
162  pub start_ts: I64,
163  /// The end time of the command execution
164  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  /// Combines stdout / stderr into one log
193  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/// Used to reference a specific resource across all resource types
206#[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/// An update's status
339#[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  /// The run is in the system but hasn't started yet
355  Queued,
356  /// The run is currently running
357  InProgress,
358  /// The run is complete
359  #[default]
360  Complete,
361}