Compare commits
7 Commits
bddff51ab3
...
1163d2a3f3
Author | SHA1 | Date | |
---|---|---|---|
1163d2a3f3 | |||
fcdb10a7ff | |||
33d3313927 | |||
eb5bdc3fd5 | |||
d74c30132c | |||
11be50d89f | |||
1dcba64095 |
10
Dockerfile
10
Dockerfile
@ -2,7 +2,15 @@ FROM rust:1.72-slim-bullseye AS builder
|
|||||||
|
|
||||||
WORKDIR /bebot-build
|
WORKDIR /bebot-build
|
||||||
|
|
||||||
COPY . ./
|
# Build and cache dependencies
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
RUN mkdir -p src && echo 'fn main() {}' > src/main.rs
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
# Build and link our app
|
||||||
|
RUN rm -rf src
|
||||||
|
COPY src ./src
|
||||||
|
RUN touch src/main.rs
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
|
|
||||||
FROM debian:bookworm-slim
|
FROM debian:bookworm-slim
|
||||||
|
58
scripts/set-webook.py
Executable file
58
scripts/set-webook.py
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import secrets
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def die(s):
|
||||||
|
print(s, file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
gitlab_token = os.getenv('GITLAB_TOKEN')
|
||||||
|
if gitlab_token is None:
|
||||||
|
die("GITLAB_TOKEN must be set in the environment")
|
||||||
|
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
die("Usage: {} GITLAB_INSTANCE HOOK_URL_BASE NAMESPACE/REPO\n\nExample: {} gitlab.example.com https://bot.example.com/bebot stuff/myrepo")
|
||||||
|
|
||||||
|
gitlab_instance = sys.argv[1]
|
||||||
|
hook_url = f"{sys.argv[2]}/hooks/gitlab"
|
||||||
|
repo_name = sys.argv[3]
|
||||||
|
|
||||||
|
gl_base = 'https://{}/api/v4/projects/{}/hooks'.format(
|
||||||
|
gitlab_instance,
|
||||||
|
re.sub('/', '%2F', repo_name)
|
||||||
|
)
|
||||||
|
headers = {
|
||||||
|
'authorization': 'Bearer {}'.format(gitlab_token),
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
"merge_requests_events": True,
|
||||||
|
"pipeline_events": True,
|
||||||
|
"push_events": True,
|
||||||
|
"tag_push_events": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = requests.get(gl_base, headers=headers)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
resp.raise_for_status()
|
||||||
|
existing = resp.json()
|
||||||
|
|
||||||
|
for hook in existing:
|
||||||
|
if hook['url'] == hook_url:
|
||||||
|
upd_url = '{}/{}'.format(gl_base, hook['id'])
|
||||||
|
resp = requests.put(upd_url, headers=headers, json=payload)
|
||||||
|
print("Updated existing hook")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
token = secrets.token_urlsafe(32)
|
||||||
|
payload["url"] = hook_url
|
||||||
|
payload["token"] = token
|
||||||
|
resp = requests.post(gl_base, headers=headers, json=payload)
|
||||||
|
resp.raise_for_status()
|
||||||
|
print(' "{}/{}":'.format(gitlab_instance, repo_name))
|
||||||
|
print(' token: "{}"'.format(token))
|
116
src/event.rs
116
src/event.rs
@ -2,8 +2,7 @@ pub trait GitlabEventExt {
|
|||||||
fn project(&self) -> &Project;
|
fn project(&self) -> &Project;
|
||||||
fn r#ref(&self) -> &str;
|
fn r#ref(&self) -> &str;
|
||||||
fn user(&self) -> &str;
|
fn user(&self) -> &str;
|
||||||
fn url(&self) -> String;
|
fn titles(&self) -> Vec<String>;
|
||||||
fn title(&self) -> String;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -14,9 +13,7 @@ pub struct User {
|
|||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
|
||||||
pub web_url: String,
|
pub web_url: String,
|
||||||
pub avatar_url: Option<String>,
|
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
pub path_with_namespace: String,
|
pub path_with_namespace: String,
|
||||||
pub default_branch: String,
|
pub default_branch: String,
|
||||||
@ -26,7 +23,6 @@ pub struct Project {
|
|||||||
pub struct Repository {
|
pub struct Repository {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub description: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -73,6 +69,7 @@ impl MergeRequestAction {
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct MergeRequestObjectAttributes {
|
pub struct MergeRequestObjectAttributes {
|
||||||
|
pub iid: u64,
|
||||||
pub target_branch: String,
|
pub target_branch: String,
|
||||||
pub source_branch: String,
|
pub source_branch: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
@ -161,7 +158,7 @@ pub enum GitlabEvent {
|
|||||||
#[serde(rename = "pipeline")]
|
#[serde(rename = "pipeline")]
|
||||||
Pipeline {
|
Pipeline {
|
||||||
object_attributes: PipelineObjectAttributes,
|
object_attributes: PipelineObjectAttributes,
|
||||||
merge_request: PipelineMergeRequest,
|
merge_request: Option<PipelineMergeRequest>,
|
||||||
user: User,
|
user: User,
|
||||||
project: Project,
|
project: Project,
|
||||||
},
|
},
|
||||||
@ -195,54 +192,101 @@ impl GitlabEventExt for GitlabEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn url(&self) -> String {
|
fn titles(&self) -> Vec<String> {
|
||||||
let url = match self {
|
|
||||||
GitlabEvent::Push { after, project, .. } => format!("{}/-/commits/{}", project.web_url, after),
|
|
||||||
GitlabEvent::TagPush {
|
|
||||||
r#ref,
|
|
||||||
checkout_sha,
|
|
||||||
project,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let refname = r#ref.split('/').into_iter().last().unwrap_or(checkout_sha);
|
|
||||||
format!("{}/-/tags/{}", project.web_url, refname)
|
|
||||||
}
|
|
||||||
GitlabEvent::MergeRequest { object_attributes, .. } => object_attributes.url.clone(),
|
|
||||||
GitlabEvent::Pipeline { object_attributes, .. } => object_attributes.url.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
url.replace("http://", "https://").to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn title(&self) -> String {
|
|
||||||
fn find_commit<'a>(commits: &'a Vec<Commit>, sha: &str) -> Option<&'a Commit> {
|
fn find_commit<'a>(commits: &'a Vec<Commit>, sha: &str) -> Option<&'a Commit> {
|
||||||
commits.iter().find(|commit| commit.id == sha)
|
commits.iter().find(|commit| commit.id == sha)
|
||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
GitlabEvent::Push { after, commits, .. } => find_commit(commits, &after)
|
GitlabEvent::Push {
|
||||||
.map(|commit| commit.title.clone())
|
after,
|
||||||
.unwrap_or_else(|| "New commit(s) pushed".to_string()),
|
project,
|
||||||
|
commits,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
const MAX_COMMITS: usize = 15; // TODO: make configurable
|
||||||
|
commits.iter().fold(Vec::new(), |mut titles, commit| {
|
||||||
|
if titles.len() < MAX_COMMITS {
|
||||||
|
titles.push(format!("[**pushed** {}]({})", commit.title, commit.url));
|
||||||
|
|
||||||
|
if titles.len() == MAX_COMMITS {
|
||||||
|
titles.push(format!(
|
||||||
|
"[**pushed** {} more commits]({}/-/compare/{}...{})",
|
||||||
|
commits.len() - MAX_COMMITS,
|
||||||
|
project.web_url,
|
||||||
|
commit.id,
|
||||||
|
after
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
titles
|
||||||
|
})
|
||||||
|
}
|
||||||
GitlabEvent::TagPush {
|
GitlabEvent::TagPush {
|
||||||
checkout_sha, commits, ..
|
r#ref,
|
||||||
} => find_commit(commits, &checkout_sha)
|
checkout_sha,
|
||||||
.map(|commit| commit.title.clone())
|
project,
|
||||||
.unwrap_or_else(|| "New tag pushed".to_string()),
|
commits,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let title = format!(
|
||||||
|
"**tagged** {}",
|
||||||
|
find_commit(commits, &checkout_sha)
|
||||||
|
.map(|commit| &commit.title)
|
||||||
|
.unwrap_or(checkout_sha)
|
||||||
|
);
|
||||||
|
let url = format!("{}/-/tags/{}", project.web_url, parse_ref(r#ref));
|
||||||
|
vec![markdown_link(&title, &url)]
|
||||||
|
}
|
||||||
GitlabEvent::MergeRequest { object_attributes, .. } => {
|
GitlabEvent::MergeRequest { object_attributes, .. } => {
|
||||||
format!("MR {}: {}", object_attributes.action.as_str(), object_attributes.title)
|
let title = format!(
|
||||||
|
"MR !{} **{}**: {}",
|
||||||
|
object_attributes.iid,
|
||||||
|
object_attributes.action.as_str(),
|
||||||
|
object_attributes.title
|
||||||
|
);
|
||||||
|
vec![markdown_link(&title, &object_attributes.url)]
|
||||||
}
|
}
|
||||||
GitlabEvent::Pipeline {
|
GitlabEvent::Pipeline {
|
||||||
object_attributes,
|
object_attributes,
|
||||||
merge_request,
|
merge_request,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let title = object_attributes.name.as_ref().unwrap_or(&merge_request.title);
|
let title = object_attributes
|
||||||
format!("Pipeline {}: {}", object_attributes.status.as_str(), title)
|
.name
|
||||||
|
.as_ref()
|
||||||
|
.map(|n| n.clone())
|
||||||
|
.or(merge_request.as_ref().map(|mr| mr.title.clone()))
|
||||||
|
.iter()
|
||||||
|
.fold(
|
||||||
|
format!("Pipeline **{}**", object_attributes.status.as_str()),
|
||||||
|
|accum, title| format!("{}: {}", accum, title),
|
||||||
|
);
|
||||||
|
vec![markdown_link(&title, &object_attributes.url)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn markdown_link(title: &String, url: &String) -> String {
|
||||||
|
format!("[{}]({})", title, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_ref(r#ref: &str) -> String {
|
||||||
|
if r#ref.starts_with("refs/") {
|
||||||
|
let parts = r#ref.split('/').collect::<Vec<_>>();
|
||||||
|
if parts.len() > 2 {
|
||||||
|
parts.into_iter().skip(2).collect::<Vec<_>>().join("/").to_string()
|
||||||
|
} else {
|
||||||
|
r#ref.to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r#ref.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
55
src/main.rs
55
src/main.rs
@ -20,6 +20,7 @@ use matrix_sdk::{
|
|||||||
ruma::{events::room::message::RoomMessageEventContent, OwnedRoomOrAliasId},
|
ruma::{events::room::message::RoomMessageEventContent, OwnedRoomOrAliasId},
|
||||||
BaseRoom, Client,
|
BaseRoom, Client,
|
||||||
};
|
};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
|
|
||||||
use crate::event::{MergeRequestAction, PipelineStatus};
|
use crate::event::{MergeRequestAction, PipelineStatus};
|
||||||
@ -104,22 +105,22 @@ async fn ensure_matrix_room_joined(matrix_client: &Client, room_id: &OwnedRoomOr
|
|||||||
room.ok_or_else(|| anyhow!("Unable to join room {}", room_id))
|
room.ok_or_else(|| anyhow!("Unable to join room {}", room_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_gitlab_message(event: &GitlabEvent) -> String {
|
fn build_gitlab_messages(event: &GitlabEvent) -> Vec<String> {
|
||||||
let project = event.project();
|
let project = event.project();
|
||||||
let refname = event
|
let refname = event::parse_ref(event.r#ref());
|
||||||
.r#ref()
|
event
|
||||||
.split('/')
|
.titles()
|
||||||
.last()
|
.iter()
|
||||||
.unwrap_or_else(|| event.r#ref())
|
.map(|title| {
|
||||||
.to_string();
|
format!(
|
||||||
format!(
|
"\\[{}\\] `{}` *{}* {}",
|
||||||
"*{}* {} **{}** [{}]({})",
|
project.path_with_namespace,
|
||||||
project.path_with_namespace,
|
refname,
|
||||||
refname,
|
event.user(),
|
||||||
event.user(),
|
title,
|
||||||
event.title(),
|
)
|
||||||
event.url()
|
})
|
||||||
)
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_gitlab_event(
|
async fn handle_gitlab_event(
|
||||||
@ -138,10 +139,11 @@ async fn handle_gitlab_event(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let room = ensure_matrix_room_joined(matrix_client, room_id).await?;
|
let room = ensure_matrix_room_joined(matrix_client, room_id).await?;
|
||||||
let msg = build_gitlab_message(&event);
|
for msg in build_gitlab_messages(&event) {
|
||||||
debug!("Sending message to {}: {}", room_id, msg);
|
debug!("Sending message to {}: {}", room_id, msg);
|
||||||
let msg_content = RoomMessageEventContent::text_markdown(&msg);
|
let msg_content = RoomMessageEventContent::text_markdown(&msg);
|
||||||
room.send(msg_content, None).await?;
|
room.send(msg_content, None).await?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +163,15 @@ async fn run() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let matrix_client = matrix_connect(&config).await.context("Failed to connect to Matrix")?;
|
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() {
|
let gitlab_root_path = if let Some(url_prefix) = config.url_prefix.as_ref() {
|
||||||
url_prefix.split('/').fold(warp::any().boxed(), |last, segment| {
|
url_prefix.split('/').fold(warp::any().boxed(), |last, segment| {
|
||||||
if segment.is_empty() {
|
if segment.is_empty() {
|
||||||
@ -180,7 +191,7 @@ async fn run() -> anyhow::Result<()> {
|
|||||||
.and(warp::body::json())
|
.and(warp::body::json())
|
||||||
.then(move |token: String, event: event::GitlabEvent| {
|
.then(move |token: String, event: event::GitlabEvent| {
|
||||||
let config = Arc::clone(&config);
|
let config = Arc::clone(&config);
|
||||||
let matrix_client = matrix_client.clone();
|
let event_tx = event_tx.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let project = event.project();
|
let project = event.project();
|
||||||
@ -192,8 +203,8 @@ async fn run() -> anyhow::Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
debug!("payload: {:?}", event);
|
debug!("payload: {:?}", event);
|
||||||
if let Some(room) = repo_config.room.as_ref().or(config.default_room.as_ref()) {
|
if let Some(room) = repo_config.room.as_ref().or(config.default_room.as_ref()) {
|
||||||
if let Err(err) = handle_gitlab_event(event, &room, &matrix_client).await {
|
if let Err(err) = event_tx.send((event, room.clone())).await {
|
||||||
warn!("Failed to handle payload: {}", err);
|
warn!("Failed to enqueue payload: {}", err);
|
||||||
}
|
}
|
||||||
warp::reply::with_status("OK", StatusCode::OK)
|
warp::reply::with_status("OK", StatusCode::OK)
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user