use async_timing_util::unix_timestamp_ms;
use derive_variants::{EnumVariants, ExtractVariant};
use serde::{Deserialize, Serialize};
use strum::{AsRefStr, Display, EnumString};
use typeshare::typeshare;
use crate::entities::{
all_logs_success, monitor_timestamp, MongoId, Operation, I64,
};
use super::{
alerter::Alerter, build::Build, builder::Builder,
deployment::Deployment, procedure::Procedure, repo::Repo,
server::Server, server_template::ServerTemplate, stack::Stack,
sync::ResourceSync, Version,
};
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[cfg_attr(
feature = "mongo",
derive(mongo_indexed::derive::MongoIndexed)
)]
#[cfg_attr(feature = "mongo", doc_index({ "target.type": 1 }))]
#[cfg_attr(feature = "mongo", sparse_doc_index({ "target.id": 1 }))]
pub struct Update {
#[serde(
default,
rename = "_id",
skip_serializing_if = "String::is_empty",
with = "bson::serde_helpers::hex_string_as_object_id"
)]
pub id: MongoId,
#[cfg_attr(feature = "mongo", index)]
pub operation: Operation,
#[cfg_attr(feature = "mongo", index)]
pub start_ts: I64,
#[cfg_attr(feature = "mongo", index)]
pub success: bool,
#[cfg_attr(feature = "mongo", index)]
pub operator: String,
pub target: ResourceTarget,
pub logs: Vec<Log>,
pub end_ts: Option<I64>,
#[cfg_attr(feature = "mongo", index)]
pub status: UpdateStatus,
#[serde(default, skip_serializing_if = "Version::is_none")]
pub version: Version,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub commit_hash: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub other_data: String,
}
impl Update {
pub fn push_simple_log(
&mut self,
stage: &str,
msg: impl Into<String>,
) {
self.logs.push(Log::simple(stage, msg.into()));
}
pub fn push_error_log(
&mut self,
stage: &str,
msg: impl Into<String>,
) {
self.logs.push(Log::error(stage, msg.into()));
}
pub fn in_progress(&mut self) {
self.status = UpdateStatus::InProgress;
}
pub fn finalize(&mut self) {
self.success = all_logs_success(&self.logs);
self.end_ts = Some(monitor_timestamp());
self.status = UpdateStatus::Complete;
}
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UpdateListItem {
pub id: String,
pub operation: Operation,
pub start_ts: I64,
pub success: bool,
pub username: String,
pub operator: String,
pub target: ResourceTarget,
pub status: UpdateStatus,
#[serde(default, skip_serializing_if = "Version::is_none")]
pub version: Version,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub other_data: String,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Log {
pub stage: String,
pub command: String,
pub stdout: String,
pub stderr: String,
pub success: bool,
pub start_ts: I64,
pub end_ts: I64,
}
impl Log {
pub fn simple(stage: &str, msg: String) -> Log {
let ts = unix_timestamp_ms() as i64;
Log {
stage: stage.to_string(),
stdout: msg,
success: true,
start_ts: ts,
end_ts: ts,
..Default::default()
}
}
pub fn error(stage: &str, msg: String) -> Log {
let ts = unix_timestamp_ms() as i64;
Log {
stage: stage.to_string(),
stderr: msg,
start_ts: ts,
end_ts: ts,
success: false,
..Default::default()
}
}
pub fn combined(&self) -> String {
match (self.stdout.is_empty(), self.stderr.is_empty()) {
(true, true) => {
format!("stdout: {}\n\nstderr: {}", self.stdout, self.stderr)
}
(true, false) => self.stdout.to_string(),
(false, true) => self.stderr.to_string(),
(false, false) => String::from("No log"),
}
}
}
#[typeshare]
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
EnumVariants,
)]
#[variant_derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
Display,
EnumString,
AsRefStr
)]
#[serde(tag = "type", content = "id")]
pub enum ResourceTarget {
System(String),
Build(String),
Builder(String),
Deployment(String),
Server(String),
Repo(String),
Alerter(String),
Procedure(String),
ServerTemplate(String),
ResourceSync(String),
Stack(String),
}
impl ResourceTarget {
pub fn extract_variant_id(
&self,
) -> (ResourceTargetVariant, &String) {
let id = match &self {
ResourceTarget::System(id) => id,
ResourceTarget::Build(id) => id,
ResourceTarget::Builder(id) => id,
ResourceTarget::Deployment(id) => id,
ResourceTarget::Server(id) => id,
ResourceTarget::Repo(id) => id,
ResourceTarget::Alerter(id) => id,
ResourceTarget::Procedure(id) => id,
ResourceTarget::ServerTemplate(id) => id,
ResourceTarget::ResourceSync(id) => id,
ResourceTarget::Stack(id) => id,
};
(self.extract_variant(), id)
}
pub fn system() -> ResourceTarget {
Self::System("system".to_string())
}
}
impl Default for ResourceTarget {
fn default() -> Self {
ResourceTarget::system()
}
}
impl From<&Build> for ResourceTarget {
fn from(build: &Build) -> Self {
Self::Build(build.id.clone())
}
}
impl From<&Deployment> for ResourceTarget {
fn from(deployment: &Deployment) -> Self {
Self::Deployment(deployment.id.clone())
}
}
impl From<&Server> for ResourceTarget {
fn from(server: &Server) -> Self {
Self::Server(server.id.clone())
}
}
impl From<&Repo> for ResourceTarget {
fn from(repo: &Repo) -> Self {
Self::Repo(repo.id.clone())
}
}
impl From<&Builder> for ResourceTarget {
fn from(builder: &Builder) -> Self {
Self::Builder(builder.id.clone())
}
}
impl From<&Alerter> for ResourceTarget {
fn from(alerter: &Alerter) -> Self {
Self::Alerter(alerter.id.clone())
}
}
impl From<&Procedure> for ResourceTarget {
fn from(procedure: &Procedure) -> Self {
Self::Procedure(procedure.id.clone())
}
}
impl From<&ServerTemplate> for ResourceTarget {
fn from(server_template: &ServerTemplate) -> Self {
Self::ServerTemplate(server_template.id.clone())
}
}
impl From<&ResourceSync> for ResourceTarget {
fn from(resource_sync: &ResourceSync) -> Self {
Self::ResourceSync(resource_sync.id.clone())
}
}
impl From<&Stack> for ResourceTarget {
fn from(resource_sync: &Stack) -> Self {
Self::Stack(resource_sync.id.clone())
}
}
#[typeshare]
#[derive(
Serialize,
Deserialize,
Debug,
Display,
EnumString,
PartialEq,
Hash,
Eq,
Clone,
Copy,
Default,
)]
pub enum UpdateStatus {
Queued,
InProgress,
#[default]
Complete,
}