Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
7f73220753
commit
fdda383f5a
@ -1,15 +1,31 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{MatchedPath, State},
|
||||
http::Request,
|
||||
response::IntoResponse,
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_http::trace::TraceLayer;
|
||||
|
||||
use crate::SharedState;
|
||||
use crate::{
|
||||
services::{
|
||||
bot::{BotRequest, BotState},
|
||||
gitea::Repository,
|
||||
},
|
||||
SharedState,
|
||||
};
|
||||
|
||||
pub async fn serve_axum(state: &SharedState, host: &SocketAddr) -> Result<(), anyhow::Error> {
|
||||
tracing::info!("running webhook server");
|
||||
let app = Router::new()
|
||||
.route("/", get(root))
|
||||
.with_state(state.clone())
|
||||
.route("/webhooks/gitea", post(gitea_webhook))
|
||||
.with_state(state.to_owned())
|
||||
.layer(
|
||||
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
|
||||
// Log the matched route's path (with placeholders not filled in).
|
||||
@ -38,3 +54,85 @@ pub async fn serve_axum(state: &SharedState, host: &SocketAddr) -> Result<(), an
|
||||
async fn root() -> &'static str {
|
||||
"Hello, contractor!"
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct GiteaWebhookComment {
|
||||
body: String,
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct GiteaWebhookRepository {
|
||||
full_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum GiteaWebhook {
|
||||
Issue {
|
||||
comment: GiteaWebhookComment,
|
||||
repository: GiteaWebhookRepository,
|
||||
},
|
||||
}
|
||||
|
||||
pub enum ApiError {
|
||||
InternalError(anyhow::Error),
|
||||
}
|
||||
|
||||
impl IntoResponse for ApiError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
match self {
|
||||
ApiError::InternalError(e) => {
|
||||
tracing::error!("failed with internal error: {}", e);
|
||||
|
||||
(axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
|
||||
}
|
||||
}
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
async fn gitea_webhook(
|
||||
State(state): State<SharedState>,
|
||||
Json(json): Json<GiteaWebhook>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
tracing::info!(
|
||||
"called: {}",
|
||||
serde_json::to_string(&json)
|
||||
.context("failed to serialize webhook")
|
||||
.map_err(ApiError::InternalError)?
|
||||
);
|
||||
|
||||
let bot_req: BotRequest = json.try_into().map_err(ApiError::InternalError)?;
|
||||
|
||||
state
|
||||
.bot()
|
||||
.handle_request(bot_req)
|
||||
.await
|
||||
.map_err(ApiError::InternalError)?;
|
||||
|
||||
Ok("Hello, contractor!")
|
||||
}
|
||||
|
||||
impl TryFrom<GiteaWebhook> for BotRequest {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: GiteaWebhook) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GiteaWebhook::Issue {
|
||||
comment,
|
||||
repository,
|
||||
} => {
|
||||
let (owner, name) = repository.full_name.split_once('/').ok_or(anyhow::anyhow!(
|
||||
"{} did not contain a valid owner/repository",
|
||||
&repository.full_name
|
||||
))?;
|
||||
|
||||
Ok(BotRequest {
|
||||
repo: Repository {
|
||||
owner: owner.into(),
|
||||
name: name.into(),
|
||||
},
|
||||
command: comment.body,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ pub async fn serve_cron_jobs(state: &SharedState) -> Result<(), anyhow::Error> {
|
||||
loop {
|
||||
tracing::info!("running cronjobs");
|
||||
|
||||
todo!();
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_secs(10_000)).await;
|
||||
}
|
||||
Ok::<(), anyhow::Error>(())
|
||||
|
@ -1,2 +1,3 @@
|
||||
pub mod bot;
|
||||
pub mod gitea;
|
||||
pub mod reconciler;
|
||||
|
65
crates/contractor/src/services/bot.rs
Normal file
65
crates/contractor/src/services/bot.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use crate::SharedState;
|
||||
|
||||
use super::gitea::Repository;
|
||||
|
||||
pub struct Bot {
|
||||
command_name: String,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None, subcommand_required = true)]
|
||||
struct BotCommand {
|
||||
#[command(subcommand)]
|
||||
command: Option<BotCommands>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum BotCommands {
|
||||
Refresh {
|
||||
#[arg(long)]
|
||||
all: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Bot {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
command_name: std::env::var("CONTRACTOR_COMMAND_NAME").unwrap_or("contractor".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_request(&self, req: impl Into<BotRequest>) -> anyhow::Result<()> {
|
||||
let req: BotRequest = req.into();
|
||||
|
||||
if !req.command.starts_with(&self.command_name) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let cmd = BotCommand::parse_from(req.command.split_whitespace());
|
||||
|
||||
match cmd.command {
|
||||
Some(BotCommands::Refresh { all }) => {
|
||||
tracing::info!("triggering refresh for: {}, all: {}", req.repo, all);
|
||||
}
|
||||
None => {
|
||||
// TODO: Send back the help menu
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BotRequest {
|
||||
pub repo: Repository,
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
pub trait BotState {
|
||||
fn bot(&self) -> Bot {
|
||||
Bot::new()
|
||||
}
|
||||
}
|
||||
impl BotState for SharedState {}
|
@ -60,6 +60,8 @@ pub struct GiteaRepository {
|
||||
pub struct DefaultGiteaClient {
|
||||
url: String,
|
||||
token: String,
|
||||
|
||||
webhook_url: String,
|
||||
}
|
||||
|
||||
impl Default for DefaultGiteaClient {
|
||||
@ -72,6 +74,10 @@ impl Default for DefaultGiteaClient {
|
||||
token: std::env::var("GITEA_TOKEN")
|
||||
.context("GITEA_TOKEN should be set")
|
||||
.unwrap(),
|
||||
webhook_url: std::env::var("CONTRACTOR_URL")
|
||||
.context("CONTRACTOR_URL should be set")
|
||||
.map(|url| format!("{}/webhooks/gitea", url.trim_end_matches('/')))
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -287,17 +293,7 @@ impl DefaultGiteaClient {
|
||||
self.url, &repo.owner, &repo.name
|
||||
);
|
||||
|
||||
let val = CreateGiteaWebhook {
|
||||
active: true,
|
||||
authorization_header: Some("something".into()),
|
||||
branch_filter: Some("*".into()),
|
||||
config: CreateGiteaWebhookConfig {
|
||||
content_type: "json".into(),
|
||||
url: "https://url?type=contractor".into(),
|
||||
},
|
||||
events: vec!["pull_request_comment".into(), "issue_comment".into()],
|
||||
r#type: GiteaWebhookType::Gitea,
|
||||
};
|
||||
let val = self.create_webhook();
|
||||
|
||||
tracing::trace!(
|
||||
"calling url: {} with body {}",
|
||||
@ -325,6 +321,20 @@ impl DefaultGiteaClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_webhook(&self) -> CreateGiteaWebhook {
|
||||
CreateGiteaWebhook {
|
||||
active: true,
|
||||
authorization_header: Some("something".into()),
|
||||
branch_filter: Some("*".into()),
|
||||
config: CreateGiteaWebhookConfig {
|
||||
content_type: "json".into(),
|
||||
url: format!("{}?type=contractor", self.webhook_url),
|
||||
},
|
||||
events: vec!["pull_request_comment".into(), "issue_comment".into()],
|
||||
r#type: GiteaWebhookType::Gitea,
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_webhook(&self, repo: &Repository, webhook: GiteaWebhook) -> anyhow::Result<()> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
@ -333,17 +343,7 @@ impl DefaultGiteaClient {
|
||||
self.url, &repo.owner, &repo.name, &webhook.id,
|
||||
);
|
||||
|
||||
let val = CreateGiteaWebhook {
|
||||
active: true,
|
||||
authorization_header: Some("something".into()),
|
||||
branch_filter: Some("*".into()),
|
||||
config: CreateGiteaWebhookConfig {
|
||||
content_type: "json".into(),
|
||||
url: "https://url?type=contractor".into(),
|
||||
},
|
||||
events: vec!["pull_request_comment".into(), "issue_comment".into()],
|
||||
r#type: GiteaWebhookType::Gitea,
|
||||
};
|
||||
let val = self.create_webhook();
|
||||
|
||||
tracing::trace!(
|
||||
"calling url: {} with body {}",
|
||||
@ -464,7 +464,6 @@ pub mod traits;
|
||||
|
||||
use anyhow::Context;
|
||||
pub use extensions::*;
|
||||
use futures::{stream::FuturesUnordered, StreamExt, TryStreamExt};
|
||||
use itertools::Itertools;
|
||||
use futures::{stream::FuturesUnordered, TryStreamExt};
|
||||
use reqwest::{StatusCode, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
Loading…
Reference in New Issue
Block a user