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