2024-05-22 08:41:30 +01:00
|
|
|
//
|
|
|
|
|
|
|
|
use std::{
|
2024-06-19 07:03:08 +01:00
|
|
|
collections::BTreeMap,
|
2024-05-22 08:41:30 +01:00
|
|
|
net::SocketAddr,
|
2024-07-31 07:20:42 +01:00
|
|
|
ops::Deref,
|
2024-05-22 08:41:30 +01:00
|
|
|
path::{Path, PathBuf},
|
|
|
|
str::FromStr,
|
|
|
|
};
|
|
|
|
|
2024-07-31 07:20:42 +01:00
|
|
|
use derive_more::Display;
|
2024-05-22 08:41:30 +01:00
|
|
|
use kxio::fs::FileSystem;
|
2024-07-18 20:51:47 +01:00
|
|
|
use secrecy::Secret;
|
2024-07-31 07:20:42 +01:00
|
|
|
use serde::{Deserialize, Serialize};
|
2024-05-22 08:41:30 +01:00
|
|
|
use tracing::info;
|
|
|
|
|
2024-07-25 09:02:43 +01:00
|
|
|
use crate::{
|
|
|
|
config::{ForgeAlias, ForgeConfig, RepoAlias},
|
|
|
|
newtype,
|
|
|
|
};
|
2024-05-22 08:41:30 +01:00
|
|
|
|
2024-06-03 07:38:59 +01:00
|
|
|
#[derive(Debug, thiserror::Error)]
|
2024-05-22 08:41:30 +01:00
|
|
|
pub enum Error {
|
2024-06-03 07:38:59 +01:00
|
|
|
#[error("fs: {0}")]
|
|
|
|
KxioFs(#[from] kxio::fs::Error),
|
|
|
|
|
|
|
|
#[error("deserialise toml: {0}")]
|
|
|
|
TomlDe(#[from] toml::de::Error),
|
|
|
|
|
|
|
|
#[error("parse IP addres/port: {0}")]
|
|
|
|
AddressParse(#[from] std::net::AddrParseError),
|
2024-05-22 08:41:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type Result<T> = core::result::Result<T, Error>;
|
|
|
|
|
|
|
|
/// Mapped from the `git-next-server.toml` file
|
2024-06-19 07:03:08 +01:00
|
|
|
#[derive(
|
|
|
|
Clone,
|
|
|
|
Debug,
|
|
|
|
derive_more::From,
|
|
|
|
PartialEq,
|
|
|
|
Eq,
|
|
|
|
PartialOrd,
|
|
|
|
Ord,
|
|
|
|
derive_more::AsRef,
|
|
|
|
serde::Deserialize,
|
|
|
|
derive_more::Constructor,
|
|
|
|
)]
|
2024-05-22 08:41:30 +01:00
|
|
|
pub struct ServerConfig {
|
2024-07-31 07:20:42 +01:00
|
|
|
listen: Listen,
|
|
|
|
shout: Shout,
|
2024-05-22 08:41:30 +01:00
|
|
|
storage: ServerStorage,
|
2024-06-19 07:03:08 +01:00
|
|
|
pub forge: BTreeMap<String, ForgeConfig>,
|
2024-05-22 08:41:30 +01:00
|
|
|
}
|
|
|
|
impl ServerConfig {
|
|
|
|
#[tracing::instrument(skip_all)]
|
|
|
|
pub fn load(fs: &FileSystem) -> Result<Self> {
|
|
|
|
let file = fs.base().join("git-next-server.toml");
|
|
|
|
info!(?file, "");
|
|
|
|
let str = fs.file_read_to_string(&file)?;
|
2024-06-03 07:38:59 +01:00
|
|
|
Ok(toml::from_str(&str)?)
|
2024-05-22 08:41:30 +01:00
|
|
|
}
|
|
|
|
|
2024-05-29 19:22:05 +01:00
|
|
|
pub fn forges(&self) -> impl Iterator<Item = (ForgeAlias, &ForgeConfig)> {
|
2024-05-22 08:41:30 +01:00
|
|
|
self.forge
|
|
|
|
.iter()
|
2024-05-29 19:22:05 +01:00
|
|
|
.map(|(alias, forge)| (ForgeAlias::new(alias.clone()), forge))
|
2024-05-22 08:41:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub const fn storage(&self) -> &ServerStorage {
|
|
|
|
&self.storage
|
|
|
|
}
|
|
|
|
|
2024-07-31 07:20:42 +01:00
|
|
|
pub const fn shout(&self) -> &Shout {
|
|
|
|
&self.shout
|
2024-07-18 20:51:47 +01:00
|
|
|
}
|
|
|
|
|
2024-07-31 07:20:42 +01:00
|
|
|
pub const fn listen(&self) -> &Listen {
|
|
|
|
&self.listen
|
2024-05-22 08:41:30 +01:00
|
|
|
}
|
|
|
|
|
2024-07-31 07:20:42 +01:00
|
|
|
pub fn listen_socket_addr(&self) -> Result<SocketAddr> {
|
|
|
|
self.listen.http.socket_addr()
|
2024-05-22 08:41:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-31 07:20:42 +01:00
|
|
|
/// Defines how the server receives webhook notifications from forges.
|
2024-06-19 07:03:08 +01:00
|
|
|
#[derive(
|
|
|
|
Clone,
|
|
|
|
Debug,
|
|
|
|
derive_more::From,
|
|
|
|
PartialEq,
|
|
|
|
Eq,
|
|
|
|
PartialOrd,
|
|
|
|
Ord,
|
|
|
|
derive_more::AsRef,
|
|
|
|
serde::Deserialize,
|
|
|
|
derive_more::Constructor,
|
|
|
|
)]
|
2024-07-31 07:20:42 +01:00
|
|
|
pub struct Listen {
|
|
|
|
http: Http,
|
|
|
|
url: ListenUrl,
|
2024-05-22 08:41:30 +01:00
|
|
|
}
|
2024-07-31 07:20:42 +01:00
|
|
|
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
|
2024-05-22 08:41:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-31 07:20:42 +01:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Defines the port the server will listen to for incoming webhooks messages
|
2024-06-19 07:03:08 +01:00
|
|
|
#[derive(
|
|
|
|
Clone,
|
|
|
|
Debug,
|
|
|
|
derive_more::From,
|
|
|
|
PartialEq,
|
|
|
|
Eq,
|
|
|
|
PartialOrd,
|
|
|
|
Ord,
|
|
|
|
derive_more::AsRef,
|
|
|
|
serde::Deserialize,
|
|
|
|
derive_more::Constructor,
|
|
|
|
)]
|
2024-07-31 07:20:42 +01:00
|
|
|
pub struct Http {
|
|
|
|
addr: String,
|
|
|
|
port: u16,
|
2024-05-22 08:41:30 +01:00
|
|
|
}
|
2024-07-31 07:20:42 +01:00
|
|
|
impl Http {
|
|
|
|
fn socket_addr(&self) -> Result<SocketAddr> {
|
|
|
|
Ok(SocketAddr::from_str(&format!(
|
|
|
|
"{}:{}",
|
|
|
|
self.addr, self.port
|
|
|
|
))?)
|
2024-06-08 15:06:34 +01:00
|
|
|
}
|
2024-05-22 08:41:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// The directory to store server data, such as cloned repos
|
2024-06-19 07:03:08 +01:00
|
|
|
#[derive(
|
|
|
|
Clone,
|
|
|
|
Debug,
|
|
|
|
derive_more::From,
|
|
|
|
PartialEq,
|
|
|
|
Eq,
|
|
|
|
PartialOrd,
|
|
|
|
Ord,
|
|
|
|
derive_more::AsRef,
|
|
|
|
serde::Deserialize,
|
|
|
|
derive_more::Constructor,
|
|
|
|
)]
|
2024-05-22 08:41:30 +01:00
|
|
|
pub struct ServerStorage {
|
|
|
|
path: PathBuf,
|
|
|
|
}
|
|
|
|
impl ServerStorage {
|
|
|
|
pub fn path(&self) -> &Path {
|
|
|
|
self.path.as_path()
|
|
|
|
}
|
|
|
|
}
|
2024-07-18 20:51:47 +01:00
|
|
|
|
|
|
|
/// Defines the Webhook Forges should send updates to
|
|
|
|
/// Must be an address that is accessible from the remote forge
|
|
|
|
#[derive(
|
|
|
|
Clone,
|
|
|
|
Debug,
|
2024-07-31 07:20:42 +01:00
|
|
|
Default,
|
2024-07-18 20:51:47 +01:00
|
|
|
derive_more::From,
|
|
|
|
PartialEq,
|
|
|
|
Eq,
|
|
|
|
PartialOrd,
|
|
|
|
Ord,
|
|
|
|
derive_more::AsRef,
|
|
|
|
serde::Deserialize,
|
|
|
|
)]
|
2024-07-31 07:20:42 +01:00
|
|
|
pub struct Shout {
|
2024-07-18 20:51:47 +01:00
|
|
|
webhook: Option<OutboundWebhook>,
|
2024-08-01 19:48:59 +01:00
|
|
|
email: Option<EmailConfig>,
|
2024-07-18 20:51:47 +01:00
|
|
|
}
|
2024-07-31 07:20:42 +01:00
|
|
|
impl Shout {
|
2024-07-21 13:44:44 +01:00
|
|
|
pub const fn new_webhook(webhook: OutboundWebhook) -> Self {
|
2024-07-18 20:51:47 +01:00
|
|
|
Self {
|
|
|
|
webhook: Some(webhook),
|
2024-08-01 19:48:59 +01:00
|
|
|
email: None,
|
2024-07-18 20:51:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-21 13:44:44 +01:00
|
|
|
pub const fn webhook(&self) -> Option<&OutboundWebhook> {
|
|
|
|
self.webhook.as_ref()
|
|
|
|
}
|
|
|
|
|
2024-07-18 20:51:47 +01:00
|
|
|
pub fn webhook_url(&self) -> Option<String> {
|
|
|
|
self.webhook.clone().map(|x| x.url)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn webhook_secret(&self) -> Option<Secret<String>> {
|
|
|
|
self.webhook.clone().map(|x| x.secret).map(Secret::new)
|
|
|
|
}
|
2024-08-01 19:48:59 +01:00
|
|
|
|
|
|
|
pub const fn email(&self) -> Option<&EmailConfig> {
|
|
|
|
self.email.as_ref()
|
|
|
|
}
|
2024-07-18 20:51:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(
|
|
|
|
Clone,
|
|
|
|
Debug,
|
|
|
|
derive_more::From,
|
|
|
|
PartialEq,
|
|
|
|
Eq,
|
|
|
|
PartialOrd,
|
|
|
|
Ord,
|
|
|
|
serde::Deserialize,
|
|
|
|
derive_more::Constructor,
|
|
|
|
)]
|
|
|
|
pub struct OutboundWebhook {
|
|
|
|
url: String,
|
|
|
|
secret: String,
|
|
|
|
}
|
|
|
|
impl OutboundWebhook {
|
|
|
|
pub fn url(&self) -> &str {
|
|
|
|
self.url.as_ref()
|
|
|
|
}
|
|
|
|
pub fn secret(&self) -> Secret<String> {
|
|
|
|
Secret::new(self.secret.clone())
|
|
|
|
}
|
|
|
|
}
|
2024-08-01 19:48:59 +01:00
|
|
|
|
|
|
|
#[derive(
|
|
|
|
Clone,
|
|
|
|
Debug,
|
|
|
|
derive_more::From,
|
|
|
|
PartialEq,
|
|
|
|
Eq,
|
|
|
|
PartialOrd,
|
|
|
|
Ord,
|
|
|
|
serde::Deserialize,
|
|
|
|
derive_more::Constructor,
|
|
|
|
)]
|
|
|
|
pub struct EmailConfig {
|
|
|
|
from: String,
|
|
|
|
to: String,
|
|
|
|
// email will be sent via sendmail, unless smtp is specified
|
|
|
|
smtp: Option<SmtpConfig>,
|
|
|
|
}
|
|
|
|
impl EmailConfig {
|
|
|
|
pub fn from(&self) -> &str {
|
|
|
|
&self.from
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn to(&self) -> &str {
|
|
|
|
&self.to
|
|
|
|
}
|
|
|
|
|
|
|
|
pub const fn smtp(&self) -> Option<&SmtpConfig> {
|
|
|
|
self.smtp.as_ref()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(
|
|
|
|
Clone,
|
|
|
|
Debug,
|
|
|
|
derive_more::From,
|
|
|
|
PartialEq,
|
|
|
|
Eq,
|
|
|
|
PartialOrd,
|
|
|
|
Ord,
|
|
|
|
serde::Deserialize,
|
|
|
|
derive_more::Constructor,
|
|
|
|
)]
|
|
|
|
pub struct SmtpConfig {
|
|
|
|
hostname: String,
|
|
|
|
username: String,
|
|
|
|
password: String,
|
|
|
|
}
|
|
|
|
impl SmtpConfig {
|
|
|
|
pub fn username(&self) -> &str {
|
|
|
|
&self.username
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn password(&self) -> &str {
|
|
|
|
&self.password
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn hostname(&self) -> &str {
|
|
|
|
&self.hostname
|
|
|
|
}
|
|
|
|
}
|