2024-05-25 11:25:13 +01:00
|
|
|
//
|
2024-06-01 14:49:28 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
|
|
|
|
2024-05-25 11:25:13 +01:00
|
|
|
mod webhook;
|
|
|
|
|
2024-08-06 16:15:56 +01:00
|
|
|
use std::borrow::ToOwned;
|
|
|
|
|
2024-07-25 09:02:43 +01:00
|
|
|
use git_next_core::{
|
2024-07-31 07:20:42 +01:00
|
|
|
self as core,
|
|
|
|
git::{self, forge::commit::Status},
|
|
|
|
server::RepoListenUrl,
|
|
|
|
ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId,
|
2024-07-25 09:02:43 +01:00
|
|
|
};
|
2024-05-23 16:50:36 +01:00
|
|
|
|
2024-04-16 22:21:55 +01:00
|
|
|
use kxio::network::{self, Network};
|
2024-05-25 11:25:13 +01:00
|
|
|
use tracing::warn;
|
2024-04-16 22:21:55 +01:00
|
|
|
|
2024-05-04 12:37:35 +01:00
|
|
|
#[derive(Clone, Debug)]
|
2024-05-23 19:36:05 +01:00
|
|
|
pub struct ForgeJo {
|
2024-05-23 16:19:28 +01:00
|
|
|
repo_details: git::RepoDetails,
|
2024-04-16 22:21:55 +01:00
|
|
|
net: Network,
|
|
|
|
}
|
2024-05-23 19:36:05 +01:00
|
|
|
impl ForgeJo {
|
2024-08-06 16:15:56 +01:00
|
|
|
#[must_use]
|
2024-05-26 09:44:56 +01:00
|
|
|
pub const fn new(repo_details: git::RepoDetails, net: Network) -> Self {
|
|
|
|
Self { repo_details, net }
|
2024-04-16 22:21:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
2024-05-23 19:36:05 +01:00
|
|
|
impl git::ForgeLike for ForgeJo {
|
2024-06-19 07:03:08 +01:00
|
|
|
fn duplicate(&self) -> Box<dyn git::ForgeLike> {
|
|
|
|
Box::new(self.clone())
|
|
|
|
}
|
2024-05-25 11:25:13 +01:00
|
|
|
fn name(&self) -> String {
|
2024-04-16 22:21:55 +01:00
|
|
|
"forgejo".to_string()
|
|
|
|
}
|
|
|
|
|
2024-07-25 09:02:43 +01:00
|
|
|
fn is_message_authorised(&self, msg: &ForgeNotification, expected: &WebhookAuth) -> bool {
|
2024-05-25 11:25:13 +01:00
|
|
|
let authorization = msg.header("authorization");
|
|
|
|
tracing::info!(?authorization, %expected, "is message authorised?");
|
|
|
|
authorization
|
2024-08-06 16:15:56 +01:00
|
|
|
.and_then(|header| header.strip_prefix("Basic ").map(ToOwned::to_owned))
|
2024-07-25 09:02:43 +01:00
|
|
|
.and_then(|value| WebhookAuth::try_new(value.as_str()).ok())
|
2024-08-06 16:15:56 +01:00
|
|
|
.is_some_and(|auth| &auth == expected)
|
2024-05-25 11:25:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_webhook_body(
|
|
|
|
&self,
|
2024-07-25 09:02:43 +01:00
|
|
|
body: &core::webhook::forge_notification::Body,
|
|
|
|
) -> git::forge::webhook::Result<core::webhook::Push> {
|
2024-05-25 11:25:13 +01:00
|
|
|
webhook::parse_body(body)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn commit_status(&self, commit: &git::Commit) -> Status {
|
2024-04-16 22:21:55 +01:00
|
|
|
let repo_details = &self.repo_details;
|
2024-05-15 07:55:05 +01:00
|
|
|
let hostname = &repo_details.forge.hostname();
|
2024-04-20 20:49:38 +01:00
|
|
|
let repo_path = &repo_details.repo_path;
|
2024-05-15 07:55:05 +01:00
|
|
|
let api_token = &repo_details.forge.token();
|
2024-04-16 22:21:55 +01:00
|
|
|
use secrecy::ExposeSecret;
|
|
|
|
let token = api_token.expose_secret();
|
|
|
|
let url = network::NetUrl::new(format!(
|
2024-04-20 20:49:38 +01:00
|
|
|
"https://{hostname}/api/v1/repos/{repo_path}/commits/{commit}/status?token={token}"
|
2024-04-16 22:21:55 +01:00
|
|
|
));
|
|
|
|
|
|
|
|
let request = network::NetRequest::new(
|
|
|
|
network::RequestMethod::Get,
|
|
|
|
url,
|
|
|
|
network::NetRequestHeaders::new(),
|
|
|
|
network::RequestBody::None,
|
|
|
|
network::ResponseType::Json,
|
|
|
|
None,
|
|
|
|
network::NetRequestLogging::None,
|
|
|
|
);
|
|
|
|
let result = self.net.get::<CombinedStatus>(request).await;
|
|
|
|
match result {
|
2024-06-06 17:45:33 +01:00
|
|
|
Ok(response) => match response.response_body() {
|
|
|
|
Some(status) => match status.state {
|
|
|
|
ForgejoState::Success => Status::Pass,
|
2024-08-06 16:15:56 +01:00
|
|
|
ForgejoState::Pending | ForgejoState::Blank => Status::Pending,
|
|
|
|
ForgejoState::Failure | ForgejoState::Error => Status::Fail,
|
2024-06-06 17:45:33 +01:00
|
|
|
},
|
|
|
|
None => {
|
|
|
|
#[cfg(not(tarpaulin_include))]
|
|
|
|
unreachable!(); // response.response_body() is always Some when
|
|
|
|
// request responseType::Json
|
2024-04-16 22:21:55 +01:00
|
|
|
}
|
2024-06-06 17:45:33 +01:00
|
|
|
},
|
2024-04-16 22:21:55 +01:00
|
|
|
Err(e) => {
|
2024-05-25 11:25:13 +01:00
|
|
|
warn!(?e, "Failed to get commit status");
|
|
|
|
Status::Pending // assume issue is transient and allow retry
|
2024-04-16 22:21:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-05-25 11:25:13 +01:00
|
|
|
|
|
|
|
async fn list_webhooks(
|
|
|
|
&self,
|
2024-07-31 07:20:42 +01:00
|
|
|
repo_listen_url: &RepoListenUrl,
|
2024-07-25 09:02:43 +01:00
|
|
|
) -> git::forge::webhook::Result<Vec<WebhookId>> {
|
2024-07-31 07:20:42 +01:00
|
|
|
webhook::list(&self.repo_details, repo_listen_url, &self.net).await
|
2024-05-25 11:25:13 +01:00
|
|
|
}
|
|
|
|
|
2024-07-25 09:02:43 +01:00
|
|
|
async fn unregister_webhook(&self, webhook_id: &WebhookId) -> git::forge::webhook::Result<()> {
|
2024-05-25 11:25:13 +01:00
|
|
|
webhook::unregister(webhook_id, &self.repo_details, &self.net).await
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn register_webhook(
|
|
|
|
&self,
|
2024-07-31 07:20:42 +01:00
|
|
|
repo_listen_url: &RepoListenUrl,
|
2024-07-25 09:02:43 +01:00
|
|
|
) -> git::forge::webhook::Result<RegisteredWebhook> {
|
2024-07-31 07:20:42 +01:00
|
|
|
webhook::register(&self.repo_details, repo_listen_url, &self.net).await
|
2024-05-25 11:25:13 +01:00
|
|
|
}
|
2024-04-16 22:21:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
2024-05-25 11:25:13 +01:00
|
|
|
struct CombinedStatus {
|
|
|
|
pub state: ForgejoState,
|
2024-04-16 22:21:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
2024-05-25 11:25:13 +01:00
|
|
|
enum ForgejoState {
|
2024-04-16 22:21:55 +01:00
|
|
|
#[serde(rename = "success")]
|
|
|
|
Success,
|
|
|
|
#[serde(rename = "pending")]
|
|
|
|
Pending,
|
|
|
|
#[serde(rename = "failure")]
|
|
|
|
Failure,
|
|
|
|
#[serde(rename = "error")]
|
|
|
|
Error,
|
|
|
|
#[serde(rename = "")]
|
|
|
|
Blank,
|
|
|
|
}
|