Compare commits

..

3 commits

Author SHA1 Message Date
89975894a4 feat: terminate process if config file is invalid
Some checks failed
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) Failing after 1m14s
2024-07-30 11:57:39 +01:00
1650e93920 feat: return better errors to user on server failure
All checks were successful
Rust / build (push) Successful in 1m14s
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
2024-07-30 11:18:29 +01:00
9a9c73d929 feat: return better errors to the user on init
All checks were successful
Rust / build (push) Successful in 1m15s
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
2024-07-30 11:18:29 +01:00
10 changed files with 67 additions and 67 deletions

View file

@ -80,6 +80,7 @@ derive_more = { version = "1.0.0-beta.6", features = [
"from", "from",
] } ] }
derive-with = "0.5" derive-with = "0.5"
anyhow = "1.0"
thiserror = "1.0" thiserror = "1.0"
pike = "0.1" pike = "0.1"

View file

@ -50,6 +50,7 @@ actix-rt = { workspace = true }
# boilerplate # boilerplate
derive_more = { workspace = true } derive_more = { workspace = true }
derive-with = { workspace = true } derive-with = { workspace = true }
anyhow = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
# Webhooks # Webhooks

View file

@ -2,6 +2,7 @@
use actix::prelude::*; use actix::prelude::*;
use actix::Recipient; use actix::Recipient;
use anyhow::{Context, Result};
use notify::event::ModifyKind; use notify::event::ModifyKind;
use notify::Watcher; use notify::Watcher;
use tracing::error; use tracing::error;
@ -19,15 +20,13 @@ pub enum Error {
#[error("io")] #[error("io")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
} }
pub async fn watch_file(path: PathBuf, recipient: Recipient<FileUpdated>) { pub async fn watch_file(path: PathBuf, recipient: Recipient<FileUpdated>) -> Result<()> {
let (tx, rx) = std::sync::mpsc::channel(); let (tx, rx) = std::sync::mpsc::channel();
#[allow(clippy::expect_used)] let mut handler = notify::recommended_watcher(tx).context("file watcher")?;
let mut handler = notify::recommended_watcher(tx).expect("file watcher");
#[allow(clippy::expect_used)]
handler handler
.watch(&path, notify::RecursiveMode::NonRecursive) .watch(&path, notify::RecursiveMode::NonRecursive)
.expect("watch file"); .with_context(|| format!("Watch file: {path:?}"))?;
info!("Watching: {:?}", path); info!("Watching: {:?}", path);
async move { async move {
loop { loop {
@ -55,4 +54,5 @@ pub async fn watch_file(path: PathBuf, recipient: Recipient<FileUpdated>) {
} }
} }
.await; .await;
Ok(())
} }

View file

@ -1,29 +1,18 @@
// //
use anyhow::{Context, Result};
use kxio::fs::FileSystem; use kxio::fs::FileSystem;
pub fn run(fs: FileSystem) { pub fn run(fs: FileSystem) -> Result<()> {
let file_name = ".git-next.toml"; let pathbuf = fs.base().join(".git-next.toml");
let pathbuf = fs.base().join(file_name); if fs
match fs.path_exists(&pathbuf) { .path_exists(&pathbuf)
Ok(exists) => { .with_context(|| format!("Checking for existing file: {pathbuf:?}"))?
if exists { {
eprintln!( eprintln!("The configuration file already exists at {pathbuf:?} - not overwritting it.",);
"The configuration file already exists at {} - not overwritting it.",
file_name
);
} else { } else {
match fs.file_write(&pathbuf, include_str!("../default.toml")) { fs.file_write(&pathbuf, include_str!("../default.toml"))
Ok(_) => { .with_context(|| format!("Writing file: {pathbuf:?}"))?;
println!("Created a default configuration file at {}", file_name); println!("Created a default configuration file at {pathbuf:?}");
}
Err(e) => {
eprintln!("Failed to write to the configuration file: {}", e)
}
}
}
}
Err(err) => {
eprintln!("Could not check if file exist: {} - {err:?}", file_name);
}
} }
Ok(())
} }

View file

@ -13,6 +13,7 @@ use git_next_core::git;
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::Result;
use clap::Parser; use clap::Parser;
use kxio::{fs, network::Network}; use kxio::{fs, network::Network};
@ -34,7 +35,7 @@ enum Server {
Start, Start,
} }
fn main() { fn main() -> Result<()> {
let fs = fs::new(PathBuf::default()); let fs = fs::new(PathBuf::default());
let net = Network::new_real(); let net = Network::new_real();
let repository_factory = git::repository::factory::real(); let repository_factory = git::repository::factory::real();
@ -42,16 +43,17 @@ fn main() {
match commands.command { match commands.command {
Command::Init => { Command::Init => {
init::run(fs); init::run(fs)?;
} }
Command::Server(server) => match server { Command::Server(server) => match server {
Server::Init => { Server::Init => {
server::init(fs); server::init(fs)?;
} }
Server::Start => { Server::Start => {
let sleep_duration = std::time::Duration::from_secs(10); let sleep_duration = std::time::Duration::from_secs(10);
server::start(fs, net, repository_factory, sleep_duration); server::start(fs, net, repository_factory, sleep_duration)?;
} }
}, },
} }
Ok(())
} }

View file

@ -12,13 +12,13 @@ impl Handler<FileUpdated> for ServerActor {
type Result = (); type Result = ();
fn handle(&mut self, _msg: FileUpdated, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, _msg: FileUpdated, ctx: &mut Self::Context) -> Self::Result {
let server_config = match ServerConfig::load(&self.fs) { match ServerConfig::load(&self.fs) {
Ok(server_config) => server_config, Ok(server_config) => {
Err(err) => {
tracing::error!("Failed to load config file. Error: {}", err);
return;
}
};
self.do_send(ReceiveServerConfig::new(server_config), ctx); self.do_send(ReceiveServerConfig::new(server_config), ctx);
} }
Err(err) => {
self.abort(ctx, format!("Failed to load config file. Error: {}", err));
}
};
}
} }

