Add configurable event publishing
This commit is contained in:
@ -18,14 +18,38 @@ use std::{collections::HashMap, fmt, fs::File, io::BufReader};
|
||||
|
||||
use anyhow::Context;
|
||||
use matrix_sdk::ruma::{OwnedRoomOrAliasId, OwnedUserId, RoomOrAliasId, UserId};
|
||||
use regex::Regex;
|
||||
use serde::de;
|
||||
|
||||
use crate::event::{IssueAction, MergeRequestAction, PipelineStatus};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "name", rename_all = "snake_case")]
|
||||
pub enum PublishEvent {
|
||||
Push {
|
||||
#[serde(default)]
|
||||
#[serde(with = "serde_regex")]
|
||||
branches: Option<Vec<Regex>>,
|
||||
},
|
||||
TagPush,
|
||||
Issues {
|
||||
actions: Option<Vec<IssueAction>>,
|
||||
},
|
||||
MergeRequest {
|
||||
actions: Option<Vec<MergeRequestAction>>,
|
||||
},
|
||||
Pipeline {
|
||||
statuses: Option<Vec<PipelineStatus>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RepoConfig {
|
||||
pub token: String,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deser_optional_room_or_alias_id")]
|
||||
pub room: Option<OwnedRoomOrAliasId>,
|
||||
pub publish_events: Option<Vec<PublishEvent>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -39,6 +63,7 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deser_optional_room_or_alias_id")]
|
||||
pub default_room: Option<OwnedRoomOrAliasId>,
|
||||
pub default_publish_events: Option<Vec<PublishEvent>>,
|
||||
pub repo_configs: HashMap<String, RepoConfig>, // key is repo url without scheme; e.g.
|
||||
// gitlab.xfce.org/xfce/xfdesktop
|
||||
}
|
||||
|
316
src/event.rs
316
src/event.rs
@ -14,7 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use crate::config::PublishEvent;
|
||||
|
||||
pub trait GitlabEventExt {
|
||||
fn should_publish(&self, publish_events: &Vec<PublishEvent>) -> bool;
|
||||
fn project(&self) -> &Project;
|
||||
fn r#ref(&self) -> Option<&str>;
|
||||
fn user(&self) -> &str;
|
||||
@ -49,37 +54,39 @@ pub struct Commit {
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MergeRequestAction {
|
||||
#[serde(rename = "open")]
|
||||
Opened,
|
||||
#[serde(rename = "close")]
|
||||
Closed,
|
||||
#[serde(rename = "reopen")]
|
||||
Reopened,
|
||||
#[serde(rename = "update")]
|
||||
Updated,
|
||||
#[serde(rename = "approved")]
|
||||
Approved,
|
||||
#[serde(rename = "unapproved")]
|
||||
Unapproved,
|
||||
#[serde(rename = "merge")]
|
||||
Open,
|
||||
Close,
|
||||
Reopen,
|
||||
Update,
|
||||
Approve,
|
||||
Unapprove,
|
||||
Merged,
|
||||
Approval,
|
||||
Unapproval,
|
||||
#[serde(other)]
|
||||
Other,
|
||||
}
|
||||
|
||||
impl MergeRequestAction {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
MergeRequestAction::Opened => "opened",
|
||||
MergeRequestAction::Closed => "closed",
|
||||
MergeRequestAction::Reopened => "reopened",
|
||||
MergeRequestAction::Updated => "updated",
|
||||
MergeRequestAction::Approved => "approved",
|
||||
MergeRequestAction::Unapproved => "unapproved",
|
||||
MergeRequestAction::Merged => "merged",
|
||||
MergeRequestAction::Other => "other",
|
||||
}
|
||||
impl fmt::Display for MergeRequestAction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
MergeRequestAction::Open => "opened",
|
||||
MergeRequestAction::Close => "closed",
|
||||
MergeRequestAction::Reopen => "reopened",
|
||||
MergeRequestAction::Update => "updated",
|
||||
MergeRequestAction::Approve => "approved",
|
||||
MergeRequestAction::Unapprove => "unapproved",
|
||||
MergeRequestAction::Approval => "approval",
|
||||
MergeRequestAction::Unapproval => "unapproval",
|
||||
MergeRequestAction::Merged => "merged",
|
||||
MergeRequestAction::Other => "other",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,19 +103,43 @@ pub struct MergeRequestObjectAttributes {
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PipelineStatus {
|
||||
#[serde(rename = "failed")]
|
||||
Created,
|
||||
WaitingForResource,
|
||||
Preparing,
|
||||
Pending,
|
||||
Running,
|
||||
Success,
|
||||
Failed,
|
||||
Canceled,
|
||||
Skipped,
|
||||
Manual,
|
||||
Scheduled,
|
||||
#[serde(other)]
|
||||
Other,
|
||||
}
|
||||
|
||||
impl PipelineStatus {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
PipelineStatus::Failed => "failed",
|
||||
PipelineStatus::Other => "other",
|
||||
}
|
||||
impl fmt::Display for PipelineStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
PipelineStatus::Created => "created",
|
||||
PipelineStatus::WaitingForResource => "waiting for resource",
|
||||
PipelineStatus::Preparing => "preparing",
|
||||
PipelineStatus::Pending => "pending",
|
||||
PipelineStatus::Running => "running",
|
||||
PipelineStatus::Success => "succeeded",
|
||||
PipelineStatus::Failed => "failed",
|
||||
PipelineStatus::Canceled => "canceled",
|
||||
PipelineStatus::Skipped => "skipped",
|
||||
PipelineStatus::Manual => "manual",
|
||||
PipelineStatus::Scheduled => "scheduled",
|
||||
PipelineStatus::Other => "other",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,37 +157,38 @@ pub struct PipelineMergeRequest {
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum IssueAction {
|
||||
#[serde(rename = "open")]
|
||||
Opened,
|
||||
#[serde(rename = "close")]
|
||||
Closed,
|
||||
#[serde(rename = "reopen")]
|
||||
Reopened,
|
||||
#[serde(rename = "update")]
|
||||
Updated,
|
||||
Open,
|
||||
Close,
|
||||
Reopen,
|
||||
Update,
|
||||
#[serde(other)]
|
||||
Other,
|
||||
}
|
||||
|
||||
impl IssueAction {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
IssueAction::Opened => "opened",
|
||||
IssueAction::Closed => "closed",
|
||||
IssueAction::Reopened => "reopened",
|
||||
IssueAction::Updated => "updated",
|
||||
IssueAction::Other => "other",
|
||||
}
|
||||
impl fmt::Display for IssueAction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
IssueAction::Open => "opened",
|
||||
IssueAction::Close => "closed",
|
||||
IssueAction::Reopen => "reopened",
|
||||
IssueAction::Update => "updated",
|
||||
IssueAction::Other => "other",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct IssueObjectAttributes {
|
||||
id: u32,
|
||||
title: String,
|
||||
action: IssueAction,
|
||||
url: String,
|
||||
pub id: u32,
|
||||
pub title: String,
|
||||
pub action: IssueAction,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -222,7 +254,74 @@ pub enum GitlabEvent {
|
||||
Other,
|
||||
}
|
||||
|
||||
macro_rules! find_publish_event {
|
||||
($cfgs:expr, $case:pat) => {
|
||||
$cfgs.iter().find(|cfg| match cfg {
|
||||
$case => true,
|
||||
_ => false,
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
impl GitlabEventExt for GitlabEvent {
|
||||
fn should_publish(&self, publish_events: &Vec<PublishEvent>) -> bool {
|
||||
match self {
|
||||
GitlabEvent::Push { r#ref, .. } => {
|
||||
if let Some(PublishEvent::Push { branches }) =
|
||||
find_publish_event!(publish_events, PublishEvent::Push { .. })
|
||||
{
|
||||
match branches {
|
||||
None => true,
|
||||
Some(branches) => {
|
||||
let refname = parse_ref(r#ref);
|
||||
branches.iter().find(|branch| branch.find(&refname).is_some()).is_some()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
GitlabEvent::TagPush { .. } => find_publish_event!(publish_events, PublishEvent::TagPush { .. }).is_some(),
|
||||
GitlabEvent::Issue { object_attributes, .. } => {
|
||||
if let Some(PublishEvent::Issues { actions }) =
|
||||
find_publish_event!(publish_events, PublishEvent::Issues { .. })
|
||||
{
|
||||
match actions {
|
||||
None => true,
|
||||
Some(actions) => actions.contains(&object_attributes.action),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
GitlabEvent::MergeRequest { object_attributes, .. } => {
|
||||
if let Some(PublishEvent::MergeRequest { actions }) =
|
||||
find_publish_event!(publish_events, PublishEvent::MergeRequest { .. })
|
||||
{
|
||||
match actions {
|
||||
None => true,
|
||||
Some(actions) => actions.contains(&object_attributes.action),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
GitlabEvent::Pipeline { object_attributes, .. } => {
|
||||
if let Some(PublishEvent::Pipeline { statuses }) =
|
||||
find_publish_event!(publish_events, PublishEvent::Pipeline { .. })
|
||||
{
|
||||
match statuses {
|
||||
None => true,
|
||||
Some(statuses) => statuses.contains(&object_attributes.status),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
GitlabEvent::Other => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn project(&self) -> &Project {
|
||||
match self {
|
||||
GitlabEvent::Push { project, .. } => &project,
|
||||
@ -307,9 +406,7 @@ impl GitlabEventExt for GitlabEvent {
|
||||
if object_attributes.action != IssueAction::Other {
|
||||
let title = format!(
|
||||
"Issue #{} **{}**: {}",
|
||||
object_attributes.id,
|
||||
object_attributes.action.as_str(),
|
||||
object_attributes.title
|
||||
object_attributes.id, object_attributes.action, object_attributes.title
|
||||
);
|
||||
vec![markdown_link(&title, &object_attributes.url)]
|
||||
} else {
|
||||
@ -320,9 +417,7 @@ impl GitlabEventExt for GitlabEvent {
|
||||
if object_attributes.action != MergeRequestAction::Other {
|
||||
let title = format!(
|
||||
"MR !{} **{}**: {}",
|
||||
object_attributes.iid,
|
||||
object_attributes.action.as_str(),
|
||||
object_attributes.title
|
||||
object_attributes.iid, object_attributes.action, object_attributes.title
|
||||
);
|
||||
vec![markdown_link(&title, &object_attributes.url)]
|
||||
} else {
|
||||
@ -341,10 +436,9 @@ impl GitlabEventExt for GitlabEvent {
|
||||
.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),
|
||||
);
|
||||
.fold(format!("Pipeline **{}**", object_attributes.status), |accum, title| {
|
||||
format!("{}: {}", accum, title)
|
||||
});
|
||||
vec![markdown_link(&title, &object_attributes.url)]
|
||||
} else {
|
||||
vec![]
|
||||
@ -376,6 +470,7 @@ pub fn parse_ref(r#ref: &str) -> String {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use regex::Regex;
|
||||
use std::{fs::File, io::BufReader};
|
||||
|
||||
fn load_test_data(name: &str) -> anyhow::Result<GitlabEvent> {
|
||||
@ -389,7 +484,7 @@ mod test {
|
||||
pub fn parse_push_event() -> anyhow::Result<()> {
|
||||
let event = load_test_data("push-event")?;
|
||||
|
||||
match event {
|
||||
match &event {
|
||||
GitlabEvent::Push {
|
||||
event_name,
|
||||
before,
|
||||
@ -412,11 +507,32 @@ mod test {
|
||||
assert_eq!(project.namespace, "Mike");
|
||||
assert_eq!(repository.name, "Diaspora");
|
||||
assert_eq!(repository.url, "git@example.com:mike/diaspora.git");
|
||||
assert_eq!(total_commits_count, 4);
|
||||
assert_eq!(*total_commits_count, 4);
|
||||
}
|
||||
_ => panic!("not a push event"),
|
||||
};
|
||||
|
||||
let publish_events = vec![PublishEvent::Push { branches: None }];
|
||||
assert!(event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::Push {
|
||||
branches: Some(vec![Regex::new(r"^master$").unwrap()]),
|
||||
}];
|
||||
assert!(event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::Push {
|
||||
branches: Some(vec![Regex::new(r"^mas").unwrap()]),
|
||||
}];
|
||||
assert!(event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::Push {
|
||||
branches: Some(vec![Regex::new(r"^foobar$").unwrap()]),
|
||||
}];
|
||||
assert!(!event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::Pipeline { statuses: None }];
|
||||
assert!(!event.should_publish(&publish_events));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -424,7 +540,7 @@ mod test {
|
||||
pub fn parse_tag_push_event() -> anyhow::Result<()> {
|
||||
let event = load_test_data("tag-push-event")?;
|
||||
|
||||
match event {
|
||||
match &event {
|
||||
GitlabEvent::TagPush {
|
||||
event_name,
|
||||
before,
|
||||
@ -446,11 +562,17 @@ mod test {
|
||||
assert_eq!(project.name, "Example");
|
||||
assert_eq!(project.namespace, "Jsmith");
|
||||
assert_eq!(repository.name, "Example");
|
||||
assert_eq!(total_commits_count, 0);
|
||||
assert_eq!(*total_commits_count, 0);
|
||||
}
|
||||
_ => panic!("not a tag push event"),
|
||||
};
|
||||
|
||||
let publish_events = vec![PublishEvent::TagPush];
|
||||
assert!(event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::Push { branches: None }];
|
||||
assert!(!event.should_publish(&publish_events));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -458,7 +580,7 @@ mod test {
|
||||
pub fn parse_issue_event() -> anyhow::Result<()> {
|
||||
let event = load_test_data("issue-event")?;
|
||||
|
||||
match event {
|
||||
match &event {
|
||||
GitlabEvent::Issue {
|
||||
user,
|
||||
object_attributes,
|
||||
@ -466,11 +588,27 @@ mod test {
|
||||
} => {
|
||||
assert_eq!(user.name, "Administrator");
|
||||
assert_eq!(object_attributes.id, 301);
|
||||
assert_eq!(object_attributes.action, IssueAction::Opened);
|
||||
assert_eq!(object_attributes.action, IssueAction::Open);
|
||||
}
|
||||
_ => panic!("not an issue event"),
|
||||
}
|
||||
|
||||
let publish_events = vec![PublishEvent::Issues { actions: None }];
|
||||
assert!(event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::Issues {
|
||||
actions: Some(vec![IssueAction::Open]),
|
||||
}];
|
||||
assert!(event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::Issues {
|
||||
actions: Some(vec![IssueAction::Close]),
|
||||
}];
|
||||
assert!(!event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::Push { branches: None }];
|
||||
assert!(!event.should_publish(&publish_events));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -478,19 +616,35 @@ mod test {
|
||||
pub fn parse_merge_request_event() -> anyhow::Result<()> {
|
||||
let event = load_test_data("merge-request-event")?;
|
||||
|
||||
match event {
|
||||
match &event {
|
||||
GitlabEvent::MergeRequest {
|
||||
user,
|
||||
object_attributes,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(user.name, "Administrator");
|
||||
assert_eq!(object_attributes.action, MergeRequestAction::Opened);
|
||||
assert_eq!(object_attributes.action, MergeRequestAction::Open);
|
||||
assert_eq!(object_attributes.title, "MS-Viewport");
|
||||
}
|
||||
_ => panic!("not a merge request event"),
|
||||
};
|
||||
|
||||
let publish_events = vec![PublishEvent::MergeRequest { actions: None }];
|
||||
assert!(event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::MergeRequest {
|
||||
actions: Some(vec![MergeRequestAction::Open]),
|
||||
}];
|
||||
assert!(event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::MergeRequest {
|
||||
actions: Some(vec![MergeRequestAction::Close]),
|
||||
}];
|
||||
assert!(!event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::Push { branches: None }];
|
||||
assert!(!event.should_publish(&publish_events));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -498,7 +652,7 @@ mod test {
|
||||
pub fn parse_pipeline_event() -> anyhow::Result<()> {
|
||||
let event = load_test_data("pipeline-event")?;
|
||||
|
||||
match event {
|
||||
match &event {
|
||||
GitlabEvent::Pipeline {
|
||||
object_attributes,
|
||||
merge_request,
|
||||
@ -508,12 +662,28 @@ mod test {
|
||||
assert_eq!(object_attributes.name, Some("Pipeline for branch: master".to_string()));
|
||||
assert_eq!(object_attributes.r#ref, "master");
|
||||
assert_eq!(object_attributes.status, PipelineStatus::Failed);
|
||||
assert_eq!(merge_request.unwrap().title, "Test");
|
||||
assert_eq!(merge_request.as_ref().unwrap().title, "Test");
|
||||
assert_eq!(user.name, "Administrator");
|
||||
}
|
||||
_ => panic!("not a pipeline event"),
|
||||
};
|
||||
|
||||
let publish_events = vec![PublishEvent::Pipeline { statuses: None }];
|
||||
assert!(event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::Pipeline {
|
||||
statuses: Some(vec![PipelineStatus::Failed]),
|
||||
}];
|
||||
assert!(event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::Pipeline {
|
||||
statuses: Some(vec![PipelineStatus::Success]),
|
||||
}];
|
||||
assert!(!event.should_publish(&publish_events));
|
||||
|
||||
let publish_events = vec![PublishEvent::Push { branches: None }];
|
||||
assert!(!event.should_publish(&publish_events));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
10
src/main.rs
10
src/main.rs
@ -215,8 +215,14 @@ async fn run() -> anyhow::Result<()> {
|
||||
} else {
|
||||
debug!("payload: {:?}", event);
|
||||
if let Some(room) = repo_config.room.as_ref().or(config.default_room.as_ref()) {
|
||||
if let Err(err) = event_tx.send((event, room.clone())).await {
|
||||
warn!("Failed to enqueue payload: {}", err);
|
||||
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 {
|
||||
|
Reference in New Issue
Block a user