Paul Campbell
206e64cd5b
Some checks failed
ci/woodpecker/cron/cron-docker-builder Pipeline was successful
ci/woodpecker/cron/push-next Pipeline was successful
ci/woodpecker/cron/tag-created Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful
Rust / build (push) Has been cancelled
This allows for more than one forge to be configured and for the webhook to correctly route incoming messages.
234 lines
7.7 KiB
Rust
234 lines
7.7 KiB
Rust
//
|
|
use std::path::PathBuf;
|
|
|
|
use actix::prelude::*;
|
|
|
|
use config::server::{ServerConfig, ServerStorage, Webhook};
|
|
use git_next_config::{
|
|
self as config, ForgeAlias, ForgeConfig, GitDir, RepoAlias, ServerRepoConfig,
|
|
};
|
|
use git_next_git::{Generation, RepoDetails, Repository};
|
|
use git_next_repo_actor::{CloneRepo, RepoActor};
|
|
use kxio::{fs::FileSystem, network::Network};
|
|
use tracing::{error, info, warn};
|
|
|
|
use crate::actors::{
|
|
file_watcher::FileUpdated,
|
|
webhook::{AddWebhookRecipient, ShutdownWebhook, WebhookActor, WebhookRouter},
|
|
};
|
|
|
|
#[derive(Debug, derive_more::Display, derive_more::From)]
|
|
pub enum Error {
|
|
#[display("Failed to create data directories")]
|
|
FailedToCreateDataDirectory(kxio::fs::Error),
|
|
|
|
#[display("The forge data path is not a directory: {path:?}")]
|
|
ForgeDirIsNotDirectory {
|
|
path: PathBuf,
|
|
},
|
|
|
|
Config(config::server::Error),
|
|
|
|
Io(std::io::Error),
|
|
}
|
|
type Result<T> = core::result::Result<T, Error>;
|
|
|
|
pub struct Server {
|
|
generation: Generation,
|
|
webhook: Option<Addr<WebhookActor>>,
|
|
fs: FileSystem,
|
|
net: Network,
|
|
repo: Repository,
|
|
}
|
|
impl Actor for Server {
|
|
type Context = Context<Self>;
|
|
}
|
|
impl Handler<FileUpdated> for Server {
|
|
type Result = ();
|
|
|
|
fn handle(&mut self, _msg: FileUpdated, ctx: &mut Self::Context) -> Self::Result {
|
|
let server_config = match ServerConfig::load(&self.fs) {
|
|
Ok(server_config) => server_config,
|
|
Err(err) => {
|
|
error!("Failed to load config file. Error: {}", err);
|
|
return;
|
|
}
|
|
};
|
|
ctx.notify(server_config);
|
|
}
|
|
}
|
|
impl Handler<ServerConfig> for Server {
|
|
type Result = ();
|
|
|
|
#[allow(clippy::cognitive_complexity)] // TODO: (#75) reduce complexity
|
|
fn handle(&mut self, msg: ServerConfig, _ctx: &mut Self::Context) -> Self::Result {
|
|
let Ok(socket_addr) = msg.http() else {
|
|
warn!("Unable to parse http.addr");
|
|
return;
|
|
};
|
|
if let Some(webhook) = self.webhook.take() {
|
|
webhook.do_send(ShutdownWebhook);
|
|
}
|
|
self.generation.inc();
|
|
let server_config = msg;
|
|
// Server Storage
|
|
let dir = server_config.storage().path();
|
|
if !dir.exists() {
|
|
info!(?dir, "server storage doesn't exist - creating it");
|
|
if let Err(err) = self.fs.dir_create(dir) {
|
|
error!(?err, ?dir, "Failed to create server storage");
|
|
return;
|
|
}
|
|
}
|
|
let Ok(canon) = dir.canonicalize() else {
|
|
error!(?dir, "Failed to confirm server storage");
|
|
return;
|
|
};
|
|
info!(dir = ?canon, "server storage");
|
|
|
|
// Forge directories in Server Storage
|
|
let server_storage = server_config.storage();
|
|
if let Err(err) = self.create_forge_data_directories(&server_config, dir) {
|
|
error!(?err, "Failure creating forge storage");
|
|
return;
|
|
}
|
|
|
|
// Webhook Server
|
|
info!("Starting Webhook Server...");
|
|
let webhook_router = WebhookRouter::new().start();
|
|
let webhook = server_config.webhook();
|
|
|
|
// Forge Actors
|
|
for (forge_alias, forge_config) in server_config.forges() {
|
|
self.create_forge_repos(forge_config, forge_alias.clone(), server_storage, webhook)
|
|
.into_iter()
|
|
.map(|a| self.start_actor(a))
|
|
.map(|(repo_alias, addr)| {
|
|
AddWebhookRecipient::new(forge_alias.clone(), repo_alias, addr.recipient())
|
|
})
|
|
.for_each(|msg| webhook_router.do_send(msg));
|
|
}
|
|
|
|
let webhook = WebhookActor::new(socket_addr, webhook_router.recipient()).start();
|
|
self.webhook.replace(webhook);
|
|
}
|
|
}
|
|
impl Server {
|
|
pub fn new(fs: FileSystem, net: Network, repo: Repository) -> Self {
|
|
let generation = Generation::new();
|
|
Self {
|
|
generation,
|
|
webhook: None,
|
|
fs,
|
|
net,
|
|
repo,
|
|
}
|
|
}
|
|
fn create_forge_data_directories(
|
|
&self,
|
|
server_config: &ServerConfig,
|
|
server_dir: &std::path::Path,
|
|
) -> Result<()> {
|
|
for (forge_name, _forge_config) in server_config.forges() {
|
|
let forge_dir: PathBuf = (&forge_name).into();
|
|
let path = server_dir.join(&forge_dir);
|
|
if self.fs.path_exists(&path)? {
|
|
if !self.fs.path_is_dir(&path)? {
|
|
return Err(Error::ForgeDirIsNotDirectory { path });
|
|
}
|
|
} else {
|
|
info!(%forge_name, ?path, "creating storage");
|
|
self.fs.dir_create_all(&path)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn create_forge_repos(
|
|
&self,
|
|
forge_config: &ForgeConfig,
|
|
forge_name: ForgeAlias,
|
|
server_storage: &ServerStorage,
|
|
webhook: &Webhook,
|
|
) -> Vec<(ForgeAlias, RepoAlias, RepoActor)> {
|
|
let span =
|
|
tracing::info_span!("create_forge_repos", name = %forge_name, config = %forge_config);
|
|
|
|
let _guard = span.enter();
|
|
info!("Creating Forge");
|
|
let mut repos = vec![];
|
|
let creator = self.create_actor(forge_name, forge_config.clone(), server_storage, webhook);
|
|
for (repo_alias, server_repo_config) in forge_config.repos() {
|
|
let forge_repo = creator((repo_alias, server_repo_config));
|
|
info!(
|
|
alias = %forge_repo.1,
|
|
"Created Repo"
|
|
);
|
|
repos.push(forge_repo);
|
|
}
|
|
repos
|
|
}
|
|
|
|
fn create_actor(
|
|
&self,
|
|
forge_name: ForgeAlias,
|
|
forge_config: ForgeConfig,
|
|
server_storage: &ServerStorage,
|
|
webhook: &Webhook,
|
|
) -> impl Fn((RepoAlias, &ServerRepoConfig)) -> (ForgeAlias, RepoAlias, RepoActor) {
|
|
let server_storage = server_storage.clone();
|
|
let webhook = webhook.clone();
|
|
let net = self.net.clone();
|
|
let repo = self.repo.clone();
|
|
let generation = self.generation;
|
|
move |(repo_alias, server_repo_config)| {
|
|
let span = tracing::info_span!("create_actor", alias = %repo_alias, config = %server_repo_config);
|
|
let _guard = span.enter();
|
|
info!("Creating Repo");
|
|
let gitdir = server_repo_config.gitdir().map_or_else(
|
|
|| {
|
|
GitDir::from(
|
|
server_storage
|
|
.path()
|
|
.join(forge_name.to_string())
|
|
.join(repo_alias.to_string()),
|
|
)
|
|
},
|
|
|gitdir| gitdir,
|
|
);
|
|
// INFO: can't canonicalise gitdir as the path needs to exist to do that and we may not
|
|
// have cloned the repo yet
|
|
let repo_details = RepoDetails::new(
|
|
generation,
|
|
&repo_alias,
|
|
server_repo_config,
|
|
&forge_name,
|
|
&forge_config,
|
|
gitdir,
|
|
);
|
|
info!("Starting Repo Actor");
|
|
let actor = RepoActor::new(
|
|
repo_details,
|
|
webhook.clone(),
|
|
generation,
|
|
net.clone(),
|
|
repo.clone(),
|
|
);
|
|
(forge_name.clone(), repo_alias, actor)
|
|
}
|
|
}
|
|
|
|
fn start_actor(
|
|
&self,
|
|
actor: (ForgeAlias, RepoAlias, RepoActor),
|
|
) -> (RepoAlias, Addr<RepoActor>) {
|
|
let (forge_name, repo_alias, actor) = actor;
|
|
let span = tracing::info_span!("start_actor", forge = %forge_name, repo = %repo_alias);
|
|
let _guard = span.enter();
|
|
let addr = actor.start();
|
|
addr.do_send(CloneRepo);
|
|
info!("Started");
|
|
(repo_alias, addr)
|
|
}
|
|
}
|