forked from kemitix/git-next
feat!: restructured server config into listen & shout sections
Groups 'http' and 'webhook' sections under 'listen'. Renames 'notification' section as 'shout'.
This commit is contained in:
parent
8df7600053
commit
538728c491
27 changed files with 300 additions and 235 deletions
|
@ -21,6 +21,9 @@ nursery = { level = "warn", priority = -1 }
|
|||
unwrap_used = "warn"
|
||||
expect_used = "warn"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }
|
||||
|
||||
[workspace.dependencies]
|
||||
git-next-core = { path = "crates/core", version = "0.12" }
|
||||
git-next-forge-forgejo = { path = "crates/forge-forgejo", version = "0.12" }
|
||||
|
|
|
@ -79,3 +79,6 @@ nursery = { level = "warn", priority = -1 }
|
|||
# pedantic = "warn"
|
||||
unwrap_used = "warn"
|
||||
expect_used = "warn"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }
|
||||
|
|
|
@ -87,7 +87,9 @@ happen to have those same names.
|
|||
|
||||
The server is configured by the `git-next-server.toml` file.
|
||||
|
||||
#### http
|
||||
#### listen
|
||||
|
||||
##### http
|
||||
|
||||
The server needs to be able to receive webhook notifications from your forge,
|
||||
(e.g. github.com). You can do this via any method that suits your environment,
|
||||
|
@ -100,17 +102,9 @@ This is the address and port that your reverse proxy should route traffic to.
|
|||
- **addr** - the IP address the server should bind to
|
||||
- **port** - the IP port the server should bind to
|
||||
|
||||
#### notification
|
||||
##### url
|
||||
|
||||
The server should be able to notify the user when manual intervention is required.
|
||||
Currently this is only available via sending a Webhook message.
|
||||
|
||||
- **type** - one of `None` or `Webhook`
|
||||
- **webhook** - the URL to POST the notification to
|
||||
|
||||
See [Notifications](#notifications) for more details.
|
||||
|
||||
#### webhook
|
||||
The HTTPS URL for forges to send webhooks to.
|
||||
|
||||
Your forges need to know where they should route webhooks to. This should be
|
||||
an address this is accessible to the forge. So, for github.com, it would need
|
||||
|
@ -118,7 +112,16 @@ to be a publicly accessible HTTPS URL. For a self-hosted forge, e.g. ForgeJo,
|
|||
on your own network, then it only needs to be accessible from the server your
|
||||
forge is running on.
|
||||
|
||||
- **url** - the HTTPS URL for forges to send webhook to
|
||||
#### shout
|
||||
|
||||
The server should be able to notify the user when manual intervention is required.
|
||||
|
||||
##### webhook
|
||||
|
||||
- **url** - the URL to POST the notification to and the
|
||||
- **secret** - the sync key used to sign the webhook payload
|
||||
|
||||
See [Notifications](#notifications) for more details.
|
||||
|
||||
#### storage
|
||||
|
||||
|
@ -214,13 +217,11 @@ Currently `git-next` can only use a `gitdir` if the forge and repo is the
|
|||
same one specified as the `origin` remote. Otherwise the behaviour is
|
||||
untested and undefined.
|
||||
|
||||
## Notifications
|
||||
## Webhook Notifications
|
||||
|
||||
`git-next` can send a number of notification to the user when intervention is required.
|
||||
Currently, only WebHooks are supported.
|
||||
|
||||
Webhooks are sent using the Standard Webhooks format. That means all POST messages have
|
||||
the following headers:
|
||||
When sending a Webhook Notification to a user they are sent using the
|
||||
Standard Webhooks format. That means all POST messages have the
|
||||
following headers:
|
||||
|
||||
- `Webhook-Id`
|
||||
- `Webhook-Signature`
|
||||
|
|
|
@ -18,14 +18,16 @@ impl Handler<RegisterWebhook> for RepoActor {
|
|||
if self.webhook_id.is_none() {
|
||||
let forge_alias = self.repo_details.forge.forge_alias().clone();
|
||||
let repo_alias = self.repo_details.repo_alias.clone();
|
||||
let webhook_url = self.webhook.url(&forge_alias, &repo_alias);
|
||||
let repo_listen_url = self
|
||||
.listen_url
|
||||
.repo_url(forge_alias.clone(), repo_alias.clone());
|
||||
let forge = self.forge.duplicate();
|
||||
let addr = ctx.address();
|
||||
let notify_user_recipient = self.notify_user_recipient.clone();
|
||||
let log = self.log.clone();
|
||||
debug!("registering webhook");
|
||||
async move {
|
||||
match forge.register_webhook(&webhook_url).await {
|
||||
match forge.register_webhook(&repo_listen_url).await {
|
||||
Ok(registered_webhook) => {
|
||||
debug!(?registered_webhook, "");
|
||||
do_send(
|
||||
|
|
|
@ -13,7 +13,8 @@ use git_next_core::{
|
|||
repository::{factory::RepositoryFactory, open::OpenRepositoryLike},
|
||||
UserNotification,
|
||||
},
|
||||
server, WebhookAuth, WebhookId,
|
||||
server::ListenUrl,
|
||||
WebhookAuth, WebhookId,
|
||||
};
|
||||
|
||||
mod branch;
|
||||
|
@ -45,7 +46,7 @@ pub struct RepoActor {
|
|||
generation: git::Generation,
|
||||
message_token: messages::MessageToken,
|
||||
repo_details: git::RepoDetails,
|
||||
webhook: server::InboundWebhook,
|
||||
listen_url: ListenUrl,
|
||||
webhook_id: Option<WebhookId>, // INFO: if [None] then no webhook is configured
|
||||
webhook_auth: Option<WebhookAuth>, // INFO: if [None] then no webhook is configured
|
||||
last_main_commit: Option<git::Commit>,
|
||||
|
@ -63,7 +64,7 @@ impl RepoActor {
|
|||
pub fn new(
|
||||
repo_details: git::RepoDetails,
|
||||
forge: Box<dyn git::ForgeLike>,
|
||||
webhook: server::InboundWebhook,
|
||||
listen_url: ListenUrl,
|
||||
generation: git::Generation,
|
||||
net: Network,
|
||||
repository_factory: Box<dyn RepositoryFactory>,
|
||||
|
@ -75,7 +76,7 @@ impl RepoActor {
|
|||
generation,
|
||||
message_token,
|
||||
repo_details,
|
||||
webhook,
|
||||
listen_url,
|
||||
webhook_id: None,
|
||||
webhook_auth: None,
|
||||
last_main_commit: None,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use git_next_core::server::ListenUrl;
|
||||
|
||||
//
|
||||
use super::*;
|
||||
|
||||
|
@ -50,8 +52,8 @@ pub fn a_network() -> kxio::network::MockNetwork {
|
|||
kxio::network::MockNetwork::new()
|
||||
}
|
||||
|
||||
pub fn a_webhook_url(forge_alias: &ForgeAlias, repo_alias: &RepoAlias) -> WebhookUrl {
|
||||
InboundWebhook::new(a_name()).url(forge_alias, repo_alias)
|
||||
pub fn a_listen_url() -> ListenUrl {
|
||||
ListenUrl::new(a_name())
|
||||
}
|
||||
|
||||
pub fn a_name() -> String {
|
||||
|
@ -168,10 +170,6 @@ pub fn a_message_token() -> MessageToken {
|
|||
MessageToken::default()
|
||||
}
|
||||
|
||||
pub fn a_webhook(url: &WebhookUrl) -> InboundWebhook {
|
||||
InboundWebhook::new(url.clone().into())
|
||||
}
|
||||
|
||||
pub fn a_forge() -> Box<MockForgeLike> {
|
||||
Box::new(MockForgeLike::new())
|
||||
}
|
||||
|
@ -182,10 +180,7 @@ pub fn a_repo_actor(
|
|||
forge: Box<dyn ForgeLike>,
|
||||
net: kxio::network::Network,
|
||||
) -> (RepoActor, RepoActorLog) {
|
||||
let forge_alias = repo_details.forge.forge_alias();
|
||||
let repo_alias = &repo_details.repo_alias;
|
||||
let webhook_url = given::a_webhook_url(forge_alias, repo_alias);
|
||||
let webhook = given::a_webhook(&webhook_url);
|
||||
let listen_url = given::a_listen_url();
|
||||
let generation = Generation::default();
|
||||
let log = RepoActorLog::default();
|
||||
let actors_log = log.clone();
|
||||
|
@ -193,7 +188,7 @@ pub fn a_repo_actor(
|
|||
RepoActor::new(
|
||||
repo_details,
|
||||
forge,
|
||||
webhook,
|
||||
listen_url,
|
||||
generation,
|
||||
net,
|
||||
repository_factory,
|
||||
|
|
|
@ -21,7 +21,6 @@ use git_next_core::{
|
|||
Commit, ForgeLike, Generation, MockForgeLike, RepoDetails,
|
||||
},
|
||||
message,
|
||||
server::{InboundWebhook, WebhookUrl},
|
||||
webhook::{forge_notification::Body, Push},
|
||||
BranchName, ForgeAlias, ForgeConfig, ForgeNotification, ForgeType, GitDir, Hostname,
|
||||
RegisteredWebhook, RemoteUrl, RepoAlias, RepoBranches, RepoConfig, RepoConfigSource,
|
||||
|
|
|
@ -16,10 +16,10 @@ impl Handler<NotifyUser> for ServerActor {
|
|||
let Some(server_config) = &self.server_config else {
|
||||
return;
|
||||
};
|
||||
let notification_config = server_config.notification().clone();
|
||||
let shout_config = server_config.shout().clone();
|
||||
let net = self.net.clone();
|
||||
async move {
|
||||
if let Some(webhook_config) = notification_config.webhook() {
|
||||
if let Some(webhook_config) = shout_config.webhook() {
|
||||
send_webhook(msg, webhook_config, net).await;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ impl Handler<ReceiveServerConfig> for ServerActor {
|
|||
#[allow(clippy::cognitive_complexity)]
|
||||
fn handle(&mut self, msg: ReceiveServerConfig, ctx: &mut Self::Context) -> Self::Result {
|
||||
tracing::info!("recieved server config");
|
||||
let Ok(socket_addr) = msg.http() else {
|
||||
let Ok(socket_addr) = msg.listen_socket_addr() else {
|
||||
return self.abort(ctx, "Unable to parse http.addr");
|
||||
};
|
||||
|
||||
|
@ -19,7 +19,7 @@ impl Handler<ReceiveServerConfig> for ServerActor {
|
|||
return self.abort(ctx, "Server storage not available");
|
||||
};
|
||||
|
||||
if msg.inbound_webhook().base_url().ends_with('/') {
|
||||
if msg.listen().url().ends_with('/') {
|
||||
return self.abort(ctx, "webhook.url must not end with a '/'");
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ impl Handler<ReceiveValidServerConfig> for ServerActor {
|
|||
// Webhook Server
|
||||
info!("Starting Webhook Server...");
|
||||
let webhook_router = WebhookRouter::default().start();
|
||||
let inbound_webhook = server_config.inbound_webhook();
|
||||
let listen_url = server_config.listen().url();
|
||||
// Forge Actors
|
||||
for (forge_alias, forge_config) in server_config.forges() {
|
||||
let repo_actors = self
|
||||
|
@ -40,7 +40,7 @@ impl Handler<ReceiveValidServerConfig> for ServerActor {
|
|||
forge_config,
|
||||
forge_alias.clone(),
|
||||
&server_storage,
|
||||
inbound_webhook,
|
||||
listen_url,
|
||||
ctx.address().recipient(),
|
||||
)
|
||||
.into_iter()
|
||||
|
|
|
@ -20,7 +20,7 @@ use crate::{
|
|||
|
||||
use git_next_core::{
|
||||
git::{repository::factory::RepositoryFactory, Generation, RepoDetails},
|
||||
server::{self, InboundWebhook, ServerConfig, ServerStorage},
|
||||
server::{self, ListenUrl, ServerConfig, ServerStorage},
|
||||
ForgeAlias, ForgeConfig, GitDir, RepoAlias, ServerRepoConfig, StoragePathType,
|
||||
};
|
||||
|
||||
|
@ -113,7 +113,7 @@ impl ServerActor {
|
|||
forge_config: &ForgeConfig,
|
||||
forge_name: ForgeAlias,
|
||||
server_storage: &ServerStorage,
|
||||
webhook: &InboundWebhook,
|
||||
listen_url: &ListenUrl,
|
||||
notify_user_recipient: Recipient<NotifyUser>,
|
||||
) -> Vec<(ForgeAlias, RepoAlias, RepoActor)> {
|
||||
let span =
|
||||
|
@ -122,7 +122,8 @@ impl ServerActor {
|
|||
let _guard = span.enter();
|
||||
tracing::info!("Creating Forge");
|
||||
let mut repos = vec![];
|
||||
let creator = self.create_actor(forge_name, forge_config.clone(), server_storage, webhook);
|
||||
let creator =
|
||||
self.create_actor(forge_name, forge_config.clone(), server_storage, listen_url);
|
||||
for (repo_alias, server_repo_config) in forge_config.repos() {
|
||||
let forge_repo = creator((
|
||||
repo_alias,
|
||||
|
@ -143,17 +144,16 @@ impl ServerActor {
|
|||
forge_name: ForgeAlias,
|
||||
forge_config: ForgeConfig,
|
||||
server_storage: &ServerStorage,
|
||||
webhook: &InboundWebhook,
|
||||
listen_url: &ListenUrl,
|
||||
) -> impl Fn(
|
||||
(RepoAlias, &ServerRepoConfig, Recipient<NotifyUser>),
|
||||
) -> (ForgeAlias, RepoAlias, RepoActor) {
|
||||
let server_storage = server_storage.clone();
|
||||
let webhook = webhook.clone();
|
||||
let listen_url = listen_url.clone();
|
||||
let net = self.net.clone();
|
||||
let repository_factory = self.repository_factory.duplicate();
|
||||
let generation = self.generation;
|
||||
let sleep_duration = self.sleep_duration;
|
||||
// let notify_user_recipient = server_addr.recipient();
|
||||
move |(repo_alias, server_repo_config, notify_user_recipient)| {
|
||||
let span = tracing::info_span!("create_actor", alias = %repo_alias, config = %server_repo_config);
|
||||
let _guard = span.enter();
|
||||
|
@ -185,7 +185,7 @@ impl ServerActor {
|
|||
let actor = RepoActor::new(
|
||||
repo_details,
|
||||
forge,
|
||||
webhook.clone(),
|
||||
listen_url.clone(),
|
||||
generation,
|
||||
net.clone(),
|
||||
repository_factory.duplicate(),
|
||||
|
@ -230,13 +230,13 @@ impl ServerActor {
|
|||
}
|
||||
|
||||
/// Attempts to gracefully shutdown the server before stopping the system.
|
||||
fn abort(&mut self, ctx: &mut <Self as actix::Actor>::Context, message: impl Into<String>) {
|
||||
fn abort(&self, ctx: &mut <Self as actix::Actor>::Context, message: impl Into<String>) {
|
||||
tracing::error!("Aborting: {}", message.into());
|
||||
self.do_send(crate::server::actor::messages::Shutdown, ctx);
|
||||
System::current().stop_with_code(1);
|
||||
}
|
||||
|
||||
fn do_send<M>(&mut self, msg: M, _ctx: &mut <Self as actix::Actor>::Context)
|
||||
fn do_send<M>(&self, msg: M, _ctx: &mut <Self as actix::Actor>::Context)
|
||||
where
|
||||
M: actix::Message + Send + 'static + std::fmt::Debug,
|
||||
Self: actix::Handler<M>,
|
||||
|
|
|
@ -4,7 +4,7 @@ use actix::prelude::*;
|
|||
use crate::server::actor::{tests::given, ReceiveServerConfig, ServerActor};
|
||||
use git_next_core::{
|
||||
git,
|
||||
server::{Http, InboundWebhook, Notification, ServerConfig, ServerStorage},
|
||||
server::{Http, Listen, ListenUrl, ServerConfig, ServerStorage, Shout},
|
||||
};
|
||||
|
||||
use std::{
|
||||
|
@ -25,9 +25,11 @@ async fn when_webhook_url_has_trailing_slash_should_not_send() {
|
|||
let server = ServerActor::new(fs.clone(), net.into(), repo, duration);
|
||||
|
||||
// collaborators
|
||||
let http = Http::new("0.0.0.0".to_string(), 80);
|
||||
let webhook = InboundWebhook::new("http://localhost/".to_string()); // With trailing slash
|
||||
let notifications = Notification::none();
|
||||
let listen = Listen::new(
|
||||
Http::new("0.0.0.0".to_string(), 80),
|
||||
ListenUrl::new("http://localhost/".to_string()), // with trailing slash
|
||||
);
|
||||
let shout = Shout::default();
|
||||
let server_storage = ServerStorage::new((fs.base()).to_path_buf());
|
||||
let repos = BTreeMap::default();
|
||||
|
||||
|
@ -39,9 +41,8 @@ async fn when_webhook_url_has_trailing_slash_should_not_send() {
|
|||
server
|
||||
.start()
|
||||
.do_send(ReceiveServerConfig::new(ServerConfig::new(
|
||||
http,
|
||||
webhook,
|
||||
notifications,
|
||||
listen,
|
||||
shout,
|
||||
server_storage,
|
||||
repos,
|
||||
)));
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
[http] # where to listen for incoming updates from forges
|
||||
addr = "0.0.0.0"
|
||||
port = 8080
|
||||
[listen]
|
||||
# The address and port to listen to for incoming webhooks from forges.
|
||||
http = { addr = "0.0.0.0", port = 8080 }
|
||||
|
||||
[webhook] # where forge should send updates to - should be route to 'http.addr:http.port' above (e.g. using a reverse proxy)
|
||||
# The URL where forge should send updates to.
|
||||
# This should be route to 'http.addr:http.port' above (e.g. using a reverse proxy)
|
||||
url = "https://localhost:8080" # don't include any query path or a trailing slash
|
||||
|
||||
[notification] # where updates from git-next should be sent to alert the user
|
||||
[shout] # where updates from git-next should be sent to alert the user
|
||||
# webhook = { url = "https//localhost:9090", secret = "secret-password" }
|
||||
|
||||
[storage] # where local copies of repositories will be cloned (bare) into
|
||||
|
@ -22,3 +23,9 @@ path = "./data"
|
|||
# [forge.default.repos] # the repos at the forge to manage
|
||||
# hello = { repo = "bob/hello", branch = "main", gitdir = "/opt/git/projects/bob/hello.git" }
|
||||
# world = { repo = "bob/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" }
|
||||
# hello = { repo = "bob/hello", branch = "main", gitdir = "/opt/git/projects/bob/hello.git" }
|
||||
# world = { repo = "bob/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" }
|
||||
# world = { repo = "bob/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" }
|
||||
# world = { repo = "bob/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" }
|
||||
# world = { repo = "bob/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" }
|
||||
# world = { repo = "bob/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" }
|
||||
|
|
|
@ -61,3 +61,6 @@ nursery = { level = "warn", priority = -1 }
|
|||
# pedantic = "warn"
|
||||
unwrap_used = "warn"
|
||||
expect_used = "warn"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
use std::{
|
||||
collections::BTreeMap,
|
||||
net::SocketAddr,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use derive_more::Display;
|
||||
use kxio::fs::FileSystem;
|
||||
use secrecy::Secret;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
|
@ -44,9 +47,8 @@ type Result<T> = core::result::Result<T, Error>;
|
|||
derive_more::Constructor,
|
||||
)]
|
||||
pub struct ServerConfig {
|
||||
http: Http,
|
||||
webhook: InboundWebhook,
|
||||
notification: Notification,
|
||||
listen: Listen,
|
||||
shout: Shout,
|
||||
storage: ServerStorage,
|
||||
pub forge: BTreeMap<String, ForgeConfig>,
|
||||
}
|
||||
|
@ -69,16 +71,75 @@ impl ServerConfig {
|
|||
&self.storage
|
||||
}
|
||||
|
||||
pub const fn notification(&self) -> &Notification {
|
||||
&self.notification
|
||||
pub const fn shout(&self) -> &Shout {
|
||||
&self.shout
|
||||
}
|
||||
|
||||
pub const fn inbound_webhook(&self) -> &InboundWebhook {
|
||||
&self.webhook
|
||||
pub const fn listen(&self) -> &Listen {
|
||||
&self.listen
|
||||
}
|
||||
|
||||
pub fn http(&self) -> Result<SocketAddr> {
|
||||
self.http.socket_addr()
|
||||
pub fn listen_socket_addr(&self) -> Result<SocketAddr> {
|
||||
self.listen.http.socket_addr()
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines how the server receives webhook notifications from forges.
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
derive_more::From,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
derive_more::AsRef,
|
||||
serde::Deserialize,
|
||||
derive_more::Constructor,
|
||||
)]
|
||||
pub struct Listen {
|
||||
http: Http,
|
||||
url: ListenUrl,
|
||||
}
|
||||
impl Listen {
|
||||
/// Returns the URL a Repo will listen to for updates from the Forge
|
||||
pub fn repo_url(&self, forge_alias: ForgeAlias, repo_alias: RepoAlias) -> RepoListenUrl {
|
||||
self.url.repo_url(forge_alias, repo_alias)
|
||||
}
|
||||
|
||||
pub const fn url(&self) -> &ListenUrl {
|
||||
&self.url
|
||||
}
|
||||
}
|
||||
|
||||
newtype!(
|
||||
ListenUrl:
|
||||
String, Serialize, Deserialize, PartialOrd, Ord, Display:
|
||||
"The base url for receiving all webhooks from all forges"
|
||||
);
|
||||
impl ListenUrl {
|
||||
pub fn repo_url(&self, forge_alias: ForgeAlias, repo_alias: RepoAlias) -> RepoListenUrl {
|
||||
RepoListenUrl::new((self.clone(), forge_alias, repo_alias))
|
||||
}
|
||||
}
|
||||
|
||||
newtype!(ForgeWebhookUrl: String: "Raw URL from a forge Webhook");
|
||||
|
||||
newtype!(RepoListenUrl: (ListenUrl, ForgeAlias, RepoAlias): "URL to listen for webhook from forges");
|
||||
impl Display for RepoListenUrl {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}/{}/{}",
|
||||
self.deref().0,
|
||||
self.deref().1,
|
||||
self.deref().2
|
||||
)
|
||||
}
|
||||
}
|
||||
impl From<RepoListenUrl> for ForgeWebhookUrl {
|
||||
fn from(value: RepoListenUrl) -> Self {
|
||||
Self::new(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,35 +169,6 @@ impl Http {
|
|||
}
|
||||
}
|
||||
|
||||
/// Defines the Webhook Forges should send updates to
|
||||
/// Must be an address that is accessible from the remote forge
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
derive_more::From,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
derive_more::AsRef,
|
||||
serde::Deserialize,
|
||||
derive_more::Constructor,
|
||||
)]
|
||||
pub struct InboundWebhook {
|
||||
url: String,
|
||||
}
|
||||
impl InboundWebhook {
|
||||
pub fn url(&self, forge_alias: &ForgeAlias, repo_alias: &RepoAlias) -> WebhookUrl {
|
||||
let base_url = &self.url;
|
||||
WebhookUrl(format!("{base_url}/{forge_alias}/{repo_alias}"))
|
||||
}
|
||||
pub fn base_url(&self) -> &str {
|
||||
&self.url
|
||||
}
|
||||
}
|
||||
|
||||
newtype!(WebhookUrl: String, serde::Serialize: "The URL for the webhook where forges should send their updates");
|
||||
|
||||
/// The directory to store server data, such as cloned repos
|
||||
#[derive(
|
||||
Clone,
|
||||
|
@ -164,6 +196,7 @@ impl ServerStorage {
|
|||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
Default,
|
||||
derive_more::From,
|
||||
PartialEq,
|
||||
Eq,
|
||||
|
@ -172,13 +205,10 @@ impl ServerStorage {
|
|||
derive_more::AsRef,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
pub struct Notification {
|
||||
pub struct Shout {
|
||||
webhook: Option<OutboundWebhook>,
|
||||
}
|
||||
impl Notification {
|
||||
pub const fn none() -> Self {
|
||||
Self { webhook: None }
|
||||
}
|
||||
impl Shout {
|
||||
pub const fn new_webhook(webhook: OutboundWebhook) -> Self {
|
||||
Self {
|
||||
webhook: Some(webhook),
|
||||
|
@ -197,11 +227,6 @@ impl Notification {
|
|||
self.webhook.clone().map(|x| x.secret).map(Secret::new)
|
||||
}
|
||||
}
|
||||
impl Default for Notification {
|
||||
fn default() -> Self {
|
||||
Self::none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
|
|
|
@ -7,7 +7,6 @@ use std::collections::BTreeMap;
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::server::Http;
|
||||
use crate::server::InboundWebhook;
|
||||
use crate::server::ServerConfig;
|
||||
use crate::server::ServerStorage;
|
||||
use crate::webhook::push::Branch;
|
||||
|
@ -465,14 +464,14 @@ mod server {
|
|||
server_config: &ServerConfig,
|
||||
fs: &kxio::fs::FileSystem,
|
||||
) -> TestResult {
|
||||
let http = &server_config.http()?;
|
||||
let http = &server_config.listen_socket_addr()?;
|
||||
let http_addr = http.ip();
|
||||
let http_port = server_config.http()?.port();
|
||||
let webhook_url = server_config.inbound_webhook().base_url();
|
||||
let http_port = server_config.listen_socket_addr()?.port();
|
||||
let listen_url = server_config.listen().url();
|
||||
let storage_path = server_config.storage().path();
|
||||
let notification = &server_config.notification();
|
||||
let notification_webhook_url = notification.webhook_url().unwrap_or_default();
|
||||
let notification_webhook_secret = notification
|
||||
let shout = server_config.shout();
|
||||
let shout_webhook_url = shout.webhook_url().unwrap_or_default();
|
||||
let shout_webhook_secret = shout
|
||||
.webhook_secret()
|
||||
.map(|secret| secret.expose_secret().clone())
|
||||
.unwrap_or_default();
|
||||
|
@ -515,20 +514,16 @@ mod server {
|
|||
let repos = repos.join("\n");
|
||||
let file_contents = &format!(
|
||||
r#"
|
||||
[http]
|
||||
addr = "{http_addr}"
|
||||
port = {http_port}
|
||||
[listen]
|
||||
http = {{ addr = "{http_addr}", port = {http_port} }}
|
||||
url = "{listen_url}"
|
||||
|
||||
[webhook]
|
||||
url = "{webhook_url}"
|
||||
[shout]
|
||||
webhook = {{ url = "{shout_webhook_url}", secret = "{shout_webhook_secret}" }}
|
||||
|
||||
[storage]
|
||||
path = {storage_path:?}
|
||||
|
||||
[notification]
|
||||
type = "Webhook"
|
||||
webhook = {{ url = "{notification_webhook_url}", secret = "{notification_webhook_secret}" }}
|
||||
|
||||
[forge.{forge_alias}]
|
||||
forge_type = "{forge_type}"
|
||||
hostname = "{forge_hostname}"
|
||||
|
@ -697,7 +692,7 @@ mod push {
|
|||
}
|
||||
mod given {
|
||||
|
||||
use crate::server::{Notification, OutboundWebhook};
|
||||
use crate::server::{Listen, ListenUrl, OutboundWebhook, Shout};
|
||||
|
||||
use super::*;
|
||||
use rand::Rng as _;
|
||||
|
@ -720,8 +715,7 @@ mod given {
|
|||
}
|
||||
pub fn a_server_config() -> ServerConfig {
|
||||
ServerConfig::new(
|
||||
an_http(),
|
||||
an_inbound_webhook(),
|
||||
a_listen(),
|
||||
a_notification_config(),
|
||||
a_server_storage(),
|
||||
some_forge_configs(),
|
||||
|
@ -743,8 +737,11 @@ mod given {
|
|||
pub fn a_port() -> u16 {
|
||||
rand::thread_rng().gen()
|
||||
}
|
||||
pub fn an_inbound_webhook() -> InboundWebhook {
|
||||
InboundWebhook::new(a_name())
|
||||
pub fn a_listen() -> Listen {
|
||||
Listen::new(an_http(), a_listen_url())
|
||||
}
|
||||
pub fn a_listen_url() -> ListenUrl {
|
||||
ListenUrl::new(a_name())
|
||||
}
|
||||
pub fn an_outbound_webhook() -> OutboundWebhook {
|
||||
OutboundWebhook::new(a_name(), a_name())
|
||||
|
@ -752,8 +749,8 @@ mod given {
|
|||
pub fn a_server_storage() -> ServerStorage {
|
||||
ServerStorage::new(a_name().into())
|
||||
}
|
||||
pub fn a_notification_config() -> Notification {
|
||||
Notification::new_webhook(an_outbound_webhook())
|
||||
pub fn a_notification_config() -> Shout {
|
||||
Shout::new_webhook(an_outbound_webhook())
|
||||
}
|
||||
pub fn some_forge_configs() -> BTreeMap<String, ForgeConfig> {
|
||||
[(a_name(), a_forge_config())].into()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//
|
||||
use crate::{
|
||||
git, server::WebhookUrl, webhook, ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId,
|
||||
git, server::RepoListenUrl, webhook, ForgeNotification, RegisteredWebhook, WebhookAuth,
|
||||
WebhookId,
|
||||
};
|
||||
|
||||
#[mockall::automock]
|
||||
|
@ -29,7 +30,10 @@ pub trait ForgeLike: std::fmt::Debug + Send + Sync {
|
|||
async fn commit_status(&self, commit: &git::Commit) -> git::forge::commit::Status;
|
||||
|
||||
// Lists all the webhooks
|
||||
async fn list_webhooks(&self, url: &WebhookUrl) -> git::forge::webhook::Result<Vec<WebhookId>>;
|
||||
async fn list_webhooks(
|
||||
&self,
|
||||
repo_listen_url: &RepoListenUrl,
|
||||
) -> git::forge::webhook::Result<Vec<WebhookId>>;
|
||||
|
||||
// Unregisters a webhook
|
||||
async fn unregister_webhook(&self, webhook: &WebhookId) -> git::forge::webhook::Result<()>;
|
||||
|
@ -37,6 +41,6 @@ pub trait ForgeLike: std::fmt::Debug + Send + Sync {
|
|||
// Registers a webhook
|
||||
async fn register_webhook(
|
||||
&self,
|
||||
webhook_url: &WebhookUrl,
|
||||
repo_listen_url: &RepoListenUrl,
|
||||
) -> git::forge::webhook::Result<RegisteredWebhook>;
|
||||
}
|
||||
|
|
|
@ -49,3 +49,6 @@ nursery = { level = "warn", priority = -1 }
|
|||
# pedantic = "warn"
|
||||
unwrap_used = "warn"
|
||||
expect_used = "warn"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }
|
||||
|
|
|
@ -5,8 +5,10 @@ mod tests;
|
|||
mod webhook;
|
||||
|
||||
use git_next_core::{
|
||||
self as core, git, git::forge::commit::Status, server, ForgeNotification, RegisteredWebhook,
|
||||
WebhookAuth, WebhookId,
|
||||
self as core,
|
||||
git::{self, forge::commit::Status},
|
||||
server::RepoListenUrl,
|
||||
ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId,
|
||||
};
|
||||
|
||||
use kxio::network::{self, Network};
|
||||
|
@ -93,9 +95,9 @@ impl git::ForgeLike for ForgeJo {
|
|||
|
||||
async fn list_webhooks(
|
||||
&self,
|
||||
webhook_url: &server::WebhookUrl,
|
||||
repo_listen_url: &RepoListenUrl,
|
||||
) -> git::forge::webhook::Result<Vec<WebhookId>> {
|
||||
webhook::list(&self.repo_details, webhook_url, &self.net).await
|
||||
webhook::list(&self.repo_details, repo_listen_url, &self.net).await
|
||||
}
|
||||
|
||||
async fn unregister_webhook(&self, webhook_id: &WebhookId) -> git::forge::webhook::Result<()> {
|
||||
|
@ -104,9 +106,9 @@ impl git::ForgeLike for ForgeJo {
|
|||
|
||||
async fn register_webhook(
|
||||
&self,
|
||||
webhook_url: &server::WebhookUrl,
|
||||
repo_listen_url: &RepoListenUrl,
|
||||
) -> git::forge::webhook::Result<RegisteredWebhook> {
|
||||
webhook::register(&self.repo_details, webhook_url, &self.net).await
|
||||
webhook::register(&self.repo_details, repo_listen_url, &self.net).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
use crate::ForgeJo;
|
||||
use git_next_core::{
|
||||
git::{self, forge::commit::Status, ForgeLike as _},
|
||||
server::{InboundWebhook, WebhookUrl},
|
||||
server::ListenUrl,
|
||||
BranchName, ForgeAlias, ForgeConfig, ForgeNotification, ForgeType, GitDir, Hostname, RepoAlias,
|
||||
RepoBranches, RepoPath, ServerRepoConfig, StoragePathType, WebhookAuth, WebhookId,
|
||||
};
|
||||
|
@ -198,7 +198,7 @@ mod forgejo {
|
|||
async fn should_return_a_list_of_matching_webhooks() {
|
||||
let fs = given::a_filesystem();
|
||||
let repo_details = given::repo_details(&fs);
|
||||
let webhook_url = given::any_webhook_url();
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
|
@ -217,9 +217,9 @@ mod forgejo {
|
|||
with::get_webhooks_by_page(
|
||||
1,
|
||||
&[
|
||||
with::ReturnedWebhook::new(hook_id_1, &webhook_url),
|
||||
with::ReturnedWebhook::new(hook_id_2, &webhook_url),
|
||||
with::ReturnedWebhook::new(hook_id_3, &given::any_webhook_url()),
|
||||
with::ReturnedWebhook::new(hook_id_1, &repo_listen_url),
|
||||
with::ReturnedWebhook::new(hook_id_2, &repo_listen_url),
|
||||
with::ReturnedWebhook::new(hook_id_3, &given::a_repo_listen_url(&repo_details)),
|
||||
],
|
||||
&mut args,
|
||||
);
|
||||
|
@ -228,7 +228,7 @@ mod forgejo {
|
|||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Ok(result) = forge.list_webhooks(&webhook_url).await);
|
||||
let_assert!(Ok(result) = forge.list_webhooks(&repo_listen_url).await);
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![
|
||||
|
@ -242,7 +242,7 @@ mod forgejo {
|
|||
async fn should_return_any_network_error() {
|
||||
let fs = given::a_filesystem();
|
||||
let repo_details = given::repo_details(&fs);
|
||||
let webhook_url = given::a_webhook_url(&given::a_forge_alias(), &given::a_repo_alias());
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
|
@ -256,7 +256,7 @@ mod forgejo {
|
|||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Err(_) = forge.list_webhooks(&webhook_url).await);
|
||||
let_assert!(Err(_) = forge.list_webhooks(&repo_listen_url).await);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -326,8 +326,7 @@ mod forgejo {
|
|||
repo_details.repo_config.is_some(),
|
||||
"repo_details needs to have repo_config for this test"
|
||||
);
|
||||
let webhook_url =
|
||||
given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias);
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
|
@ -352,7 +351,7 @@ mod forgejo {
|
|||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Ok(registered_webhook) = forge.register_webhook(&webhook_url).await);
|
||||
let_assert!(Ok(registered_webhook) = forge.register_webhook(&repo_listen_url).await);
|
||||
assert_eq!(
|
||||
registered_webhook.id(),
|
||||
&WebhookId::new(format!("{webhook_id}"))
|
||||
|
@ -368,11 +367,10 @@ mod forgejo {
|
|||
repo_details.repo_config.is_none(),
|
||||
"repo_details needs to NOT have repo_config for this test"
|
||||
);
|
||||
let webhook_url =
|
||||
given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias);
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let net = given::a_network();
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
let_assert!(Err(err) = forge.register_webhook(&webhook_url).await);
|
||||
let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await);
|
||||
assert!(
|
||||
matches!(err, git::forge::webhook::Error::NoRepoConfig),
|
||||
"{err:?}"
|
||||
|
@ -387,8 +385,7 @@ mod forgejo {
|
|||
repo_details.repo_config.is_some(),
|
||||
"repo_details needs to have repo_config for this test"
|
||||
);
|
||||
let webhook_url =
|
||||
given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias);
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
|
@ -400,11 +397,11 @@ mod forgejo {
|
|||
repo_path,
|
||||
token,
|
||||
};
|
||||
let hook1 = with::ReturnedWebhook::new(given::a_forgejo_webhook_id(), &webhook_url);
|
||||
let hook2 = with::ReturnedWebhook::new(given::a_forgejo_webhook_id(), &webhook_url);
|
||||
let hook1 = with::ReturnedWebhook::new(given::a_forgejo_webhook_id(), &repo_listen_url);
|
||||
let hook2 = with::ReturnedWebhook::new(given::a_forgejo_webhook_id(), &repo_listen_url);
|
||||
let hook3 = with::ReturnedWebhook::new(
|
||||
given::a_forgejo_webhook_id(),
|
||||
&given::any_webhook_url(),
|
||||
&given::a_repo_listen_url(&repo_details),
|
||||
);
|
||||
let hooks = [hook1, hook2, hook3];
|
||||
|
||||
|
@ -424,7 +421,7 @@ mod forgejo {
|
|||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Ok(registered_webhook) = forge.register_webhook(&webhook_url).await);
|
||||
let_assert!(Ok(registered_webhook) = forge.register_webhook(&repo_listen_url).await);
|
||||
assert_eq!(
|
||||
registered_webhook.id(),
|
||||
&WebhookId::new(format!("{webhook_id}"))
|
||||
|
@ -439,8 +436,7 @@ mod forgejo {
|
|||
repo_details.repo_config.is_some(),
|
||||
"repo_details needs to have repo_config for this test"
|
||||
);
|
||||
let webhook_url =
|
||||
given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias);
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
|
@ -464,7 +460,7 @@ mod forgejo {
|
|||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Err(err) = forge.register_webhook(&webhook_url).await);
|
||||
let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await);
|
||||
assert!(
|
||||
matches!(err, git::forge::webhook::Error::FailedToRegister(_)),
|
||||
"{err:?}"
|
||||
|
@ -479,8 +475,7 @@ mod forgejo {
|
|||
repo_details.repo_config.is_some(),
|
||||
"repo_details needs to have repo_config for this test"
|
||||
);
|
||||
let webhook_url =
|
||||
given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias);
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
|
@ -503,7 +498,7 @@ mod forgejo {
|
|||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Err(err) = forge.register_webhook(&webhook_url).await);
|
||||
let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await);
|
||||
assert!(
|
||||
matches!(err, git::forge::webhook::Error::FailedToRegister(_)),
|
||||
"{err:?}"
|
||||
|
@ -511,6 +506,8 @@ mod forgejo {
|
|||
}
|
||||
}
|
||||
mod with {
|
||||
use git_next_core::server::RepoListenUrl;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn get_webhooks_by_page(
|
||||
|
@ -556,20 +553,25 @@ mod forgejo {
|
|||
pub config: Config,
|
||||
}
|
||||
impl ReturnedWebhook {
|
||||
pub fn new(id: i64, url: &WebhookUrl) -> Self {
|
||||
pub fn new(id: i64, url: &RepoListenUrl) -> Self {
|
||||
Self {
|
||||
id,
|
||||
config: Config { url: url.clone() },
|
||||
config: Config {
|
||||
url: url.to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Config {
|
||||
pub url: WebhookUrl,
|
||||
pub url: String,
|
||||
}
|
||||
}
|
||||
mod given {
|
||||
|
||||
use git::RepoDetails;
|
||||
use git_next_core::server::RepoListenUrl;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn a_commit_state(
|
||||
|
@ -674,14 +676,6 @@ mod forgejo {
|
|||
kxio::network::MockNetwork::new()
|
||||
}
|
||||
|
||||
pub fn a_webhook_url(forge_alias: &ForgeAlias, repo_alias: &RepoAlias) -> WebhookUrl {
|
||||
InboundWebhook::new(a_name()).url(forge_alias, repo_alias)
|
||||
}
|
||||
|
||||
pub fn any_webhook_url() -> WebhookUrl {
|
||||
a_webhook_url(&a_forge_alias(), &a_repo_alias())
|
||||
}
|
||||
|
||||
pub fn a_name() -> String {
|
||||
use rand::Rng;
|
||||
use std::iter;
|
||||
|
@ -765,5 +759,16 @@ mod forgejo {
|
|||
gitdir,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn a_repo_listen_url(repo_details: &RepoDetails) -> RepoListenUrl {
|
||||
let listen_url = a_listen_url();
|
||||
let forge_alias = repo_details.forge.forge_alias().clone();
|
||||
let repo_alias = repo_details.repo_alias.clone();
|
||||
RepoListenUrl::new((listen_url, forge_alias, repo_alias))
|
||||
}
|
||||
|
||||
fn a_listen_url() -> ListenUrl {
|
||||
ListenUrl::new(a_name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
use git_next_core::{git, server::WebhookUrl, WebhookId};
|
||||
use git_next_core::{git, server::RepoListenUrl, WebhookId};
|
||||
|
||||
use kxio::network;
|
||||
|
||||
|
@ -7,7 +7,7 @@ use crate::webhook::Hook;
|
|||
|
||||
pub async fn list(
|
||||
repo_details: &git::RepoDetails,
|
||||
webhook_url: &WebhookUrl,
|
||||
repo_listen_url: &RepoListenUrl,
|
||||
net: &network::Network,
|
||||
) -> git::forge::webhook::Result<Vec<WebhookId>> {
|
||||
let mut ids: Vec<WebhookId> = vec![];
|
||||
|
@ -38,7 +38,7 @@ pub async fn list(
|
|||
}
|
||||
for hook in list {
|
||||
if let Some(existing_url) = hook.config.get("url") {
|
||||
if existing_url.starts_with(webhook_url.as_ref()) {
|
||||
if existing_url.starts_with(&repo_listen_url.to_string()) {
|
||||
ids.push(hook.id());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use git_next_core::server::RepoListenUrl;
|
||||
//
|
||||
use git_next_core::{git, server, RegisteredWebhook, WebhookAuth, WebhookId};
|
||||
use git_next_core::{git, RegisteredWebhook, WebhookAuth, WebhookId};
|
||||
|
||||
use kxio::network;
|
||||
use tracing::{info, warn};
|
||||
|
@ -10,7 +11,7 @@ use crate::webhook::Hook;
|
|||
#[tracing::instrument(skip_all)]
|
||||
pub async fn register(
|
||||
repo_details: &git::RepoDetails,
|
||||
webhook_url: &server::WebhookUrl,
|
||||
repo_listen_url: &RepoListenUrl,
|
||||
net: &network::Network,
|
||||
) -> git::forge::webhook::Result<RegisteredWebhook> {
|
||||
let Some(repo_config) = repo_details.repo_config.clone() else {
|
||||
|
@ -18,7 +19,7 @@ pub async fn register(
|
|||
};
|
||||
|
||||
// remove any lingering webhooks for the same URL
|
||||
let existing_webhook_ids = webhook::list(repo_details, webhook_url, net).await?;
|
||||
let existing_webhook_ids = webhook::list(repo_details, repo_listen_url, net).await?;
|
||||
for webhook_id in existing_webhook_ids {
|
||||
webhook::unregister(&webhook_id, repo_details, net).await?;
|
||||
}
|
||||
|
@ -30,7 +31,6 @@ pub async fn register(
|
|||
let url = network::NetUrl::new(format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}"
|
||||
));
|
||||
let repo_alias = &repo_details.repo_alias;
|
||||
let headers = network::NetRequestHeaders::new().with("Content-Type", "application/json");
|
||||
let authorisation = WebhookAuth::generate();
|
||||
let body = network::json!({
|
||||
|
@ -39,7 +39,7 @@ pub async fn register(
|
|||
"branch_filter": format!("{{{},{},{}}}", repo_config.branches().main(), repo_config.branches().next(), repo_config.branches().dev()),
|
||||
"config": {
|
||||
"content_type": "json",
|
||||
"url": format!("{}/{}", webhook_url.as_ref(), repo_alias),
|
||||
"url": repo_listen_url.to_string(),
|
||||
},
|
||||
"events": [ "push" ],
|
||||
"type": "forgejo"
|
||||
|
|
|
@ -57,3 +57,6 @@ nursery = { level = "warn", priority = -1 }
|
|||
# pedantic = "warn"
|
||||
unwrap_used = "warn"
|
||||
expect_used = "warn"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }
|
||||
|
|
|
@ -9,7 +9,8 @@ use crate as github;
|
|||
use git_next_core::{
|
||||
self as core,
|
||||
git::{self, forge::commit::Status},
|
||||
server, ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId,
|
||||
server::{self, RepoListenUrl},
|
||||
ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId,
|
||||
};
|
||||
|
||||
use derive_more::Constructor;
|
||||
|
@ -57,9 +58,9 @@ impl git::ForgeLike for Github {
|
|||
|
||||
async fn list_webhooks(
|
||||
&self,
|
||||
webhook_url: &server::WebhookUrl,
|
||||
repo_listen_url: &RepoListenUrl,
|
||||
) -> git::forge::webhook::Result<Vec<WebhookId>> {
|
||||
github::webhook::list(self, webhook_url).await
|
||||
github::webhook::list(self, repo_listen_url).await
|
||||
}
|
||||
|
||||
async fn unregister_webhook(&self, webhook_id: &WebhookId) -> git::forge::webhook::Result<()> {
|
||||
|
@ -69,9 +70,9 @@ impl git::ForgeLike for Github {
|
|||
// https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#create-a-repository-webhook
|
||||
async fn register_webhook(
|
||||
&self,
|
||||
webhook_url: &server::WebhookUrl,
|
||||
repo_listen_url: &RepoListenUrl,
|
||||
) -> git::forge::webhook::Result<RegisteredWebhook> {
|
||||
github::webhook::register(self, webhook_url).await
|
||||
github::webhook::register(self, repo_listen_url).await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,8 +104,8 @@ impl GithubHook {
|
|||
pub fn id(&self) -> WebhookId {
|
||||
WebhookId::new(format!("{}", self.id))
|
||||
}
|
||||
pub fn url(&self) -> server::WebhookUrl {
|
||||
server::WebhookUrl::new(self.config.url.clone())
|
||||
pub fn url(&self) -> server::ListenUrl {
|
||||
server::ListenUrl::new(self.config.url.clone())
|
||||
}
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
use crate::{Github, GithubState, GithubStatus};
|
||||
use git_next_core::{
|
||||
git::{self, forge::commit::Status, ForgeLike},
|
||||
server::{InboundWebhook, WebhookUrl},
|
||||
server::ListenUrl,
|
||||
webhook::{self, forge_notification::Body},
|
||||
ForgeAlias, ForgeConfig, ForgeNotification, ForgeType, GitDir, Hostname, RepoAlias,
|
||||
RepoBranches, RepoPath, ServerRepoConfig, StoragePathType, WebhookAuth, WebhookId,
|
||||
|
@ -158,7 +158,7 @@ mod github {
|
|||
#[tokio::test]
|
||||
async fn should_return_a_list_of_matching_webhooks() {
|
||||
let repo_details = given::repo_details();
|
||||
let webhook_url = given::any_webhook_url();
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let hook_id_1 = given::a_github_webhook_id();
|
||||
|
@ -174,8 +174,8 @@ mod github {
|
|||
with::get_webhooks_by_page(
|
||||
1,
|
||||
&[
|
||||
with::ReturnedWebhook::new(hook_id_1, &webhook_url),
|
||||
with::ReturnedWebhook::new(hook_id_2, &webhook_url),
|
||||
with::ReturnedWebhook::new(hook_id_1, &repo_listen_url),
|
||||
with::ReturnedWebhook::new(hook_id_2, &repo_listen_url),
|
||||
with::ReturnedWebhook::new(hook_id_3, &given::any_webhook_url()),
|
||||
],
|
||||
&mut args,
|
||||
|
@ -185,7 +185,7 @@ mod github {
|
|||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Ok(result) = forge.list_webhooks(&webhook_url).await);
|
||||
let_assert!(Ok(result) = forge.list_webhooks(&repo_listen_url).await);
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![
|
||||
|
@ -198,7 +198,7 @@ mod github {
|
|||
#[tokio::test]
|
||||
async fn should_return_any_network_error() {
|
||||
let repo_details = given::repo_details();
|
||||
let webhook_url = given::a_webhook_url(&given::a_forge_alias(), &given::a_repo_alias());
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let mut net = given::a_network();
|
||||
|
@ -209,7 +209,7 @@ mod github {
|
|||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Err(_) = forge.list_webhooks(&webhook_url).await);
|
||||
let_assert!(Err(_) = forge.list_webhooks(&repo_listen_url).await);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -271,8 +271,7 @@ mod github {
|
|||
repo_details.repo_config.is_some(),
|
||||
"repo_details needs to have repo_config for this test"
|
||||
);
|
||||
let webhook_url =
|
||||
given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias);
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let mut net = given::a_network();
|
||||
|
@ -288,14 +287,14 @@ mod github {
|
|||
net.add_post_response(
|
||||
format!("https://api.{hostname}/repos/{repo_path}/hooks").as_str(),
|
||||
StatusCode::OK,
|
||||
json!({"id": webhook_id, "config":{"url": webhook_url}})
|
||||
json!({"id": webhook_id, "config":{"url": repo_listen_url.to_string()}})
|
||||
.to_string()
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Ok(registered_webhook) = forge.register_webhook(&webhook_url).await);
|
||||
let_assert!(Ok(registered_webhook) = forge.register_webhook(&repo_listen_url).await);
|
||||
assert_eq!(
|
||||
registered_webhook.id(),
|
||||
&WebhookId::new(format!("{webhook_id}"))
|
||||
|
@ -310,11 +309,10 @@ mod github {
|
|||
repo_details.repo_config.is_none(),
|
||||
"repo_details needs to NOT have repo_config for this test"
|
||||
);
|
||||
let webhook_url =
|
||||
given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias);
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let net = given::a_network();
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
let_assert!(Err(err) = forge.register_webhook(&webhook_url).await);
|
||||
let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await);
|
||||
assert!(
|
||||
matches!(err, git::forge::webhook::Error::NoRepoConfig),
|
||||
"{err:?}"
|
||||
|
@ -328,8 +326,7 @@ mod github {
|
|||
repo_details.repo_config.is_some(),
|
||||
"repo_details needs to have repo_config for this test"
|
||||
);
|
||||
let webhook_url =
|
||||
given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias);
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let mut net = given::a_network();
|
||||
|
@ -338,8 +335,8 @@ mod github {
|
|||
hostname,
|
||||
repo_path,
|
||||
};
|
||||
let hook1 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &webhook_url);
|
||||
let hook2 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &webhook_url);
|
||||
let hook1 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &repo_listen_url);
|
||||
let hook2 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &repo_listen_url);
|
||||
let hook3 =
|
||||
with::ReturnedWebhook::new(given::a_github_webhook_id(), &given::any_webhook_url());
|
||||
let hooks = [hook1, hook2, hook3];
|
||||
|
@ -354,14 +351,14 @@ mod github {
|
|||
net.add_post_response(
|
||||
format!("https://api.{hostname}/repos/{repo_path}/hooks").as_str(),
|
||||
StatusCode::OK,
|
||||
json!({"id": webhook_id, "config":{"url": webhook_url}})
|
||||
json!({"id": webhook_id, "config":{"url": repo_listen_url.to_string()}})
|
||||
.to_string()
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Ok(registered_webhook) = forge.register_webhook(&webhook_url).await);
|
||||
let_assert!(Ok(registered_webhook) = forge.register_webhook(&repo_listen_url).await);
|
||||
assert_eq!(
|
||||
registered_webhook.id(),
|
||||
&WebhookId::new(format!("{webhook_id}"))
|
||||
|
@ -375,8 +372,7 @@ mod github {
|
|||
repo_details.repo_config.is_some(),
|
||||
"repo_details needs to have repo_config for this test"
|
||||
);
|
||||
let webhook_url =
|
||||
given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias);
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let mut net = given::a_network();
|
||||
|
@ -396,7 +392,7 @@ mod github {
|
|||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Err(err) = forge.register_webhook(&webhook_url).await);
|
||||
let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await);
|
||||
assert!(
|
||||
matches!(err, git::forge::webhook::Error::FailedToRegister(_)),
|
||||
"{err:?}"
|
||||
|
@ -410,8 +406,7 @@ mod github {
|
|||
repo_details.repo_config.is_some(),
|
||||
"repo_details needs to have repo_config for this test"
|
||||
);
|
||||
let webhook_url =
|
||||
given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias);
|
||||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let mut net = given::a_network();
|
||||
|
@ -430,7 +425,7 @@ mod github {
|
|||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Err(err) = forge.register_webhook(&webhook_url).await);
|
||||
let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await);
|
||||
assert!(
|
||||
matches!(err, git::forge::webhook::Error::FailedToRegister(_)),
|
||||
"{err:?}"
|
||||
|
@ -439,6 +434,8 @@ mod github {
|
|||
}
|
||||
|
||||
pub mod with {
|
||||
use git_next_core::server::RepoListenUrl;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn get_webhooks_by_page(
|
||||
|
@ -476,21 +473,26 @@ mod github {
|
|||
pub config: Config,
|
||||
}
|
||||
impl ReturnedWebhook {
|
||||
pub fn new(id: i64, url: &WebhookUrl) -> Self {
|
||||
pub fn new(id: i64, url: &RepoListenUrl) -> Self {
|
||||
Self {
|
||||
id,
|
||||
config: Config { url: url.clone() },
|
||||
config: Config {
|
||||
url: url.to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Config {
|
||||
pub url: WebhookUrl,
|
||||
pub url: String,
|
||||
}
|
||||
}
|
||||
|
||||
mod given {
|
||||
|
||||
use git::RepoDetails;
|
||||
use git_next_core::server::RepoListenUrl;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn commit_states(
|
||||
|
@ -615,11 +617,15 @@ mod github {
|
|||
kxio::network::MockNetwork::new()
|
||||
}
|
||||
|
||||
pub fn a_webhook_url(forge_alias: &ForgeAlias, repo_alias: &RepoAlias) -> WebhookUrl {
|
||||
InboundWebhook::new(a_name()).url(forge_alias, repo_alias)
|
||||
pub fn a_repo_listen_url(repo_details: &RepoDetails) -> RepoListenUrl {
|
||||
RepoListenUrl::new((
|
||||
ListenUrl::new(a_name()),
|
||||
repo_details.forge.forge_alias().clone(),
|
||||
repo_details.repo_alias.clone(),
|
||||
))
|
||||
}
|
||||
pub fn any_webhook_url() -> WebhookUrl {
|
||||
given::a_webhook_url(&given::a_forge_alias(), &given::a_repo_alias())
|
||||
pub fn any_webhook_url() -> RepoListenUrl {
|
||||
given::a_repo_listen_url(&given::repo_details())
|
||||
}
|
||||
|
||||
pub fn a_name() -> String {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
//
|
||||
use crate as github;
|
||||
use git_next_core::{git, server, WebhookId};
|
||||
use git_next_core::{git, server::RepoListenUrl, WebhookId};
|
||||
|
||||
use kxio::network;
|
||||
|
||||
// https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#list-repository-webhooks
|
||||
pub async fn list(
|
||||
github: &github::Github,
|
||||
webhook_url: &server::WebhookUrl,
|
||||
repo_listen_url: &RepoListenUrl,
|
||||
) -> git::forge::webhook::Result<Vec<WebhookId>> {
|
||||
let mut ids: Vec<WebhookId> = vec![];
|
||||
let repo_details = &github.repo_details;
|
||||
|
@ -39,7 +39,11 @@ pub async fn list(
|
|||
return Ok(ids);
|
||||
}
|
||||
for hook in list {
|
||||
if hook.url().as_ref().starts_with(webhook_url.as_ref()) {
|
||||
if hook
|
||||
.url()
|
||||
.as_ref()
|
||||
.starts_with(&repo_listen_url.to_string())
|
||||
{
|
||||
ids.push(hook.id());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
//
|
||||
use crate::{self as github, webhook};
|
||||
use git_next_core::{git, server, RegisteredWebhook, WebhookAuth, WebhookId};
|
||||
use git_next_core::{git, server::RepoListenUrl, RegisteredWebhook, WebhookAuth, WebhookId};
|
||||
|
||||
use kxio::network;
|
||||
|
||||
// https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#create-a-repository-webhook
|
||||
pub async fn register(
|
||||
github: &github::Github,
|
||||
webhook_url: &server::WebhookUrl,
|
||||
repo_listen_url: &RepoListenUrl,
|
||||
) -> git::forge::webhook::Result<RegisteredWebhook> {
|
||||
let repo_details = &github.repo_details;
|
||||
if repo_details.repo_config.is_none() {
|
||||
|
@ -15,7 +15,7 @@ pub async fn register(
|
|||
};
|
||||
|
||||
// remove any lingering webhooks for the same URL
|
||||
let existing_webhook_ids = webhook::list(github, webhook_url).await?;
|
||||
let existing_webhook_ids = webhook::list(github, repo_listen_url).await?;
|
||||
for webhook_id in existing_webhook_ids {
|
||||
webhook::unregister(github, &webhook_id).await?;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ pub async fn register(
|
|||
"active": true,
|
||||
"events": ["push"],
|
||||
"config": {
|
||||
"url": webhook_url.as_ref(),
|
||||
"url": repo_listen_url.as_ref(),
|
||||
"content_type": "json",
|
||||
"secret": authorisation.to_string(),
|
||||
"insecure_ssl": "0",
|
||||
|
|
Loading…
Reference in a new issue