core_lib/notifications/
sender.rs1use anyhow::Result;
2use chrono::Utc;
3use reqwest::Client;
4use serde_json::json;
5
6use crate::{
7 core::watcher::WatchContext,
8 exec::metrics::ExecMetrics,
9 notifications::{DiscordEmbed, DiscordField, DiscordFooter, DiscordImage},
10};
11
12pub async fn discord_sender(url: &str, embed: &DiscordEmbed) -> Result<()> {
13 let payload = json!({
14 "embeds": [embed]
15 });
16
17 let client = Client::new();
18 let resp = client.post(url).json(&payload).send().await?;
19
20 if resp.status().is_success() {
21 Ok(())
22 } else {
23 Err(anyhow::anyhow!(
24 "Discord error: {} - {:?}",
25 resp.status(),
26 resp.text().await?
27 ))
28 }
29}
30
31pub async fn discord_send_succes(ctx: &WatchContext, m: &ExecMetrics) -> Result<()> {
32 if ctx.config.pipeline.notifications.is_none() {
33 return Ok(());
34 }
35
36 let notification_config = ctx.config.pipeline.notifications.as_ref().unwrap();
37
38 let embed = DiscordEmbed {
39 title: "✅Pipeline finish".into(),
40 description: format!("Pipeline **{}** executed successfully", ctx.repo.name),
41 color: 0x2ECC71,
42 thumbnail: DiscordImage::load(notification_config.thumbnail.clone()),
43 fields: vec![
44 DiscordField {
45 name: "Service name".into(),
46 value: format!("`{}`", ctx.repo.name.clone()),
47 inline: false,
48 },
49 DiscordField {
50 name: "Duration".into(),
51 value: format!("`{:.2}s`", (m.duration_ms.unwrap_or(1) as f64) / 1000.0),
52 inline: true,
53 },
54 DiscordField {
55 name: "CPU".into(),
56 value: format!("`{:.2}%`", m.cpu_usage),
57 inline: true,
58 },
59 DiscordField {
60 name: "Mem (%)".into(),
61 value: format!("`{:.2}%`", m.mem_usage),
62 inline: true,
63 },
64 DiscordField {
65 name: "Mem (Mb)".into(),
66 value: format!("`{}Mb`", m.mem_usage_kb / (1024 * 1024)),
67 inline: true,
68 },
69 ],
70 footer: Some(DiscordFooter {
71 text: "Fleet CI/CD Pipeline".into(),
72 }),
73 timestamp: Some(m.finished_at.unwrap()),
74 };
75
76 for c in notification_config.channels.iter() {
77 if c.service == "discord" {
78 discord_sender(&c.url, &embed).await?;
79 }
80 }
81 Ok(())
82}
83
84pub async fn discord_send_failure(ctx: &WatchContext, msg: &str, m: &ExecMetrics) -> Result<()> {
87 if ctx.config.pipeline.notifications.is_none() {
88 return Ok(());
89 }
90
91 let notification_config = ctx.config.pipeline.notifications.as_ref().unwrap();
92
93 let embed = DiscordEmbed {
94 title: "❌ Pipeline failed".into(),
95 description: String::from(msg),
96 color: 0xE74C3C,
97 thumbnail: DiscordImage::load(notification_config.thumbnail.clone()),
98 fields: vec![
99 DiscordField {
100 name: "Service name".into(),
101 value: format!("`{}`", ctx.repo.name.clone()),
102 inline: false,
103 },
104 DiscordField {
105 name: "Duration".into(),
106 value: format!("`{:.2}s`", (m.duration_ms.unwrap_or(1) as f64) / 1000.0),
107 inline: true,
108 },
109 DiscordField {
110 name: "CPU".into(),
111 value: format!("`{:.2}%`", m.cpu_usage),
112 inline: true,
113 },
114 DiscordField {
115 name: "Mem (%)".into(),
116 value: format!("`{:.2}%`", m.mem_usage),
117 inline: true,
118 },
119 DiscordField {
120 name: "Mem (Mb)".into(),
121 value: format!("`{}Mb`", m.mem_usage_kb / (1024 * 1024)),
122 inline: true,
123 },
124 ],
125 footer: Some(DiscordFooter {
126 text: "Fleet CI/CD Pipeline".into(),
127 }),
128 timestamp: Some(m.finished_at.unwrap_or(Utc::now())),
129 };
130 for c in notification_config.channels.iter() {
131 if c.service == "discord" {
132 discord_sender(&c.url, &embed).await?;
133 }
134 }
135 Ok(())
136}