use std::{
path::{Path, PathBuf},
str::FromStr,
};
use anyhow::{Context, anyhow};
use async_timing_util::unix_timestamp_ms;
use clap::{Parser, ValueEnum};
use mogh_error::{AddStatusCodeError, Serror};
use rand::RngExt as _;
use reqwest::StatusCode;
use serde::{
Deserialize, Serialize,
de::{Visitor, value::MapAccessDeserializer},
};
use strum::{AsRefStr, Display, EnumDiscriminants, EnumString};
use typeshare::typeshare;
use crate::{
deserializers::file_contents_deserializer, entities::update::Log,
parsers::parse_key_value_list,
};
pub mod action;
pub mod alert;
pub mod alerter;
pub mod api_key;
pub mod build;
pub mod builder;
pub mod config;
pub mod deployment;
pub mod docker;
pub mod logger;
pub mod onboarding_key;
pub mod permission;
pub mod procedure;
pub mod provider;
pub mod repo;
pub mod resource;
pub mod schedule;
pub mod server;
pub mod stack;
pub mod stats;
pub mod swarm;
pub mod sync;
pub mod tag;
pub mod terminal;
pub mod toml;
pub mod update;
pub mod user;
pub mod user_group;
pub mod variable;
#[typeshare(serialized_as = "number")]
pub type I64 = i64;
#[typeshare(serialized_as = "number")]
pub type U64 = u64;
#[typeshare(serialized_as = "number")]
pub type Usize = usize;
#[typeshare(serialized_as = "any")]
pub type MongoDocument = bson::Document;
#[typeshare(serialized_as = "any")]
pub type JsonValue = serde_json::Value;
#[typeshare(serialized_as = "any")]
pub type JsonObject = serde_json::Map<String, serde_json::Value>;
#[typeshare(serialized_as = "MongoIdObj")]
pub type MongoId = String;
#[typeshare(serialized_as = "__Serror")]
pub type _Serror = Serror;
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize, Parser,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct NoData {}
pub trait MergePartial: Sized {
type Partial;
fn merge_partial(self, partial: Self::Partial) -> Self;
}
pub fn random_string(length: usize) -> String {
rand::rng()
.sample_iter(&rand::distr::Alphanumeric)
.take(length)
.map(char::from)
.collect()
}
pub fn random_bytes(length: usize) -> Vec<u8> {
rand::rng()
.sample_iter(&rand::distr::Alphanumeric)
.take(length)
.collect()
}
pub fn all_logs_success(logs: &[update::Log]) -> bool {
for log in logs {
if !log.success {
return false;
}
}
true
}
pub fn optional_string(string: impl Into<String>) -> Option<String> {
let string = string.into();
if string.is_empty() {
None
} else {
Some(string)
}
}
pub fn optional_str(str: &str) -> Option<&str> {
if str.is_empty() { None } else { Some(str) }
}
pub fn to_general_name(name: &str) -> String {
name.trim().replace('\n', "_").to_string()
}
pub fn to_path_compatible_name(name: &str) -> String {
name.trim().replace([' ', '\n'], "_").to_string()
}
pub fn to_container_compatible_name(name: &str) -> String {
name.trim().replace([' ', ',', '\n', '&'], "_").to_string()
}
pub fn to_docker_compatible_name(name: &str) -> String {
name
.to_lowercase()
.replace([' ', '.', ',', '\n', '&'], "_")
.trim()
.to_string()
}
pub fn komodo_timestamp() -> i64 {
unix_timestamp_ms() as i64
}
#[typeshare]
pub struct CreateApiKeyResponse {
pub key: String,
pub secret: String,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct MongoIdObj {
#[serde(rename = "$oid")]
pub oid: String,
}
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct __Serror {
pub error: String,
pub trace: Vec<String>,
}
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct SystemCommand {
#[serde(default)]
pub path: String,
#[serde(default, deserialize_with = "file_contents_deserializer")]
pub command: String,
}
impl SystemCommand {
pub fn command(&self) -> Option<String> {
if self.is_none() {
None
} else {
Some(format!("cd {} && {}", self.path, self.command))
}
}
pub fn into_option(self) -> Option<SystemCommand> {
if self.is_none() { None } else { Some(self) }
}
pub fn is_none(&self) -> bool {
self.command.is_empty()
}
}
#[typeshare]
#[derive(Serialize, Debug, Clone, Copy, Default, PartialEq)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct Version {
pub major: i32,
pub minor: i32,
pub patch: i32,
}
impl<'de> Deserialize<'de> for Version {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct VersionInner {
major: i32,
minor: i32,
patch: i32,
}
impl From<VersionInner> for Version {
fn from(
VersionInner {
major,
minor,
patch,
}: VersionInner,
) -> Self {
Version {
major,
minor,
patch,
}
}
}
struct VersionVisitor;
impl<'de> Visitor<'de> for VersionVisitor {
type Value = Version;
fn expecting(
&self,
formatter: &mut std::fmt::Formatter,
) -> std::fmt::Result {
write!(
formatter,
"version string or object | example: '0.2.4' or {{ \"major\": 0, \"minor\": 2, \"patch\": 4, }}"
)
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.try_into()
.map_err(|e| serde::de::Error::custom(format!("{e:#}")))
}
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
Ok(
VersionInner::deserialize(MapAccessDeserializer::new(map))?
.into(),
)
}
}
deserializer.deserialize_any(VersionVisitor)
}
}
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{}.{}.{}",
self.major, self.minor, self.patch
))
}
}
impl TryFrom<&str> for Version {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let mut split = value.split('.');
let major = split
.next()
.context("must provide at least major version")?
.parse::<i32>()
.context("major version must be integer")?;
let minor = split
.next()
.map(|minor| minor.parse::<i32>())
.transpose()
.context("minor version must be integer")?
.unwrap_or_default();
let patch = split
.next()
.map(|patch| patch.parse::<i32>())
.transpose()
.context("patch version must be integer")?
.unwrap_or_default();
Ok(Version {
major,
minor,
patch,
})
}
}
impl Version {
pub fn increment(&mut self) {
self.patch += 1;
}
pub fn is_none(&self) -> bool {
self.major == 0 && self.minor == 0 && self.patch == 0
}
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Parser,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct EnvironmentVar {
pub variable: String,
pub value: String,
}
pub fn environment_vars_from_str(
input: &str,
) -> anyhow::Result<Vec<EnvironmentVar>> {
parse_key_value_list(input).map(|list| {
list
.into_iter()
.map(|(variable, value)| EnvironmentVar { variable, value })
.collect()
})
}
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct LatestCommit {
pub hash: String,
pub message: String,
}
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct FileContents {
pub path: String,
pub contents: String,
}
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ImageDigest(String);
impl ImageDigest {
pub fn new(image: &str, digest: &str) -> Self {
Self(format!("{image}@{digest}"))
}
pub fn parse(digest: &str) -> Option<Self> {
if digest.contains('@') {
Some(Self(digest.to_string()))
} else if digest.starts_with("sha256:") {
Some(Self(format!("__unknown__@{digest}")))
} else {
None
}
}
pub fn vec(image_digests: &[String]) -> Vec<Self> {
image_digests
.iter()
.filter_map(|digest| ImageDigest::parse(digest))
.collect()
}
pub fn into_inner(self) -> String {
self.0
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn update_available(
&self,
current_digests: &[ImageDigest],
) -> bool {
let Some(latest_digest) = self.digest() else {
return false;
};
let digests = current_digests
.iter()
.filter_map(|digest| digest.digest())
.collect::<Vec<_>>();
if digests.is_empty() {
return false;
}
!digests.contains(&latest_digest)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn image_digest(&self) -> Option<(&str, &str)> {
self.0.split_once('@')
}
pub fn image(&self) -> Option<&str> {
self.image_digest().map(|(image, _)| image)
}
pub fn digest(&self) -> Option<&str> {
self.image_digest().map(|(_, digest)| digest)
}
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct MaintenanceWindow {
pub name: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub schedule_type: MaintenanceScheduleType,
#[serde(default)]
pub day_of_week: String,
#[serde(default)]
pub date: String,
#[serde(default)]
pub hour: u8,
#[serde(default)]
pub minute: u8,
pub duration_minutes: u32,
#[serde(default)]
pub timezone: String,
#[serde(default = "default_enabled")]
pub enabled: bool,
}
fn default_enabled() -> bool {
true
}
#[typeshare]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum DefaultRepoFolder {
Stacks,
Builds,
Repos,
NotApplicable,
}
#[typeshare]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct RepoExecutionArgs {
pub name: String,
pub provider: String,
pub https: bool,
pub account: Option<String>,
pub repo: Option<String>,
pub branch: String,
pub commit: Option<String>,
pub destination: Option<String>,
pub default_folder: DefaultRepoFolder,
}
impl RepoExecutionArgs {
pub fn path(&self, root_repo_dir: &Path) -> PathBuf {
match &self.destination {
Some(destination) => root_repo_dir
.join(to_path_compatible_name(&self.name))
.join(destination),
None => root_repo_dir.join(to_path_compatible_name(&self.name)),
}
.components()
.collect()
}
pub fn remote_url(
&self,
access_token: Option<&str>,
) -> anyhow::Result<String> {
let access_token_at = match access_token {
Some(token) => match token.split_once(':') {
Some((username, token)) => format!(
"{}:{}@",
urlencoding::encode(username.trim()),
urlencoding::encode(token.trim())
),
None => {
format!("token:{}@", urlencoding::encode(token.trim()))
}
},
None => String::new(),
};
let protocol = if self.https { "https" } else { "http" };
let repo = self
.repo
.as_ref()
.context("resource has no repo attached")?;
Ok(format!(
"{protocol}://{access_token_at}{}/{repo}",
self.provider
))
}
pub fn unique_path(
&self,
repo_dir: &Path,
) -> anyhow::Result<PathBuf> {
let repo = self
.repo
.as_ref()
.context("resource has no repo attached")?;
let res = repo_dir
.join(self.provider.replace('/', "-"))
.join(repo.replace('/', "-"))
.join(self.branch.replace('/', "-"))
.join(self.commit.as_deref().unwrap_or("latest"))
.components()
.collect();
Ok(res)
}
}
impl From<&self::stack::Stack> for RepoExecutionArgs {
fn from(stack: &self::stack::Stack) -> Self {
RepoExecutionArgs {
name: stack.name.clone(),
provider: optional_string(&stack.config.git_provider)
.unwrap_or_else(|| String::from("github.com")),
https: stack.config.git_https,
account: optional_string(&stack.config.git_account),
repo: optional_string(&stack.config.repo),
branch: optional_string(&stack.config.branch)
.unwrap_or_else(|| String::from("main")),
commit: optional_string(&stack.config.commit),
destination: optional_string(&stack.config.clone_path),
default_folder: DefaultRepoFolder::Stacks,
}
}
}
impl From<&self::build::Build> for RepoExecutionArgs {
fn from(build: &self::build::Build) -> RepoExecutionArgs {
RepoExecutionArgs {
name: build.name.clone(),
provider: optional_string(&build.config.git_provider)
.unwrap_or_else(|| String::from("github.com")),
https: build.config.git_https,
account: optional_string(&build.config.git_account),
repo: optional_string(&build.config.repo),
branch: optional_string(&build.config.branch)
.unwrap_or_else(|| String::from("main")),
commit: optional_string(&build.config.commit),
destination: None,
default_folder: DefaultRepoFolder::Builds,
}
}
}
impl From<&self::repo::Repo> for RepoExecutionArgs {
fn from(repo: &self::repo::Repo) -> RepoExecutionArgs {
RepoExecutionArgs {
name: repo.name.clone(),
provider: optional_string(&repo.config.git_provider)
.unwrap_or_else(|| String::from("github.com")),
https: repo.config.git_https,
account: optional_string(&repo.config.git_account),
repo: optional_string(&repo.config.repo),
branch: optional_string(&repo.config.branch)
.unwrap_or_else(|| String::from("main")),
commit: optional_string(&repo.config.commit),
destination: optional_string(&repo.config.path),
default_folder: DefaultRepoFolder::Repos,
}
}
}
impl From<&self::sync::ResourceSync> for RepoExecutionArgs {
fn from(sync: &self::sync::ResourceSync) -> Self {
RepoExecutionArgs {
name: sync.name.clone(),
provider: optional_string(&sync.config.git_provider)
.unwrap_or_else(|| String::from("github.com")),
https: sync.config.git_https,
account: optional_string(&sync.config.git_account),
repo: optional_string(&sync.config.repo),
branch: optional_string(&sync.config.branch)
.unwrap_or_else(|| String::from("main")),
commit: optional_string(&sync.config.commit),
destination: None,
default_folder: DefaultRepoFolder::NotApplicable,
}
}
}
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct RepoExecutionResponse {
pub logs: Vec<Log>,
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub path: PathBuf,
pub commit_hash: Option<String>,
pub commit_message: Option<String>,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Default,
Serialize,
Deserialize,
Display,
EnumString,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum Timelength {
#[serde(rename = "1-sec")]
#[strum(serialize = "1-sec")]
OneSecond,
#[serde(rename = "2-sec")]
#[strum(serialize = "2-sec")]
TwoSeconds,
#[serde(rename = "3-sec")]
#[strum(serialize = "3-sec")]
ThreeSeconds,
#[serde(rename = "5-sec")]
#[strum(serialize = "5-sec")]
FiveSeconds,
#[serde(rename = "10-sec")]
#[strum(serialize = "10-sec")]
TenSeconds,
#[serde(rename = "15-sec")]
#[strum(serialize = "15-sec")]
FifteenSeconds,
#[serde(rename = "30-sec")]
#[strum(serialize = "30-sec")]
ThirtySeconds,
#[default]
#[serde(rename = "1-min")]
#[strum(serialize = "1-min")]
OneMinute,
#[serde(rename = "2-min")]
#[strum(serialize = "2-min")]
TwoMinutes,
#[serde(rename = "3-min")]
#[strum(serialize = "3-min")]
ThreeMinutes,
#[serde(rename = "5-min")]
#[strum(serialize = "5-min")]
FiveMinutes,
#[serde(rename = "10-min")]
#[strum(serialize = "10-min")]
TenMinutes,
#[serde(rename = "15-min")]
#[strum(serialize = "15-min")]
FifteenMinutes,
#[serde(rename = "30-min")]
#[strum(serialize = "30-min")]
ThirtyMinutes,
#[serde(rename = "1-hr")]
#[strum(serialize = "1-hr")]
OneHour,
#[serde(rename = "2-hr")]
#[strum(serialize = "2-hr")]
TwoHours,
#[serde(rename = "3-hr")]
#[strum(serialize = "3-hr")]
ThreeHours,
#[serde(rename = "6-hr")]
#[strum(serialize = "6-hr")]
SixHours,
#[serde(rename = "8-hr")]
#[strum(serialize = "8-hr")]
EightHours,
#[serde(rename = "12-hr")]
#[strum(serialize = "12-hr")]
TwelveHours,
#[serde(rename = "1-day")]
#[strum(serialize = "1-day")]
OneDay,
#[serde(rename = "2-day")]
#[strum(serialize = "2-day")]
TwoDays,
#[serde(rename = "3-day")]
#[strum(serialize = "3-day")]
ThreeDays,
#[serde(rename = "1-wk")]
#[strum(serialize = "1-wk")]
OneWeek,
#[serde(rename = "2-wk")]
#[strum(serialize = "2-wk")]
TwoWeeks,
#[serde(rename = "30-day")]
#[strum(serialize = "30-day")]
ThirtyDays,
}
impl TryInto<async_timing_util::Timelength> for Timelength {
type Error = anyhow::Error;
fn try_into(
self,
) -> Result<async_timing_util::Timelength, Self::Error> {
async_timing_util::Timelength::from_str(&self.to_string())
.context("failed to parse timelength?")
}
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Default,
EnumString,
Serialize,
Deserialize,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum DayOfWeek {
#[default]
#[serde(alias = "monday", alias = "Mon", alias = "mon")]
#[strum(serialize = "monday", serialize = "Mon", serialize = "mon")]
Monday,
#[serde(alias = "tuesday", alias = "Tue", alias = "tue")]
#[strum(
serialize = "tuesday",
serialize = "Tue",
serialize = "tue"
)]
Tuesday,
#[serde(alias = "wednesday", alias = "Wed", alias = "wed")]
#[strum(
serialize = "wednesday",
serialize = "Wed",
serialize = "wed"
)]
Wednesday,
#[serde(alias = "thursday", alias = "Thurs", alias = "thurs")]
#[strum(
serialize = "thursday",
serialize = "Thurs",
serialize = "thurs"
)]
Thursday,
#[serde(alias = "friday", alias = "Fri", alias = "fri")]
#[strum(serialize = "friday", serialize = "Fri", serialize = "fri")]
Friday,
#[serde(alias = "saturday", alias = "Sat", alias = "sat")]
#[strum(
serialize = "saturday",
serialize = "Sat",
serialize = "sat"
)]
Saturday,
#[serde(alias = "sunday", alias = "Sun", alias = "sun")]
#[strum(serialize = "sunday", serialize = "Sun", serialize = "sun")]
Sunday,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Default,
EnumString,
Serialize,
Deserialize,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum MaintenanceScheduleType {
#[default]
Daily,
Weekly,
OneTime, }
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Default,
EnumString,
Serialize,
Deserialize,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum IanaTimezone {
#[serde(rename = "Etc/GMT+12")]
#[strum(serialize = "Etc/GMT+12")]
EtcGmtMinus12,
#[serde(rename = "Pacific/Pago_Pago")]
#[strum(serialize = "Pacific/Pago_Pago")]
PacificPagoPago,
#[serde(rename = "Pacific/Honolulu")]
#[strum(serialize = "Pacific/Honolulu")]
PacificHonolulu,
#[serde(rename = "Pacific/Marquesas")]
#[strum(serialize = "Pacific/Marquesas")]
PacificMarquesas,
#[serde(rename = "America/Anchorage")]
#[strum(serialize = "America/Anchorage")]
AmericaAnchorage,
#[serde(rename = "America/Los_Angeles")]
#[strum(serialize = "America/Los_Angeles")]
AmericaLosAngeles,
#[serde(rename = "America/Denver")]
#[strum(serialize = "America/Denver")]
AmericaDenver,
#[serde(rename = "America/Chicago")]
#[strum(serialize = "America/Chicago")]
AmericaChicago,
#[serde(rename = "America/New_York")]
#[strum(serialize = "America/New_York")]
AmericaNewYork,
#[serde(rename = "America/Halifax")]
#[strum(serialize = "America/Halifax")]
AmericaHalifax,
#[serde(rename = "America/St_Johns")]
#[strum(serialize = "America/St_Johns")]
AmericaStJohns,
#[serde(rename = "America/Sao_Paulo")]
#[strum(serialize = "America/Sao_Paulo")]
AmericaSaoPaulo,
#[serde(rename = "America/Noronha")]
#[strum(serialize = "America/Noronha")]
AmericaNoronha,
#[serde(rename = "Atlantic/Azores")]
#[strum(serialize = "Atlantic/Azores")]
AtlanticAzores,
#[default]
#[serde(rename = "Etc/UTC")]
#[strum(serialize = "Etc/UTC")]
EtcUtc,
#[serde(rename = "Europe/Berlin")]
#[strum(serialize = "Europe/Berlin")]
EuropeBerlin,
#[serde(rename = "Europe/Bucharest")]
#[strum(serialize = "Europe/Bucharest")]
EuropeBucharest,
#[serde(rename = "Europe/Moscow")]
#[strum(serialize = "Europe/Moscow")]
EuropeMoscow,
#[serde(rename = "Asia/Tehran")]
#[strum(serialize = "Asia/Tehran")]
AsiaTehran,
#[serde(rename = "Asia/Dubai")]
#[strum(serialize = "Asia/Dubai")]
AsiaDubai,
#[serde(rename = "Asia/Kabul")]
#[strum(serialize = "Asia/Kabul")]
AsiaKabul,
#[serde(rename = "Asia/Karachi")]
#[strum(serialize = "Asia/Karachi")]
AsiaKarachi,
#[serde(rename = "Asia/Kolkata")]
#[strum(serialize = "Asia/Kolkata")]
AsiaKolkata,
#[serde(rename = "Asia/Kathmandu")]
#[strum(serialize = "Asia/Kathmandu")]
AsiaKathmandu,
#[serde(rename = "Asia/Dhaka")]
#[strum(serialize = "Asia/Dhaka")]
AsiaDhaka,
#[serde(rename = "Asia/Yangon")]
#[strum(serialize = "Asia/Yangon")]
AsiaYangon,
#[serde(rename = "Asia/Bangkok")]
#[strum(serialize = "Asia/Bangkok")]
AsiaBangkok,
#[serde(rename = "Asia/Shanghai")]
#[strum(serialize = "Asia/Shanghai")]
AsiaShanghai,
#[serde(rename = "Australia/Eucla")]
#[strum(serialize = "Australia/Eucla")]
AustraliaEucla,
#[serde(rename = "Asia/Tokyo")]
#[strum(serialize = "Asia/Tokyo")]
AsiaTokyo,
#[serde(rename = "Australia/Adelaide")]
#[strum(serialize = "Australia/Adelaide")]
AustraliaAdelaide,
#[serde(rename = "Australia/Sydney")]
#[strum(serialize = "Australia/Sydney")]
AustraliaSydney,
#[serde(rename = "Australia/Lord_Howe")]
#[strum(serialize = "Australia/Lord_Howe")]
AustraliaLordHowe,
#[serde(rename = "Pacific/Port_Moresby")]
#[strum(serialize = "Pacific/Port_Moresby")]
PacificPortMoresby,
#[serde(rename = "Pacific/Auckland")]
#[strum(serialize = "Pacific/Auckland")]
PacificAuckland,
#[serde(rename = "Pacific/Chatham")]
#[strum(serialize = "Pacific/Chatham")]
PacificChatham,
#[serde(rename = "Pacific/Tongatapu")]
#[strum(serialize = "Pacific/Tongatapu")]
PacificTongatapu,
#[serde(rename = "Pacific/Kiritimati")]
#[strum(serialize = "Pacific/Kiritimati")]
PacificKiritimati,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
Default,
Display,
EnumString,
AsRefStr,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum Operation {
#[default]
None,
CreateSwarm,
UpdateSwarm,
RenameSwarm,
DeleteSwarm,
RemoveSwarmNodes,
RemoveSwarmStacks,
RemoveSwarmServices,
CreateSwarmConfig,
RotateSwarmConfig,
RemoveSwarmConfigs,
CreateSwarmSecret,
RotateSwarmSecret,
RemoveSwarmSecrets,
CreateServer,
UpdateServer,
UpdateServerKey,
DeleteServer,
RenameServer,
StartContainer,
RestartContainer,
PauseContainer,
UnpauseContainer,
StopContainer,
DestroyContainer,
StartAllContainers,
RestartAllContainers,
PauseAllContainers,
UnpauseAllContainers,
StopAllContainers,
PruneContainers,
CreateNetwork,
DeleteNetwork,
PruneNetworks,
DeleteImage,
PruneImages,
DeleteVolume,
PruneVolumes,
PruneDockerBuilders,
PruneBuildx,
PruneSystem,
CreateStack,
UpdateStack,
RenameStack,
DeleteStack,
WriteStackContents,
RefreshStackCache,
PullStack,
DeployStack,
StartStack,
RestartStack,
PauseStack,
UnpauseStack,
StopStack,
DestroyStack,
RunStackService,
CheckStackForUpdate,
DeployStackService,
PullStackService,
StartStackService,
RestartStackService,
PauseStackService,
UnpauseStackService,
StopStackService,
DestroyStackService,
CreateDeployment,
UpdateDeployment,
RenameDeployment,
DeleteDeployment,
Deploy,
PullDeployment,
StartDeployment,
RestartDeployment,
PauseDeployment,
UnpauseDeployment,
StopDeployment,
DestroyDeployment,
CheckDeploymentForUpdate,
CreateBuild,
UpdateBuild,
RenameBuild,
DeleteBuild,
RunBuild,
CancelBuild,
WriteDockerfile,
CreateRepo,
UpdateRepo,
RenameRepo,
DeleteRepo,
CloneRepo,
PullRepo,
BuildRepo,
CancelRepoBuild,
CreateProcedure,
UpdateProcedure,
RenameProcedure,
DeleteProcedure,
RunProcedure,
CreateAction,
UpdateAction,
RenameAction,
DeleteAction,
RunAction,
CreateResourceSync,
UpdateResourceSync,
RenameResourceSync,
DeleteResourceSync,
WriteSyncContents,
CommitSync,
RunSync,
CreateBuilder,
UpdateBuilder,
RenameBuilder,
DeleteBuilder,
CreateAlerter,
UpdateAlerter,
RenameAlerter,
DeleteAlerter,
TestAlerter,
SendAlert,
ClearRepoCache,
BackupCoreDatabase,
GlobalAutoUpdate,
RotateAllServerKeys,
RotateCoreKeys,
CreateVariable,
UpdateVariableValue,
DeleteVariable,
CreateGitProviderAccount,
UpdateGitProviderAccount,
DeleteGitProviderAccount,
CreateDockerRegistryAccount,
UpdateDockerRegistryAccount,
DeleteDockerRegistryAccount,
}
#[typeshare]
#[derive(
Serialize,
Deserialize,
Debug,
Default,
Display,
EnumString,
PartialEq,
Hash,
Eq,
Clone,
Copy,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum SearchCombinator {
#[default]
Or,
And,
}
#[typeshare]
#[derive(
Serialize,
Deserialize,
Debug,
PartialEq,
Hash,
Eq,
Clone,
Copy,
Default,
Display,
EnumString,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(rename_all = "UPPERCASE")]
#[strum(serialize_all = "UPPERCASE")]
pub enum TerminationSignal {
#[serde(alias = "1")]
SigHup,
#[serde(alias = "2")]
SigInt,
#[serde(alias = "3")]
SigQuit,
#[default]
#[serde(alias = "15")]
SigTerm,
}
#[typeshare]
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
EnumDiscriminants,
)]
#[strum_discriminants(name(ResourceTargetVariant))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(
not(feature = "utoipa"),
strum_discriminants(derive(
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
Display,
EnumString,
AsRefStr,
ValueEnum,
))
)]
#[cfg_attr(
feature = "utoipa",
strum_discriminants(derive(
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
Display,
EnumString,
AsRefStr,
ValueEnum,
utoipa::ToSchema,
))
)]
#[serde(tag = "type", content = "id")]
pub enum ResourceTarget {
System(String),
Swarm(String),
Server(String),
Stack(String),
Deployment(String),
Build(String),
Repo(String),
Procedure(String),
Action(String),
Builder(String),
Alerter(String),
ResourceSync(String),
}
impl ResourceTarget {
pub fn system() -> ResourceTarget {
Self::System("system".to_string())
}
}
impl Default for ResourceTarget {
fn default() -> Self {
ResourceTarget::system()
}
}
impl ResourceTarget {
pub fn is_empty(&self) -> bool {
match self {
ResourceTarget::System(id) => id.is_empty(),
ResourceTarget::Swarm(id) => id.is_empty(),
ResourceTarget::Server(id) => id.is_empty(),
ResourceTarget::Stack(id) => id.is_empty(),
ResourceTarget::Deployment(id) => id.is_empty(),
ResourceTarget::Build(id) => id.is_empty(),
ResourceTarget::Repo(id) => id.is_empty(),
ResourceTarget::Procedure(id) => id.is_empty(),
ResourceTarget::Action(id) => id.is_empty(),
ResourceTarget::Builder(id) => id.is_empty(),
ResourceTarget::Alerter(id) => id.is_empty(),
ResourceTarget::ResourceSync(id) => id.is_empty(),
}
}
pub fn extract_variant(&self) -> ResourceTargetVariant {
self.into()
}
pub fn extract_variant_id(
&self,
) -> (ResourceTargetVariant, &String) {
let id = match self {
ResourceTarget::System(id) => id,
ResourceTarget::Swarm(id) => id,
ResourceTarget::Server(id) => id,
ResourceTarget::Stack(id) => id,
ResourceTarget::Build(id) => id,
ResourceTarget::Builder(id) => id,
ResourceTarget::Deployment(id) => id,
ResourceTarget::Repo(id) => id,
ResourceTarget::Alerter(id) => id,
ResourceTarget::Procedure(id) => id,
ResourceTarget::Action(id) => id,
ResourceTarget::ResourceSync(id) => id,
};
(self.into(), id)
}
}
impl From<&server::Server> for ResourceTarget {
fn from(server: &server::Server) -> Self {
Self::Server(server.id.clone())
}
}
impl From<&stack::Stack> for ResourceTarget {
fn from(stack: &stack::Stack) -> Self {
Self::Stack(stack.id.clone())
}
}
impl From<&deployment::Deployment> for ResourceTarget {
fn from(deployment: &deployment::Deployment) -> Self {
Self::Deployment(deployment.id.clone())
}
}
impl From<&build::Build> for ResourceTarget {
fn from(build: &build::Build) -> Self {
Self::Build(build.id.clone())
}
}
impl From<&repo::Repo> for ResourceTarget {
fn from(repo: &repo::Repo) -> Self {
Self::Repo(repo.id.clone())
}
}
impl From<&procedure::Procedure> for ResourceTarget {
fn from(procedure: &procedure::Procedure) -> Self {
Self::Procedure(procedure.id.clone())
}
}
impl From<&action::Action> for ResourceTarget {
fn from(action: &action::Action) -> Self {
Self::Action(action.id.clone())
}
}
impl From<&sync::ResourceSync> for ResourceTarget {
fn from(resource_sync: &sync::ResourceSync) -> Self {
Self::ResourceSync(resource_sync.id.clone())
}
}
impl From<&builder::Builder> for ResourceTarget {
fn from(builder: &builder::Builder) -> Self {
Self::Builder(builder.id.clone())
}
}
impl From<&alerter::Alerter> for ResourceTarget {
fn from(alerter: &alerter::Alerter) -> Self {
Self::Alerter(alerter.id.clone())
}
}
impl ResourceTargetVariant {
pub fn toml_header(&self) -> &'static str {
match self {
ResourceTargetVariant::System => "system",
ResourceTargetVariant::Swarm => "swarm",
ResourceTargetVariant::Server => "server",
ResourceTargetVariant::Stack => "stack",
ResourceTargetVariant::Deployment => "deployment",
ResourceTargetVariant::Build => "build",
ResourceTargetVariant::Repo => "repo",
ResourceTargetVariant::Procedure => "procedure",
ResourceTargetVariant::Action => "action",
ResourceTargetVariant::ResourceSync => "resource_sync",
ResourceTargetVariant::Builder => "builder",
ResourceTargetVariant::Alerter => "alerter",
}
}
}
#[typeshare]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum ScheduleFormat {
#[default]
English,
Cron,
}
#[typeshare]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum FileFormat {
#[default]
KeyValue,
Toml,
Yaml,
Json,
}
pub const KOMODO_EXIT_CODE: &str = "__KOMODO_EXIT_CODE:";
pub fn resource_link(
host: &str,
resource_type: ResourceTargetVariant,
id: &str,
) -> String {
let path = match resource_type {
ResourceTargetVariant::System => unreachable!(),
ResourceTargetVariant::Swarm => format!("/swarms/{id}"),
ResourceTargetVariant::Server => {
format!("/servers/{id}")
}
ResourceTargetVariant::Stack => {
format!("/stacks/{id}")
}
ResourceTargetVariant::Deployment => {
format!("/deployments/{id}")
}
ResourceTargetVariant::Build => format!("/builds/{id}"),
ResourceTargetVariant::Repo => format!("/repos/{id}"),
ResourceTargetVariant::Procedure => {
format!("/procedures/{id}")
}
ResourceTargetVariant::Action => {
format!("/actions/{id}")
}
ResourceTargetVariant::ResourceSync => {
format!("/resource-syncs/{id}")
}
ResourceTargetVariant::Builder => {
format!("/builders/{id}")
}
ResourceTargetVariant::Alerter => {
format!("/alerters/{id}")
}
};
format!("{host}{path}")
}
#[allow(clippy::large_enum_variant)]
pub enum SwarmOrServer {
Swarm(swarm::Swarm),
Server(server::Server),
None,
}
impl SwarmOrServer {
pub fn verify_has_target(&self) -> mogh_error::Result<()> {
if let Self::None = self {
Err(
anyhow!("Must attach either swarm or server")
.status_code(StatusCode::BAD_REQUEST),
)
} else {
Ok(())
}
}
pub fn swarm_id(&self) -> Option<&str> {
let Self::Swarm(swarm) = self else {
return None;
};
Some(&swarm.id)
}
pub fn swarm_name(&self) -> Option<&str> {
let Self::Swarm(swarm) = self else {
return None;
};
Some(&swarm.name)
}
pub fn server_id(&self) -> Option<&str> {
let Self::Server(server) = self else {
return None;
};
Some(&server.id)
}
pub fn server_name(&self) -> Option<&str> {
let Self::Server(server) = self else {
return None;
};
Some(&server.name)
}
}