Skip to main content

core_lib/notifications/
sender.rs

1use 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    // A hung webhook must not stall the pipeline: 10s cap on the whole request.
19    let resp = client
20        .post(url)
21        .json(&payload)
22        .timeout(std::time::Duration::from_secs(10))
23        .send()
24        .await?;
25
26    if resp.status().is_success() {
27        Ok(())
28    } else {
29        Err(anyhow::anyhow!(
30            "Discord error: {} - {:?}",
31            resp.status(),
32            resp.text().await?
33        ))
34    }
35}
36
37pub async fn discord_send_succes(ctx: &WatchContext, m: &ExecMetrics) -> Result<()> {
38    if ctx.config.pipeline.notifications.is_none() {
39        return Ok(());
40    }
41
42    let notification_config = ctx.config.pipeline.notifications.as_ref().unwrap();
43
44    let embed = DiscordEmbed {
45        title: "✅Pipeline finish".into(),
46        description: format!("Pipeline **{}** executed successfully", ctx.repo.name),
47        color: 0x2ECC71,
48        thumbnail: DiscordImage::load(notification_config.thumbnail.clone()),
49        fields: vec![
50            DiscordField {
51                name: "Service name".into(),
52                value: format!("`{}`", ctx.repo.name.clone()),
53                inline: false,
54            },
55            DiscordField {
56                name: "Duration".into(),
57                value: format!("`{:.2}s`", (m.duration_ms.unwrap_or(1) as f64) / 1000.0),
58                inline: true,
59            },
60            DiscordField {
61                name: "CPU".into(),
62                value: format!("`{:.2}%`", m.cpu_usage),
63                inline: true,
64            },
65            DiscordField {
66                name: "Mem (%)".into(),
67                value: format!("`{:.2}%`", m.mem_usage),
68                inline: true,
69            },
70            DiscordField {
71                name: "Mem (Mb)".into(),
72                value: format!("`{}Mb`", m.mem_usage_kb / (1024 * 1024)),
73                inline: true,
74            },
75        ],
76        footer: Some(DiscordFooter {
77            text: "Fleet CI/CD Pipeline".into(),
78        }),
79        timestamp: Some(m.finished_at.unwrap()),
80    };
81
82    for c in notification_config.channels.iter() {
83        if c.service == "discord" {
84            discord_sender(&c.url, &embed).await?;
85        }
86    }
87    Ok(())
88}
89
90/// this function take ctx and msg
91/// msg will be split on ":/:" pattern and divide in field
92pub async fn discord_send_failure(ctx: &WatchContext, msg: &str, m: &ExecMetrics) -> Result<()> {
93    if ctx.config.pipeline.notifications.is_none() {
94        return Ok(());
95    }
96
97    let notification_config = ctx.config.pipeline.notifications.as_ref().unwrap();
98
99    let embed = DiscordEmbed {
100        title: "❌ Pipeline failed".into(),
101        description: String::from(msg),
102        color: 0xE74C3C,
103        thumbnail: DiscordImage::load(notification_config.thumbnail.clone()),
104        fields: vec![
105            DiscordField {
106                name: "Service name".into(),
107                value: format!("`{}`", ctx.repo.name.clone()),
108                inline: false,
109            },
110            DiscordField {
111                name: "Duration".into(),
112                value: format!("`{:.2}s`", (m.duration_ms.unwrap_or(1) as f64) / 1000.0),
113                inline: true,
114            },
115            DiscordField {
116                name: "CPU".into(),
117                value: format!("`{:.2}%`", m.cpu_usage),
118                inline: true,
119            },
120            DiscordField {
121                name: "Mem (%)".into(),
122                value: format!("`{:.2}%`", m.mem_usage),
123                inline: true,
124            },
125            DiscordField {
126                name: "Mem (Mb)".into(),
127                value: format!("`{}Mb`", m.mem_usage_kb / (1024 * 1024)),
128                inline: true,
129            },
130        ],
131        footer: Some(DiscordFooter {
132            text: "Fleet CI/CD Pipeline".into(),
133        }),
134        timestamp: Some(m.finished_at.unwrap_or(Utc::now())),
135    };
136    for c in notification_config.channels.iter() {
137        if c.service == "discord" {
138            discord_sender(&c.url, &embed).await?;
139        }
140    }
141    Ok(())
142}