View file

@ -12,18 +12,15 @@ impl Handler<ReceiveServerConfig> for ServerActor {
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.http() else {
tracing::error!("Unable to parse http.addr"); self.abort(ctx, "Unable to parse http.addr");
return;
}; };
let Some(server_storage) = self.server_storage(&msg) else { let Some(server_storage) = self.server_storage(&msg) else {
tracing::error!("Server storage not available"); self.abort(ctx, "Server storage not available");
return;
}; };
if msg.inbound_webhook().base_url().ends_with('/') { if msg.inbound_webhook().base_url().ends_with('/') {
tracing::error!("webhook.url must not end with a '/'"); self.abort(ctx, "webhook.url must not end with a '/'");
return;
} }
self.do_send( self.do_send(

View file

@ -229,6 +229,19 @@ impl ServerActor {
Some(server_storage) Some(server_storage)
} }
/// Attempts to gracefully shutdown the server before terminating the process.
fn abort(
&mut self,
ctx: &mut <Self as actix::Actor>::Context,
message: impl Into<String>,
) -> ! {
tracing::error!("Aborting: {}", message.into());
self.do_send(crate::server::actor::messages::Shutdown, ctx);
std::thread::sleep(std::time::Duration::from_millis(200));
System::current().stop();
std::process::exit(-1);
}
fn do_send<M>(&mut self, msg: M, _ctx: &mut <Self as actix::Actor>::Context) fn do_send<M>(&mut 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,

View file

@ -10,31 +10,26 @@ use crate::file_watcher::{watch_file, FileUpdated};
use actor::ServerActor; use actor::ServerActor;
use git_next_core::git::RepositoryFactory; use git_next_core::git::RepositoryFactory;
use anyhow::{Context, Result};
use kxio::{fs::FileSystem, network::Network}; use kxio::{fs::FileSystem, network::Network};
use tracing::info; use tracing::info;
use std::path::PathBuf; use std::path::PathBuf;
pub fn init(fs: FileSystem) { pub fn init(fs: FileSystem) -> Result<()> {
let file_name = "git-next-server.toml"; let file_name = "git-next-server.toml";
let pathbuf = PathBuf::from(file_name); let pathbuf = PathBuf::from(file_name);
let Ok(exists) = fs.path_exists(&pathbuf) else { if fs
eprintln!("Could not check if file exist: {}", file_name); .path_exists(&pathbuf)
return; .with_context(|| format!("Checking for existing file: {pathbuf:?}"))?
}; {
if exists { eprintln!("The configuration file already exists at {pathbuf:?} - not overwritting it.",);
eprintln!(
"The configuration file already exists at {} - not overwritting it.",
file_name
);
} else { } else {
match fs.file_write(&pathbuf, include_str!("server-default.toml")) { fs.file_write(&pathbuf, include_str!("server-default.toml"))
Ok(_) => println!("Created a default configuration file at {}", file_name), .with_context(|| format!("Writing file: {pathbuf:?}"))?;
Err(e) => { println!("Created a default configuration file at {pathbuf:?}",);
eprintln!("Failed to write to the configuration file: {}", e)
}
}
} }
Ok(())
} }
pub fn start( pub fn start(
@ -42,7 +37,7 @@ pub fn start(
net: Network, net: Network,
repo: Box<dyn RepositoryFactory>, repo: Box<dyn RepositoryFactory>,
sleep_duration: std::time::Duration, sleep_duration: std::time::Duration,
) { ) -> Result<()> {
init_logging(); init_logging();
let execution = async move { let execution = async move {
@ -51,7 +46,10 @@ pub fn start(
server.do_send(FileUpdated); server.do_send(FileUpdated);
info!("Starting File Watcher..."); info!("Starting File Watcher...");
watch_file("git-next-server.toml".into(), server.clone().recipient()).await; #[allow(clippy::expect_used)]
watch_file("git-next-server.toml".into(), server.clone().recipient())
.await
.expect("file watcher");
info!("Server running - Press Ctrl-C to stop..."); info!("Server running - Press Ctrl-C to stop...");
let _ = actix_rt::signal::ctrl_c().await; let _ = actix_rt::signal::ctrl_c().await;
@ -62,9 +60,8 @@ pub fn start(
}; };
let system = System::new(); let system = System::new();
Arbiter::current().spawn(execution); Arbiter::current().spawn(execution);
if let Err(err) = system.run() { system.run()?;
tracing::error!(?err, "") Ok(())
};
} }
pub fn init_logging() { pub fn init_logging() {

View file

@ -8,7 +8,7 @@ mod init {
let file = fs.base().join(".git-next.toml"); let file = fs.base().join(".git-next.toml");
fs.file_write(&file, "contents")?; fs.file_write(&file, "contents")?;
crate::init::run(fs.clone()); crate::init::run(fs.clone())?;
assert_eq!( assert_eq!(
fs.file_read_to_string(&file)?, fs.file_read_to_string(&file)?,
@ -23,7 +23,7 @@ mod init {
fn should_create_default_file_if_not_exists() -> TestResult { fn should_create_default_file_if_not_exists() -> TestResult {
let fs = kxio::fs::temp()?; let fs = kxio::fs::temp()?;
crate::init::run(fs.clone()); crate::init::run(fs.clone())?;
let file = fs.base().join(".git-next.toml"); let file = fs.base().join(".git-next.toml");