add pending update alert

This commit is contained in:
mbecker20
2024-06-08 01:39:18 -07:00
parent cad1ee123e
commit d2cecf316c
6 changed files with 336 additions and 153 deletions

View File

@@ -3,16 +3,23 @@ use monitor_client::{
api::write::*,
entities::{
self,
alert::{Alert, AlertData},
alerter::Alerter,
build::Build,
builder::Builder,
deployment::Deployment,
monitor_timestamp,
permission::PermissionLevel,
procedure::Procedure,
repo::Repo,
server::Server,
server::{stats::SeverityLevel, Server},
server_template::ServerTemplate,
sync::{PendingSyncUpdates, ResourceSync},
sync::{
PendingSyncUpdates, PendingSyncUpdatesData,
PendingSyncUpdatesDataErr, PendingSyncUpdatesDataOk,
ResourceSync,
},
update::ResourceTarget,
user::User,
},
};
@@ -21,6 +28,7 @@ use mungos::{
mongodb::bson::{doc, to_document},
};
use resolver_api::Resolve;
use serror::serialize_error_pretty;
use crate::{
helpers::{
@@ -96,108 +104,136 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
>(&sync, &user, PermissionLevel::Execute)
.await?;
let (res, _, hash, message) =
crate::helpers::sync::remote::get_remote_resources(&sync)
.await
.context("failed to get remote resources")?;
let resources = res?;
let res = async {
let (res, _, hash, message) =
crate::helpers::sync::remote::get_remote_resources(&sync)
.await
.context("failed to get remote resources")?;
let resources = res?;
let all_resources = AllResourcesById::load().await?;
let id_to_tags = get_id_to_tags(None).await?;
let all_resources = AllResourcesById::load().await?;
let id_to_tags = get_id_to_tags(None).await?;
let pending = PendingSyncUpdates {
hash,
message,
server_updates: get_updates_for_view::<Server>(
resources.servers,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get server updates")?,
deployment_updates: get_updates_for_view::<Deployment>(
resources.deployments,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get deployment updates")?,
build_updates: get_updates_for_view::<Build>(
resources.builds,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get build updates")?,
repo_updates: get_updates_for_view::<Repo>(
resources.repos,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get repo updates")?,
procedure_updates: get_updates_for_view::<Procedure>(
resources.procedures,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get procedure updates")?,
alerter_updates: get_updates_for_view::<Alerter>(
resources.alerters,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get alerter updates")?,
builder_updates: get_updates_for_view::<Builder>(
resources.builders,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get builder updates")?,
server_template_updates:
get_updates_for_view::<ServerTemplate>(
resources.server_templates,
let data = PendingSyncUpdatesDataOk {
server_updates: get_updates_for_view::<Server>(
resources.servers,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get server template updates")?,
resource_sync_updates: get_updates_for_view::<
entities::sync::ResourceSync,
>(
resources.resource_syncs,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get resource sync updates")?,
variable_updates:
crate::helpers::sync::variables::get_updates_for_view(
resources.variables,
sync.config.delete,
)
.await
.context("failed to get variable updates")?,
user_group_updates:
crate::helpers::sync::user_groups::get_updates_for_view(
resources.user_groups,
.context("failed to get server updates")?,
deployment_updates: get_updates_for_view::<Deployment>(
resources.deployments,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get user group updates")?,
.context("failed to get deployment updates")?,
build_updates: get_updates_for_view::<Build>(
resources.builds,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get build updates")?,
repo_updates: get_updates_for_view::<Repo>(
resources.repos,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get repo updates")?,
procedure_updates: get_updates_for_view::<Procedure>(
resources.procedures,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get procedure updates")?,
alerter_updates: get_updates_for_view::<Alerter>(
resources.alerters,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get alerter updates")?,
builder_updates: get_updates_for_view::<Builder>(
resources.builders,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get builder updates")?,
server_template_updates:
get_updates_for_view::<ServerTemplate>(
resources.server_templates,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get server template updates")?,
resource_sync_updates: get_updates_for_view::<
entities::sync::ResourceSync,
>(
resources.resource_syncs,
sync.config.delete,
&all_resources,
&id_to_tags,
)
.await
.context("failed to get resource sync updates")?,
variable_updates:
crate::helpers::sync::variables::get_updates_for_view(
resources.variables,
sync.config.delete,
)
.await
.context("failed to get variable updates")?,
user_group_updates:
crate::helpers::sync::user_groups::get_updates_for_view(
resources.user_groups,
sync.config.delete,
&all_resources,
)
.await
.context("failed to get user group updates")?,
};
anyhow::Ok((hash, message, data))
}
.await;
let (pending, has_updates) = match res {
Ok((hash, message, data)) => {
let has_updates = !data.no_updates();
(
PendingSyncUpdates {
hash: Some(hash),
message: Some(message),
data: PendingSyncUpdatesData::Ok(data),
},
has_updates,
)
}
Err(e) => (
PendingSyncUpdates {
hash: None,
message: None,
data: PendingSyncUpdatesData::Err(
PendingSyncUpdatesDataErr {
message: serialize_error_pretty(&e),
},
),
},
false,
),
};
let pending = to_document(&pending)
@@ -211,6 +247,71 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
)
.await?;
// check to update alert
let id = sync.id.clone();
let name = sync.name.clone();
tokio::task::spawn(async move {
let db = db_client().await;
let Some(existing) = db_client()
.await
.alerts
.find_one(
doc! {
"resolved": false,
"target.type": "ResourceSync",
"target.id": &id,
},
None,
)
.await
.context("failed to query db for alert")
.inspect_err(|e| warn!("{e:#}"))
.ok()
else {
return;
};
match (existing, has_updates) {
// OPEN A NEW ALERT
(None, true) => {
let alert = Alert {
id: Default::default(),
ts: monitor_timestamp(),
resolved: false,
level: SeverityLevel::Ok,
target: ResourceTarget::ResourceSync(id.clone()),
data: AlertData::ResourceSyncPendingUpdates { id, name },
resolved_ts: None,
};
db.alerts
.insert_one(&alert, None)
.await
.context("failed to open existing pending resource sync updates alert")
.inspect_err(|e| warn!("{e:#}"))
.ok();
}
// CLOSE ALERT
(Some(existing), false) => {
update_one_by_id(
&db.alerts,
&existing.id,
doc! {
"$set": {
"resolved": true,
"resolved_ts": monitor_timestamp()
}
},
None,
)
.await
.context("failed to close existing pending resource sync updates alert")
.inspect_err(|e| warn!("{e:#}"))
.ok();
}
// NOTHING TO DO
_ => {}
}
});
crate::resource::get::<ResourceSync>(&sync.id).await
}
}

View File

@@ -246,6 +246,17 @@ async fn send_slack_alert(
];
(text, blocks.into())
}
AlertData::ResourceSyncPendingUpdates { id, name } => {
let text =
format!("{level} | There are pending resource sync updates");
let blocks = vec![
Block::header(text.clone()),
Block::section(format!(
"sync id: **{id}**\nsync name: **{name}**"
)),
];
(text, blocks.into())
}
AlertData::None {} => Default::default(),
};
if !text.is_empty() {

View File

@@ -148,6 +148,11 @@ pub enum AlertData {
message: String,
},
ResourceSyncPendingUpdates {
id: String,
name: String,
},
None {},
}

View File

@@ -67,24 +67,34 @@ pub struct ResourceSyncInfo {
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SyncUpdate {
/// Resources to create
pub to_create: i32,
/// Resources to update
pub to_update: i32,
/// Resources to delete
pub to_delete: i32,
/// A readable log of all the changes to be applied
pub log: String,
pub struct PendingSyncUpdates {
/// The commit hash which produced these pending updates
pub hash: Option<String>,
/// The commit message which produced these pending updates
pub message: Option<String>,
/// The data associated with the sync. Either Ok containing diffs,
/// or Err containing an error message
pub data: PendingSyncUpdatesData,
}
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
#[allow(clippy::large_enum_variant)]
pub enum PendingSyncUpdatesData {
Ok(PendingSyncUpdatesDataOk),
Err(PendingSyncUpdatesDataErr),
}
impl Default for PendingSyncUpdatesData {
fn default() -> Self {
Self::Ok(Default::default())
}
}
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PendingSyncUpdates {
/// The commit hash which produced these pending updates
pub hash: String,
/// The commit message which produced these pending updates
pub message: String,
pub struct PendingSyncUpdatesDataOk {
/// Readable log of any pending server updates
pub server_updates: Option<SyncUpdate>,
/// Readable log of any pending deployment updates
@@ -109,6 +119,41 @@ pub struct PendingSyncUpdates {
pub user_group_updates: Option<SyncUpdate>,
}
impl PendingSyncUpdatesDataOk {
pub fn no_updates(&self) -> bool {
self.server_updates.is_none()
&& self.deployment_updates.is_none()
&& self.build_updates.is_none()
&& self.repo_updates.is_none()
&& self.procedure_updates.is_none()
&& self.alerter_updates.is_none()
&& self.builder_updates.is_none()
&& self.server_template_updates.is_none()
&& self.resource_sync_updates.is_none()
&& self.variable_updates.is_none()
&& self.user_group_updates.is_none()
}
}
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SyncUpdate {
/// Resources to create
pub to_create: i32,
/// Resources to update
pub to_update: i32,
/// Resources to delete
pub to_delete: i32,
/// A readable log of all the changes to be applied
pub log: String,
}
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingSyncUpdatesDataErr {
pub message: String,
}
#[typeshare(serialized_as = "Partial<ResourceSyncConfig>")]
pub type _PartialResourceSyncConfig = PartialResourceSyncConfig;

View File

@@ -183,6 +183,10 @@ export type AlertData =
instance_id: string;
/** A reason for the failure */
message: string;
}}
| { type: "ResourceSyncPendingUpdates", data: {
id: string;
name: string;
}}
| { type: "None", data: {
}};
@@ -1174,44 +1178,20 @@ export interface ResourceSyncConfig {
webhook_enabled: boolean;
}
export interface SyncUpdate {
/** Resources to create */
to_create: number;
/** Resources to update */
to_update: number;
/** Resources to delete */
to_delete: number;
/** A readable log of all the changes to be applied */
log: string;
}
export type PendingSyncUpdatesData =
| { type: "Ok", data: PendingSyncUpdatesDataOk }
| { type: "Err", data: PendingSyncUpdatesDataErr };
export interface PendingSyncUpdates {
/** The commit hash which produced these pending updates */
hash: string;
hash?: string;
/** The commit message which produced these pending updates */
message: string;
/** Readable log of any pending server updates */
server_updates?: SyncUpdate;
/** Readable log of any pending deployment updates */
deployment_updates?: SyncUpdate;
/** Readable log of any pending build updates */
build_updates?: SyncUpdate;
/** Readable log of any pending repo updates */
repo_updates?: SyncUpdate;
/** Readable log of any pending procedure updates */
procedure_updates?: SyncUpdate;
/** Readable log of any pending alerter updates */
alerter_updates?: SyncUpdate;
/** Readable log of any pending builder updates */
builder_updates?: SyncUpdate;
/** Readable log of any pending server template updates */
server_template_updates?: SyncUpdate;
/** Readable log of any pending resource sync updates */
resource_sync_updates?: SyncUpdate;
/** Readable log of any pending variable updates */
variable_updates?: SyncUpdate;
/** Readable log of any pending user group updates */
user_group_updates?: SyncUpdate;
message?: string;
/**
* The data associated with the sync. Either Ok containing diffs,
* or Err containing an error message
*/
data: PendingSyncUpdatesData;
}
export interface ResourceSyncInfo {
@@ -3801,6 +3781,46 @@ export interface HetznerServerTemplateConfig {
port: number;
}
export interface SyncUpdate {
/** Resources to create */
to_create: number;
/** Resources to update */
to_update: number;
/** Resources to delete */
to_delete: number;
/** A readable log of all the changes to be applied */
log: string;
}
export interface PendingSyncUpdatesDataOk {
/** Readable log of any pending server updates */
server_updates?: SyncUpdate;
/** Readable log of any pending deployment updates */
deployment_updates?: SyncUpdate;
/** Readable log of any pending build updates */
build_updates?: SyncUpdate;
/** Readable log of any pending repo updates */
repo_updates?: SyncUpdate;
/** Readable log of any pending procedure updates */
procedure_updates?: SyncUpdate;
/** Readable log of any pending alerter updates */
alerter_updates?: SyncUpdate;
/** Readable log of any pending builder updates */
builder_updates?: SyncUpdate;
/** Readable log of any pending server template updates */
server_template_updates?: SyncUpdate;
/** Readable log of any pending resource sync updates */
resource_sync_updates?: SyncUpdate;
/** Readable log of any pending variable updates */
variable_updates?: SyncUpdate;
/** Readable log of any pending user group updates */
user_group_updates?: SyncUpdate;
}
export interface PendingSyncUpdatesDataErr {
message: string;
}
export type AuthRequest =
| { type: "GetLoginOptions", params: GetLoginOptions }
| { type: "CreateLocalUser", params: CreateLocalUser }

View File

@@ -177,19 +177,20 @@ export const filterBySplit = <T>(
};
export const sync_no_changes = (sync: Types.ResourceSync) => {
const pending = sync.info?.pending;
const pending = sync.info?.pending.data;
if (!pending) return false;
if (pending.type === "Err") return false;
return (
!pending.server_updates &&
!pending.deployment_updates &&
!pending.build_updates &&
!pending.repo_updates &&
!pending.procedure_updates &&
!pending.alerter_updates &&
!pending.builder_updates &&
!pending.server_template_updates &&
!pending.resource_sync_updates &&
!pending.variable_updates &&
!pending.user_group_updates
!pending.data.server_updates &&
!pending.data.deployment_updates &&
!pending.data.build_updates &&
!pending.data.repo_updates &&
!pending.data.procedure_updates &&
!pending.data.alerter_updates &&
!pending.data.builder_updates &&
!pending.data.server_template_updates &&
!pending.data.resource_sync_updates &&
!pending.data.variable_updates &&
!pending.data.user_group_updates
);
};