common: improve notifications system
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I084bb95a4eb79d5a15f7c062c112124c6a6a6964
This commit is contained in:
parent
caadb52f64
commit
8c1968c863
2 changed files with 153 additions and 1 deletions
|
|
@ -32,7 +32,13 @@ pub async fn dispatch_build_finished(
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Email notification
|
// 4. GitLab commit status
|
||||||
|
if let (Some(url), Some(token)) = (&config.gitlab_url, &config.gitlab_token) {
|
||||||
|
set_gitlab_status(url, token, &project.repository_url, commit_hash, build)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Email notification
|
||||||
if let Some(ref email_config) = config.email
|
if let Some(ref email_config) = config.email
|
||||||
&& (!email_config.on_failure_only || build.status == BuildStatus::Failed)
|
&& (!email_config.on_failure_only || build.status == BuildStatus::Failed)
|
||||||
{
|
{
|
||||||
|
|
@ -183,6 +189,67 @@ async fn set_gitea_status(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn set_gitlab_status(
|
||||||
|
base_url: &str,
|
||||||
|
token: &str,
|
||||||
|
repo_url: &str,
|
||||||
|
commit: &str,
|
||||||
|
build: &Build,
|
||||||
|
) {
|
||||||
|
// Parse project path from URL
|
||||||
|
let project_path = match parse_gitlab_project(repo_url, base_url) {
|
||||||
|
Some(p) => p,
|
||||||
|
None => {
|
||||||
|
warn!("Cannot parse GitLab project from {repo_url}");
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// GitLab uses different state names
|
||||||
|
let (state, description) = match build.status {
|
||||||
|
BuildStatus::Completed => ("success", "Build succeeded"),
|
||||||
|
BuildStatus::Failed => ("failed", "Build failed"),
|
||||||
|
BuildStatus::Running => ("running", "Build in progress"),
|
||||||
|
BuildStatus::Pending => ("pending", "Build queued"),
|
||||||
|
BuildStatus::Cancelled => ("canceled", "Build cancelled"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// URL-encode the project path for the API
|
||||||
|
let encoded_project = urlencoding::encode(&project_path);
|
||||||
|
let url = format!(
|
||||||
|
"{}/api/v4/projects/{}/statuses/{}",
|
||||||
|
base_url.trim_end_matches('/'),
|
||||||
|
encoded_project,
|
||||||
|
commit
|
||||||
|
);
|
||||||
|
|
||||||
|
let body = serde_json::json!({
|
||||||
|
"state": state,
|
||||||
|
"description": description,
|
||||||
|
"name": format!("fc/{}", build.job_name),
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
match client
|
||||||
|
.post(&url)
|
||||||
|
.header("PRIVATE-TOKEN", token)
|
||||||
|
.json(&body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(resp) => {
|
||||||
|
if !resp.status().is_success() {
|
||||||
|
let status = resp.status();
|
||||||
|
let text = resp.text().await.unwrap_or_default();
|
||||||
|
warn!("GitLab status API returned {status}: {text}");
|
||||||
|
} else {
|
||||||
|
info!(build_id = %build.id, "Set GitLab commit status: {state}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => error!("GitLab status API request failed: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_github_repo(url: &str) -> Option<(String, String)> {
|
fn parse_github_repo(url: &str) -> Option<(String, String)> {
|
||||||
// Handle https://github.com/owner/repo.git or git@github.com:owner/repo.git
|
// Handle https://github.com/owner/repo.git or git@github.com:owner/repo.git
|
||||||
let url = url.trim_end_matches(".git");
|
let url = url.trim_end_matches(".git");
|
||||||
|
|
@ -216,6 +283,23 @@ fn parse_gitea_repo(
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_gitlab_project(repo_url: &str, base_url: &str) -> Option<String> {
|
||||||
|
let url = repo_url.trim_end_matches(".git");
|
||||||
|
let base = base_url.trim_end_matches('/');
|
||||||
|
if let Some(rest) = url.strip_prefix(&format!("{base}/")) {
|
||||||
|
return Some(rest.to_string());
|
||||||
|
}
|
||||||
|
// Also try without scheme match (e.g., https vs git@)
|
||||||
|
if let (Some(at_pos), Some(colon_pos)) = (
|
||||||
|
url.find('@'),
|
||||||
|
url.find('@').and_then(|p| url[p..].find(':')),
|
||||||
|
) {
|
||||||
|
let path = &url[at_pos + colon_pos + 1..];
|
||||||
|
return Some(path.to_string());
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
async fn send_email_notification(
|
async fn send_email_notification(
|
||||||
config: &EmailConfig,
|
config: &EmailConfig,
|
||||||
build: &Build,
|
build: &Build,
|
||||||
|
|
@ -311,3 +395,68 @@ async fn send_email_notification(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_github_repo_https() {
|
||||||
|
let result = parse_github_repo("https://github.com/owner/repo.git");
|
||||||
|
assert_eq!(result, Some(("owner".to_string(), "repo".to_string())));
|
||||||
|
|
||||||
|
let result = parse_github_repo("https://github.com/owner/repo");
|
||||||
|
assert_eq!(result, Some(("owner".to_string(), "repo".to_string())));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_github_repo_ssh() {
|
||||||
|
let result = parse_github_repo("git@github.com:owner/repo.git");
|
||||||
|
assert_eq!(result, Some(("owner".to_string(), "repo".to_string())));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_github_repo_invalid() {
|
||||||
|
assert_eq!(parse_github_repo("https://gitlab.com/owner/repo"), None);
|
||||||
|
assert_eq!(parse_github_repo("invalid-url"), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_gitea_repo() {
|
||||||
|
let result = parse_gitea_repo(
|
||||||
|
"https://gitea.example.com/owner/repo.git",
|
||||||
|
"https://gitea.example.com",
|
||||||
|
);
|
||||||
|
assert_eq!(result, Some(("owner".to_string(), "repo".to_string())));
|
||||||
|
|
||||||
|
let result = parse_gitea_repo(
|
||||||
|
"https://gitea.example.com/owner/repo",
|
||||||
|
"https://gitea.example.com/",
|
||||||
|
);
|
||||||
|
assert_eq!(result, Some(("owner".to_string(), "repo".to_string())));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_gitlab_project() {
|
||||||
|
let result = parse_gitlab_project(
|
||||||
|
"https://gitlab.com/group/subgroup/repo.git",
|
||||||
|
"https://gitlab.com",
|
||||||
|
);
|
||||||
|
assert_eq!(result, Some("group/subgroup/repo".to_string()));
|
||||||
|
|
||||||
|
let result = parse_gitlab_project(
|
||||||
|
"https://gitlab.com/owner/repo",
|
||||||
|
"https://gitlab.com/",
|
||||||
|
);
|
||||||
|
assert_eq!(result, Some("owner/repo".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_gitlab_project_ssh() {
|
||||||
|
let result = parse_gitlab_project(
|
||||||
|
"git@gitlab.com:group/repo.git",
|
||||||
|
"https://gitlab.com",
|
||||||
|
);
|
||||||
|
assert_eq!(result, Some("group/repo".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ fn build_app(pool: sqlx::PgPool) -> axum::Router {
|
||||||
pool,
|
pool,
|
||||||
config,
|
config,
|
||||||
sessions: std::sync::Arc::new(dashmap::DashMap::new()),
|
sessions: std::sync::Arc::new(dashmap::DashMap::new()),
|
||||||
|
http_client: reqwest::Client::new(),
|
||||||
};
|
};
|
||||||
fc_server::routes::router(state, &server_config)
|
fc_server::routes::router(state, &server_config)
|
||||||
}
|
}
|
||||||
|
|
@ -54,6 +55,7 @@ async fn test_router_no_duplicate_routes() {
|
||||||
pool,
|
pool,
|
||||||
config,
|
config,
|
||||||
sessions: std::sync::Arc::new(dashmap::DashMap::new()),
|
sessions: std::sync::Arc::new(dashmap::DashMap::new()),
|
||||||
|
http_client: reqwest::Client::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let _app = fc_server::routes::router(state, &server_config);
|
let _app = fc_server::routes::router(state, &server_config);
|
||||||
|
|
@ -68,6 +70,7 @@ fn build_app_with_config(
|
||||||
pool,
|
pool,
|
||||||
config,
|
config,
|
||||||
sessions: std::sync::Arc::new(dashmap::DashMap::new()),
|
sessions: std::sync::Arc::new(dashmap::DashMap::new()),
|
||||||
|
http_client: reqwest::Client::new(),
|
||||||
};
|
};
|
||||||
fc_server::routes::router(state, &server_config)
|
fc_server::routes::router(state, &server_config)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue