monitor_client/entities/
mod.rs

1use std::str::FromStr;
2
3use anyhow::Context;
4use async_timing_util::unix_timestamp_ms;
5use build::StandardRegistryConfig;
6use clap::Parser;
7use config::core::AwsEcrConfig;
8use derive_empty_traits::EmptyTraits;
9use serde::{
10  de::{
11    value::{MapAccessDeserializer, SeqAccessDeserializer},
12    Visitor,
13  },
14  Deserialize, Deserializer, Serialize,
15};
16use serror::Serror;
17use strum::{AsRefStr, Display, EnumString};
18use typeshare::typeshare;
19
20/// Subtypes of [Alert][alert::Alert].
21pub mod alert;
22/// Subtypes of [Alerter][alerter::Alerter].
23pub mod alerter;
24/// Subtypes of [ApiKey][api_key::ApiKey].
25pub mod api_key;
26/// Subtypes of [Build][build::Build].
27pub mod build;
28/// Subtypes of [Builder][builder::Builder].
29pub mod builder;
30/// [core config][config::core] and [periphery config][config::periphery]
31pub mod config;
32/// Subtypes of [Deployment][deployment::Deployment].
33pub mod deployment;
34/// Subtypes of [LogConfig][logger::LogConfig].
35pub mod logger;
36/// Subtypes of [Permission][permission::Permission].
37pub mod permission;
38/// Subtypes of [Procedure][procedure::Procedure].
39pub mod procedure;
40/// Subtypes of [ProviderAccount][provider::ProviderAccount]
41pub mod provider;
42/// Subtypes of [Repo][repo::Repo].
43pub mod repo;
44/// Subtypes of [Resource][resource::Resource].
45pub mod resource;
46/// Subtypes of [Server][server::Server].
47pub mod server;
48/// Subtypes of [ServerTemplate][server_template::ServerTemplate].
49pub mod server_template;
50/// Subtypes of [Stack][stack::Stack]
51pub mod stack;
52/// Subtypes of [ResourceSync][sync::ResourceSync]
53pub mod sync;
54/// Subtypes of [Tag][tag::Tag].
55pub mod tag;
56/// Subtypes of [ResourcesToml][toml::ResourcesToml].
57pub mod toml;
58/// Subtypes of [Update][update::Update].
59pub mod update;
60/// Subtypes of [User][user::User].
61pub mod user;
62/// Subtypes of [UserGroup][user_group::UserGroup].
63pub mod user_group;
64/// Subtypes of [Variable][variable::Variable]
65pub mod variable;
66
67#[typeshare(serialized_as = "number")]
68pub type I64 = i64;
69#[typeshare(serialized_as = "number")]
70pub type U64 = u64;
71#[typeshare(serialized_as = "any")]
72pub type MongoDocument = bson::Document;
73#[typeshare(serialized_as = "any")]
74pub type JsonValue = serde_json::Value;
75#[typeshare(serialized_as = "MongoIdObj")]
76pub type MongoId = String;
77#[typeshare(serialized_as = "__Serror")]
78pub type _Serror = Serror;
79
80/// Represents an empty json object: `{}`
81#[typeshare]
82#[derive(
83  Debug,
84  Clone,
85  Default,
86  PartialEq,
87  Serialize,
88  Deserialize,
89  Parser,
90  EmptyTraits,
91)]
92pub struct NoData {}
93
94pub trait MergePartial: Sized {
95  type Partial;
96  fn merge_partial(self, partial: Self::Partial) -> Self;
97}
98
99pub fn all_logs_success(logs: &[update::Log]) -> bool {
100  for log in logs {
101    if !log.success {
102      return false;
103    }
104  }
105  true
106}
107
108pub fn optional_string(string: &str) -> Option<String> {
109  if string.is_empty() {
110    None
111  } else {
112    Some(string.to_string())
113  }
114}
115
116pub fn get_image_name(
117  build::Build {
118    name,
119    config:
120      build::BuildConfig {
121        image_name,
122        image_registry,
123        ..
124      },
125    ..
126  }: &build::Build,
127  aws_ecr: impl FnOnce(&String) -> Option<AwsEcrConfig>,
128) -> anyhow::Result<String> {
129  let name = if image_name.is_empty() {
130    to_monitor_name(name)
131  } else {
132    to_monitor_name(image_name)
133  };
134  let name = match image_registry {
135    build::ImageRegistry::None(_) => name,
136    build::ImageRegistry::AwsEcr(label) => {
137      let AwsEcrConfig {
138        region, account_id, ..
139      } = aws_ecr(label).with_context(|| {
140        format!("didn't find aws ecr config for registry {label}")
141      })?;
142      format!("{account_id}.dkr.ecr.{region}.amazonaws.com/{name}")
143    }
144    build::ImageRegistry::Standard(StandardRegistryConfig {
145      domain,
146      account,
147      organization,
148    }) => {
149      if !organization.is_empty() {
150        let org = organization.to_lowercase();
151        format!("{domain}/{org}/{name}")
152      } else if !account.is_empty() {
153        format!("{domain}/{account}/{name}")
154      } else {
155        name
156      }
157    }
158  };
159  Ok(name)
160}
161
162pub fn to_monitor_name(name: &str) -> String {
163  name.to_lowercase().replace([' ', '.'], "_")
164}
165
166pub fn monitor_timestamp() -> i64 {
167  unix_timestamp_ms() as i64
168}
169
170#[typeshare]
171#[derive(Serialize, Deserialize, Debug, Clone)]
172pub struct MongoIdObj {
173  #[serde(rename = "$oid")]
174  pub oid: String,
175}
176
177#[typeshare]
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct __Serror {
180  pub error: String,
181  pub trace: Vec<String>,
182}
183
184#[typeshare]
185#[derive(
186  Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq,
187)]
188pub struct SystemCommand {
189  #[serde(default)]
190  pub path: String,
191  #[serde(default)]
192  pub command: String,
193}
194
195impl SystemCommand {
196  pub fn command(&self) -> Option<String> {
197    if self.is_none() {
198      None
199    } else {
200      Some(format!("cd {} && {}", self.path, self.command))
201    }
202  }
203
204  pub fn into_option(self) -> Option<SystemCommand> {
205    if self.is_none() {
206      None
207    } else {
208      Some(self)
209    }
210  }
211
212  pub fn is_none(&self) -> bool {
213    self.path.is_empty() || self.command.is_empty()
214  }
215}
216
217#[typeshare]
218#[derive(Serialize, Debug, Clone, Copy, Default, PartialEq)]
219pub struct Version {
220  pub major: i32,
221  pub minor: i32,
222  pub patch: i32,
223}
224
225impl<'de> Deserialize<'de> for Version {
226  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
227  where
228    D: serde::Deserializer<'de>,
229  {
230    #[derive(Deserialize)]
231    struct VersionInner {
232      major: i32,
233      minor: i32,
234      patch: i32,
235    }
236
237    impl From<VersionInner> for Version {
238      fn from(
239        VersionInner {
240          major,
241          minor,
242          patch,
243        }: VersionInner,
244      ) -> Self {
245        Version {
246          major,
247          minor,
248          patch,
249        }
250      }
251    }
252
253    struct VersionVisitor;
254
255    impl<'de> Visitor<'de> for VersionVisitor {
256      type Value = Version;
257      fn expecting(
258        &self,
259        formatter: &mut std::fmt::Formatter,
260      ) -> std::fmt::Result {
261        write!(
262          formatter,
263          "version string or object | example: '0.2.4' or {{ \"major\": 0, \"minor\": 2, \"patch\": 4, }}"
264        )
265      }
266
267      fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
268      where
269        E: serde::de::Error,
270      {
271        v.try_into()
272          .map_err(|e| serde::de::Error::custom(format!("{e:#}")))
273      }
274
275      fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
276      where
277        A: serde::de::MapAccess<'de>,
278      {
279        Ok(
280          VersionInner::deserialize(MapAccessDeserializer::new(map))?
281            .into(),
282        )
283      }
284    }
285
286    deserializer.deserialize_any(VersionVisitor)
287  }
288}
289
290impl std::fmt::Display for Version {
291  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292    f.write_fmt(format_args!(
293      "{}.{}.{}",
294      self.major, self.minor, self.patch
295    ))
296  }
297}
298
299impl TryFrom<&str> for Version {
300  type Error = anyhow::Error;
301
302  fn try_from(value: &str) -> Result<Self, Self::Error> {
303    let mut split = value.split('.');
304    let major = split
305      .next()
306      .context("must provide at least major version")?
307      .parse::<i32>()
308      .context("major version must be integer")?;
309    let minor = split
310      .next()
311      .map(|minor| minor.parse::<i32>())
312      .transpose()
313      .context("minor version must be integer")?
314      .unwrap_or_default();
315    let patch = split
316      .next()
317      .map(|patch| patch.parse::<i32>())
318      .transpose()
319      .context("patch version must be integer")?
320      .unwrap_or_default();
321    Ok(Version {
322      major,
323      minor,
324      patch,
325    })
326  }
327}
328
329impl Version {
330  pub fn increment(&mut self) {
331    self.patch += 1;
332  }
333
334  pub fn is_none(&self) -> bool {
335    self.major == 0 && self.minor == 0 && self.patch == 0
336  }
337}
338
339#[typeshare]
340#[derive(
341  Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize,
342)]
343pub struct EnvironmentVar {
344  pub variable: String,
345  pub value: String,
346}
347
348pub fn environment_vars_to_string(vars: &[EnvironmentVar]) -> String {
349  vars
350    .iter()
351    .map(|EnvironmentVar { variable, value }| {
352      format!("{variable}={value}")
353    })
354    .collect::<Vec<_>>()
355    .join("\n")
356}
357
358pub fn environment_vars_from_str(
359  value: &str,
360) -> anyhow::Result<Vec<EnvironmentVar>> {
361  let trimmed = value.trim();
362  if trimmed.is_empty() {
363    return Ok(Vec::new());
364  }
365  let res = trimmed
366    .split('\n')
367    .map(|line| line.trim())
368    .enumerate()
369    .filter(|(_, line)| {
370      !line.is_empty()
371        && !line.starts_with('#')
372        && !line.starts_with("//")
373    })
374    .map(|(i, line)| {
375      let (variable, value) = line
376        .split_once('=')
377        .with_context(|| format!("line {i} missing assignment (=)"))
378        .map(|(variable, value)| {
379          (variable.trim().to_string(), value.trim().to_string())
380        })?;
381      anyhow::Ok(EnvironmentVar { variable, value })
382    })
383    .collect::<anyhow::Result<Vec<_>>>()?;
384  Ok(res)
385}
386
387pub fn env_vars_deserializer<'de, D>(
388  deserializer: D,
389) -> Result<Vec<EnvironmentVar>, D::Error>
390where
391  D: Deserializer<'de>,
392{
393  deserializer.deserialize_any(EnvironmentVarVisitor)
394}
395
396pub fn option_env_vars_deserializer<'de, D>(
397  deserializer: D,
398) -> Result<Option<Vec<EnvironmentVar>>, D::Error>
399where
400  D: Deserializer<'de>,
401{
402  deserializer.deserialize_any(OptionEnvVarVisitor)
403}
404
405struct EnvironmentVarVisitor;
406
407impl<'de> Visitor<'de> for EnvironmentVarVisitor {
408  type Value = Vec<EnvironmentVar>;
409
410  fn expecting(
411    &self,
412    formatter: &mut std::fmt::Formatter,
413  ) -> std::fmt::Result {
414    write!(formatter, "string or Vec<EnvironmentVar>")
415  }
416
417  fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
418  where
419    E: serde::de::Error,
420  {
421    environment_vars_from_str(v)
422      .map_err(|e| serde::de::Error::custom(format!("{e:#}")))
423  }
424
425  fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
426  where
427    A: serde::de::SeqAccess<'de>,
428  {
429    #[derive(Deserialize)]
430    struct EnvironmentVarInner {
431      variable: String,
432      value: String,
433    }
434
435    impl From<EnvironmentVarInner> for EnvironmentVar {
436      fn from(value: EnvironmentVarInner) -> Self {
437        Self {
438          variable: value.variable,
439          value: value.value,
440        }
441      }
442    }
443
444    let res = Vec::<EnvironmentVarInner>::deserialize(
445      SeqAccessDeserializer::new(seq),
446    )?
447    .into_iter()
448    .map(Into::into)
449    .collect();
450    Ok(res)
451  }
452}
453
454struct OptionEnvVarVisitor;
455
456impl<'de> Visitor<'de> for OptionEnvVarVisitor {
457  type Value = Option<Vec<EnvironmentVar>>;
458
459  fn expecting(
460    &self,
461    formatter: &mut std::fmt::Formatter,
462  ) -> std::fmt::Result {
463    write!(formatter, "null or string or Vec<EnvironmentVar>")
464  }
465
466  fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
467  where
468    E: serde::de::Error,
469  {
470    EnvironmentVarVisitor.visit_str(v).map(Some)
471  }
472
473  fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
474  where
475    A: serde::de::SeqAccess<'de>,
476  {
477    EnvironmentVarVisitor.visit_seq(seq).map(Some)
478  }
479
480  fn visit_none<E>(self) -> Result<Self::Value, E>
481  where
482    E: serde::de::Error,
483  {
484    Ok(None)
485  }
486
487  fn visit_unit<E>(self) -> Result<Self::Value, E>
488  where
489    E: serde::de::Error,
490  {
491    Ok(None)
492  }
493}
494
495#[typeshare]
496#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct LatestCommit {
498  pub hash: String,
499  pub message: String,
500}
501
502#[typeshare]
503#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
504pub struct CloneArgs {
505  /// Resource name (eg Build name, Repo name)
506  pub name: String,
507  /// Git provider domain. Default: `github.com`
508  pub provider: Option<String>,
509  /// Use https (vs http).
510  pub https: bool,
511  /// Full repo identifier. <namespace>/<repo_name>
512  pub repo: Option<String>,
513  /// Git Branch. Default: `main`
514  pub branch: Option<String>,
515  /// Specific commit hash. Optional
516  pub commit: Option<String>,
517  /// The clone destination path
518  pub destination: Option<String>,
519  /// Command to run after the repo has been cloned
520  pub on_clone: Option<SystemCommand>,
521  /// Command to run after the repo has been pulled
522  pub on_pull: Option<SystemCommand>,
523  /// Configure the account used to access repo (if private)
524  pub account: Option<String>,
525}
526
527impl From<&self::build::Build> for CloneArgs {
528  fn from(build: &self::build::Build) -> CloneArgs {
529    CloneArgs {
530      name: build.name.clone(),
531      repo: optional_string(&build.config.repo),
532      branch: optional_string(&build.config.branch),
533      commit: optional_string(&build.config.commit),
534      destination: None,
535      on_clone: build.config.pre_build.clone().into_option(),
536      on_pull: None,
537      provider: optional_string(&build.config.git_provider),
538      https: build.config.git_https,
539      account: optional_string(&build.config.git_account),
540    }
541  }
542}
543
544impl From<&self::repo::Repo> for CloneArgs {
545  fn from(repo: &self::repo::Repo) -> CloneArgs {
546    CloneArgs {
547      name: repo.name.clone(),
548      repo: optional_string(&repo.config.repo),
549      branch: optional_string(&repo.config.branch),
550      commit: optional_string(&repo.config.commit),
551      destination: optional_string(&repo.config.path),
552      on_clone: repo.config.on_clone.clone().into_option(),
553      on_pull: repo.config.on_pull.clone().into_option(),
554      provider: optional_string(&repo.config.git_provider),
555      https: repo.config.git_https,
556      account: optional_string(&repo.config.git_account),
557    }
558  }
559}
560
561impl From<&self::sync::ResourceSync> for CloneArgs {
562  fn from(sync: &self::sync::ResourceSync) -> Self {
563    CloneArgs {
564      name: sync.name.clone(),
565      repo: optional_string(&sync.config.repo),
566      branch: optional_string(&sync.config.branch),
567      commit: optional_string(&sync.config.commit),
568      destination: None,
569      on_clone: None,
570      on_pull: None,
571      provider: optional_string(&sync.config.git_provider),
572      https: sync.config.git_https,
573      account: optional_string(&sync.config.git_account),
574    }
575  }
576}
577
578impl From<&self::stack::Stack> for CloneArgs {
579  fn from(stack: &self::stack::Stack) -> Self {
580    CloneArgs {
581      name: stack.name.clone(),
582      repo: optional_string(&stack.config.repo),
583      branch: optional_string(&stack.config.branch),
584      commit: optional_string(&stack.config.commit),
585      destination: None,
586      on_clone: None,
587      on_pull: None,
588      provider: optional_string(&stack.config.git_provider),
589      https: stack.config.git_https,
590      account: optional_string(&stack.config.git_account),
591    }
592  }
593}
594
595#[typeshare]
596#[derive(
597  Serialize,
598  Deserialize,
599  Debug,
600  Display,
601  EnumString,
602  PartialEq,
603  Hash,
604  Eq,
605  Clone,
606  Copy,
607  Default,
608)]
609#[serde(rename_all = "snake_case")]
610#[strum(serialize_all = "snake_case")]
611pub enum Timelength {
612  #[serde(rename = "1-sec")]
613  #[strum(serialize = "1-sec")]
614  OneSecond,
615  #[serde(rename = "5-sec")]
616  #[strum(serialize = "5-sec")]
617  FiveSeconds,
618  #[serde(rename = "10-sec")]
619  #[strum(serialize = "10-sec")]
620  TenSeconds,
621  #[serde(rename = "15-sec")]
622  #[strum(serialize = "15-sec")]
623  FifteenSeconds,
624  #[serde(rename = "30-sec")]
625  #[strum(serialize = "30-sec")]
626  ThirtySeconds,
627  #[default]
628  #[serde(rename = "1-min")]
629  #[strum(serialize = "1-min")]
630  OneMinute,
631  #[serde(rename = "2-min")]
632  #[strum(serialize = "2-min")]
633  TwoMinutes,
634  #[serde(rename = "5-min")]
635  #[strum(serialize = "5-min")]
636  FiveMinutes,
637  #[serde(rename = "10-min")]
638  #[strum(serialize = "10-min")]
639  TenMinutes,
640  #[serde(rename = "15-min")]
641  #[strum(serialize = "15-min")]
642  FifteenMinutes,
643  #[serde(rename = "30-min")]
644  #[strum(serialize = "30-min")]
645  ThirtyMinutes,
646  #[serde(rename = "1-hr")]
647  #[strum(serialize = "1-hr")]
648  OneHour,
649  #[serde(rename = "2-hr")]
650  #[strum(serialize = "2-hr")]
651  TwoHours,
652  #[serde(rename = "6-hr")]
653  #[strum(serialize = "6-hr")]
654  SixHours,
655  #[serde(rename = "8-hr")]
656  #[strum(serialize = "8-hr")]
657  EightHours,
658  #[serde(rename = "12-hr")]
659  #[strum(serialize = "12-hr")]
660  TwelveHours,
661  #[serde(rename = "1-day")]
662  #[strum(serialize = "1-day")]
663  OneDay,
664  #[serde(rename = "3-day")]
665  #[strum(serialize = "3-day")]
666  ThreeDay,
667  #[serde(rename = "1-wk")]
668  #[strum(serialize = "1-wk")]
669  OneWeek,
670  #[serde(rename = "2-wk")]
671  #[strum(serialize = "2-wk")]
672  TwoWeeks,
673  #[serde(rename = "30-day")]
674  #[strum(serialize = "30-day")]
675  ThirtyDays,
676}
677
678impl TryInto<async_timing_util::Timelength> for Timelength {
679  type Error = anyhow::Error;
680  fn try_into(
681    self,
682  ) -> Result<async_timing_util::Timelength, Self::Error> {
683    async_timing_util::Timelength::from_str(&self.to_string())
684      .context("failed to parse timelength?")
685  }
686}
687
688#[typeshare]
689#[derive(
690  Debug,
691  Clone,
692  Copy,
693  PartialEq,
694  Eq,
695  Hash,
696  Serialize,
697  Deserialize,
698  Default,
699  Display,
700  EnumString,
701  AsRefStr,
702)]
703pub enum Operation {
704  // do nothing
705  #[default]
706  None,
707
708  // server
709  CreateServer,
710  UpdateServer,
711  DeleteServer,
712  RenameServer,
713  PruneImages,
714  PruneContainers,
715  PruneNetworks,
716  CreateNetwork,
717  DeleteNetwork,
718  StopAllContainers,
719
720  // build
721  CreateBuild,
722  UpdateBuild,
723  DeleteBuild,
724  RunBuild,
725  CancelBuild,
726
727  // builder
728  CreateBuilder,
729  UpdateBuilder,
730  DeleteBuilder,
731
732  // deployment
733  CreateDeployment,
734  UpdateDeployment,
735  DeleteDeployment,
736  Deploy,
737  StartContainer,
738  RestartContainer,
739  PauseContainer,
740  UnpauseContainer,
741  StopContainer,
742  RemoveContainer,
743  RenameDeployment,
744
745  // repo
746  CreateRepo,
747  UpdateRepo,
748  DeleteRepo,
749  CloneRepo,
750  PullRepo,
751  BuildRepo,
752  CancelRepoBuild,
753
754  // alerter
755  CreateAlerter,
756  UpdateAlerter,
757  DeleteAlerter,
758
759  // procedure
760  CreateProcedure,
761  UpdateProcedure,
762  DeleteProcedure,
763  RunProcedure,
764
765  // server template
766  CreateServerTemplate,
767  UpdateServerTemplate,
768  DeleteServerTemplate,
769  LaunchServer,
770
771  // sync
772  CreateResourceSync,
773  UpdateResourceSync,
774  DeleteResourceSync,
775  RunSync,
776
777  // stack
778  CreateStack,
779  UpdateStack,
780  RenameStack,
781  DeleteStack,
782  RefreshStackCache,
783  DeployStack,
784  StartStack,
785  RestartStack,
786  PauseStack,
787  UnpauseStack,
788  StopStack,
789  DestroyStack,
790
791  // stack (service)
792  StartStackService,
793  RestartStackService,
794  PauseStackService,
795  UnpauseStackService,
796  StopStackService,
797
798  // variable
799  CreateVariable,
800  UpdateVariableValue,
801  DeleteVariable,
802
803  // git provider
804  CreateGitProviderAccount,
805  UpdateGitProviderAccount,
806  DeleteGitProviderAccount,
807
808  // docker registry
809  CreateDockerRegistryAccount,
810  UpdateDockerRegistryAccount,
811  DeleteDockerRegistryAccount,
812}
813
814#[typeshare]
815#[derive(
816  Serialize,
817  Deserialize,
818  Debug,
819  Default,
820  Display,
821  EnumString,
822  PartialEq,
823  Hash,
824  Eq,
825  Clone,
826  Copy,
827)]
828pub enum SearchCombinator {
829  #[default]
830  Or,
831  And,
832}