Rearrange the code a bit
I'm prepping to make bebot do more things than just be a gitlab webhook handler, so I've moved the gitlab stuff into its own module (and some of the matrix helper functions too, for good measure). The config file also now puts all the gitlab-specific configuration under a 'gitlab' key.
This commit is contained in:
209
src/main.rs
209
src/main.rs
@@ -22,138 +22,26 @@ extern crate log;
|
||||
extern crate serde;
|
||||
|
||||
mod config;
|
||||
mod event;
|
||||
mod gitlab;
|
||||
mod gitlab_event;
|
||||
mod matrix;
|
||||
|
||||
use std::{env, net::IpAddr, process::exit, sync::Arc, time::Duration};
|
||||
use std::{env, net::IpAddr, process::exit, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use constant_time_eq::constant_time_eq;
|
||||
use event::{GitlabEvent, GitlabEventExt};
|
||||
use http::StatusCode;
|
||||
use matrix_sdk::{
|
||||
config::SyncSettings,
|
||||
room::Joined,
|
||||
ruma::{events::room::message::RoomMessageEventContent, OwnedRoomOrAliasId},
|
||||
BaseRoom, Client,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
use warp::Filter;
|
||||
|
||||
async fn build_sync_settings(matrix_client: &Client) -> SyncSettings {
|
||||
let mut settings = SyncSettings::default().timeout(Duration::from_secs(30));
|
||||
if let Some(token) = matrix_client.sync_token().await {
|
||||
settings = settings.token(token);
|
||||
}
|
||||
settings
|
||||
}
|
||||
|
||||
async fn matrix_connect(config: &config::Config) -> anyhow::Result<Client> {
|
||||
let client = Client::builder()
|
||||
.server_name(config.user_id.server_name())
|
||||
.user_agent(format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")))
|
||||
.build()
|
||||
.await?;
|
||||
client
|
||||
.login_username(&config.user_id, &config.password)
|
||||
.initial_device_display_name("Bebot")
|
||||
.send()
|
||||
.await?;
|
||||
info!("Connected to matrix as {}; waiting for first sync", config.user_id);
|
||||
|
||||
let settings = build_sync_settings(&client).await;
|
||||
client.sync_once(settings).await?;
|
||||
info!("First matrix sync complete");
|
||||
|
||||
let sync_client = client.clone();
|
||||
tokio::spawn(async move {
|
||||
let settings = build_sync_settings(&sync_client).await;
|
||||
if let Err(err) = sync_client.sync(settings).await {
|
||||
error!("Matrix sync failed: {}", err);
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
async fn ensure_matrix_room_joined(matrix_client: &Client, room_id: &OwnedRoomOrAliasId) -> anyhow::Result<Joined> {
|
||||
fn room_matches(a_room: &BaseRoom, our_room: &OwnedRoomOrAliasId) -> bool {
|
||||
let our_room_str = our_room.as_str();
|
||||
a_room.room_id().as_str() == our_room_str
|
||||
|| a_room
|
||||
.canonical_alias()
|
||||
.iter()
|
||||
.any(|alias| alias.as_str() == our_room_str)
|
||||
|| a_room.alt_aliases().iter().any(|alias| alias.as_str() == our_room_str)
|
||||
}
|
||||
|
||||
let mut room = matrix_client
|
||||
.joined_rooms()
|
||||
.iter()
|
||||
.find(|a_room| room_matches(a_room, room_id))
|
||||
.cloned();
|
||||
if room.is_none() {
|
||||
if let Some(invited) = matrix_client
|
||||
.invited_rooms()
|
||||
.iter()
|
||||
.find(|a_room| room_matches(a_room, room_id))
|
||||
{
|
||||
invited.accept_invitation().await?;
|
||||
} else {
|
||||
matrix_client.join_room_by_id_or_alias(room_id, &[]).await?;
|
||||
}
|
||||
let settings = build_sync_settings(matrix_client).await;
|
||||
matrix_client.sync_once(settings).await?;
|
||||
room = matrix_client
|
||||
.joined_rooms()
|
||||
.iter()
|
||||
.find(|a_room| room_matches(a_room, room_id))
|
||||
.cloned();
|
||||
}
|
||||
|
||||
room.ok_or_else(|| anyhow!("Unable to join room {}", room_id))
|
||||
}
|
||||
|
||||
fn build_gitlab_messages(event: &GitlabEvent) -> Vec<String> {
|
||||
let project = event.project();
|
||||
let refname = event.r#ref().map(event::parse_ref);
|
||||
event
|
||||
.titles()
|
||||
.iter()
|
||||
.map(|title| {
|
||||
format!(
|
||||
"\\[{}\\] {}*{}* {}",
|
||||
project.path_with_namespace,
|
||||
refname
|
||||
.as_ref()
|
||||
.map(|rn| format!("`{}` ", rn))
|
||||
.unwrap_or_else(|| "".to_string()),
|
||||
event.user(),
|
||||
title,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn handle_gitlab_event(
|
||||
event: GitlabEvent,
|
||||
room_id: &OwnedRoomOrAliasId,
|
||||
matrix_client: &Client,
|
||||
) -> anyhow::Result<()> {
|
||||
let room = ensure_matrix_room_joined(matrix_client, room_id).await?;
|
||||
for msg in build_gitlab_messages(&event) {
|
||||
debug!("Sending message to {}: {}", room_id, msg);
|
||||
let msg_content = RoomMessageEventContent::text_markdown(&msg);
|
||||
room.send(msg_content, None).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run() -> anyhow::Result<()> {
|
||||
let config_path = env::args()
|
||||
.nth(1)
|
||||
.ok_or_else(|| anyhow!("Config file should be passed as only parameter"))?;
|
||||
let config = Arc::new(config::load(config_path).await?);
|
||||
|
||||
let matrix_client = matrix::connect(&config).await.context("Failed to connect to Matrix")?;
|
||||
|
||||
let gitlab = gitlab::build_webhook_route(Arc::clone(&config), matrix_client)?;
|
||||
let routes = gitlab.with(warp::log("bebot"));
|
||||
|
||||
let addr = config
|
||||
.bind_address
|
||||
.as_ref()
|
||||
@@ -162,83 +50,6 @@ async fn run() -> anyhow::Result<()> {
|
||||
.parse::<IpAddr>()
|
||||
.context("Failed to parse bind_address")?;
|
||||
let port = config.bind_port.unwrap_or(3000);
|
||||
|
||||
let matrix_client = matrix_connect(&config).await.context("Failed to connect to Matrix")?;
|
||||
|
||||
let (event_tx, mut event_rx) = mpsc::channel::<(GitlabEvent, OwnedRoomOrAliasId)>(100);
|
||||
tokio::spawn(async move {
|
||||
while let Some((event, room)) = event_rx.recv().await {
|
||||
if let Err(err) = handle_gitlab_event(event, &room, &matrix_client).await {
|
||||
warn!("Failed to handle payload: {}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let gitlab_root_path = if let Some(url_prefix) = config.url_prefix.as_ref() {
|
||||
url_prefix.split('/').fold(warp::any().boxed(), |last, segment| {
|
||||
if segment.is_empty() {
|
||||
last
|
||||
} else {
|
||||
last.and(warp::path(segment.to_string())).boxed()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
warp::any().boxed()
|
||||
};
|
||||
|
||||
let gitlab = gitlab_root_path
|
||||
.and(warp::path!("hooks" / "gitlab"))
|
||||
.and(warp::post())
|
||||
.and(warp::header::<String>("x-gitlab-token"))
|
||||
.and(warp::body::json())
|
||||
.then(move |token: String, event: event::GitlabEvent| {
|
||||
let config = Arc::clone(&config);
|
||||
let event_tx = event_tx.clone();
|
||||
|
||||
async move {
|
||||
match event {
|
||||
GitlabEvent::Other => {
|
||||
warp::reply::with_status("Unsupported Gitlab event type", StatusCode::BAD_REQUEST)
|
||||
}
|
||||
_ => {
|
||||
let project = event.project();
|
||||
let config_key = project.web_url.replace("http://", "").replace("https://", "");
|
||||
if let Some(repo_config) = config.repo_configs.get(&config_key) {
|
||||
if !constant_time_eq(token.as_bytes(), repo_config.token.as_bytes()) {
|
||||
warn!("Invalid token for repo '{}'", config_key);
|
||||
warp::reply::with_status("Invalid token", StatusCode::FORBIDDEN)
|
||||
} else {
|
||||
debug!("payload: {:?}", event);
|
||||
if let Some(room) = repo_config.room.as_ref().or(config.default_room.as_ref()) {
|
||||
let publish_events = repo_config
|
||||
.publish_events
|
||||
.as_ref()
|
||||
.or(config.default_publish_events.as_ref());
|
||||
if publish_events.map(|ecs| event.should_publish(ecs)).unwrap_or(true) {
|
||||
if let Err(err) = event_tx.send((event, room.clone())).await {
|
||||
warn!("Failed to enqueue payload: {}", err);
|
||||
}
|
||||
}
|
||||
warp::reply::with_status("OK", StatusCode::OK)
|
||||
} else {
|
||||
info!("Channel not configured for repo '{}'", config_key);
|
||||
warp::reply::with_status(
|
||||
"Matrix room not configured for repo",
|
||||
StatusCode::NOT_FOUND,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("Repo '{}' unconfigured", config_key);
|
||||
warp::reply::with_status("Repo not configured", StatusCode::NOT_FOUND)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let routes = gitlab.with(warp::log("bebot"));
|
||||
|
||||
warp::serve(routes).run((addr, port)).await;
|
||||
|
||||
Ok(())
|
||||
|
Reference in New Issue
Block a user