diff --git a/.woodpecker/push-next.yml b/.woodpecker/push-next.yml index 8e11c2d..cdfe4d2 100644 --- a/.woodpecker/push-next.yml +++ b/.woodpecker/push-next.yml @@ -1,5 +1,4 @@ steps: - todo_check: # INFO: https://woodpecker-ci.org/plugins/TODO-Checker image: codeberg.org/epsilon_02/todo-checker:1.1 @@ -8,8 +7,8 @@ steps: branch: next settings: # git-next-woodpecker-todo-checker - read:issue - repository_token: '776a3b928b852472c2af727a360c85c00af64b9f' - prefix_regex: "(#|//) (TODO|FIXME): " + repository_token: "776a3b928b852472c2af727a360c85c00af64b9f" + prefix_regex: "(#|//) (TODO|FIXME): " debug: false lint_and_build: @@ -21,7 +20,7 @@ steps: CARGO_TERM_COLOR: always commands: - cargo fmt --all -- --check - - cargo clippy -- -D warnings -W clippy::nursery -W clippy::unwrap_used -W clippy::expect_used + - cargo clippy -- -D warnings - cargo build test: diff --git a/Cargo.toml b/Cargo.toml index c478073..ab594cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ anyhow = "1.0" cc-cli = { version = "0.1" } [lints.clippy] -nursery = "warn" +nursery = { level = "warn", priority = -1 } # pedantic = "warn" unwrap_used = "warn" expect_used = "warn" diff --git a/src/server/actors/repo/config.rs b/src/server/actors/repo/config.rs index d90b731..c36c8b9 100644 --- a/src/server/actors/repo/config.rs +++ b/src/server/actors/repo/config.rs @@ -1,18 +1,25 @@ use actix::prelude::*; -use tracing::error; +use tracing::{error, info}; -use crate::server::{config::RepoDetails, gitforge}; +use crate::server::{config, gitforge}; use super::{LoadedConfig, RepoActor}; /// Loads the [RepoConfig] from the `.git-next.toml` file in the repository -pub async fn load(repo_details: RepoDetails, addr: Addr, forge: gitforge::Forge) { - let repo_config = match crate::server::config::load::load(&repo_details, &forge).await { +#[tracing::instrument(skip_all, fields(branch = %repo_details.branch))] +pub async fn load( + repo_details: config::RepoDetails, + addr: Addr, + forge: gitforge::Forge, +) { + info!("Loading .git-next.toml from repo"); + let repo_config = match config::load::load(&repo_details, &forge).await { Ok(repo_config) => repo_config, Err(err) => { error!(?err, "Failed to load config"); return; } }; + info!("Loaded .git-next.toml from repo"); addr.do_send(LoadedConfig(repo_config)); } diff --git a/src/server/actors/repo/mod.rs b/src/server/actors/repo/mod.rs index 03a0929..a7e391e 100644 --- a/src/server/actors/repo/mod.rs +++ b/src/server/actors/repo/mod.rs @@ -5,7 +5,7 @@ pub mod webhook; use actix::prelude::*; use kxio::network::Network; -use tracing::{info, warn}; +use tracing::{debug, info, warn, Instrument}; use crate::server::{ actors::repo::webhook::WebhookAuth, @@ -16,6 +16,7 @@ use crate::server::{ use self::webhook::WebhookId; pub struct RepoActor { + span: tracing::Span, details: RepoDetails, webhook: Webhook, webhook_id: Option, // INFO: if [None] then no webhook is configured @@ -28,6 +29,7 @@ pub struct RepoActor { } impl RepoActor { pub(crate) fn new(details: RepoDetails, webhook: Webhook, net: Network) -> Self { + let span = tracing::info_span!("RepoActor", repo = %details); let forge = match details.forge.forge_type { #[cfg(feature = "forgejo")] crate::server::config::ForgeType::ForgeJo => { @@ -36,7 +38,9 @@ impl RepoActor { #[cfg(test)] crate::server::config::ForgeType::MockForge => gitforge::Forge::new_mock(), }; + debug!(?forge, "new"); Self { + span, details, webhook, webhook_id: None, @@ -52,11 +56,14 @@ impl RepoActor { impl Actor for RepoActor { type Context = Context; fn stopping(&mut self, ctx: &mut Self::Context) -> Running { + let _gaurd = self.span.enter(); + info!("Checking webhook"); match self.webhook_id.take() { Some(webhook_id) => { let repo_details = self.details.clone(); let net = self.net.clone(); webhook::unregister(webhook_id, repo_details, net) + .in_current_span() .into_actor(self) .wait(ctx); Running::Continue @@ -69,7 +76,7 @@ impl std::fmt::Display for RepoActor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "RepoActor: {}/{}", + "{}/{}", self.details.forge.forge_name, self.details.repo_alias ) } @@ -80,9 +87,9 @@ impl std::fmt::Display for RepoActor { pub struct CloneRepo; impl Handler for RepoActor { type Result = (); - #[tracing::instrument(skip_all, fields(%self))] + #[tracing::instrument(name = "RepoActor::CloneRepo", skip_all, fields(repo = %self, gitdir = %self.details.gitdir))] fn handle(&mut self, _msg: CloneRepo, ctx: &mut Self::Context) -> Self::Result { - info!(%self.details, "Clone/Update Repo"); + info!("Message Received"); let gitdir = self.details.gitdir.clone(); match self.forge.repo_clone(gitdir) { Ok(_) => ctx.address().do_send(LoadConfigFromRepo), @@ -96,13 +103,14 @@ impl Handler for RepoActor { pub struct LoadConfigFromRepo; impl Handler for RepoActor { type Result = (); - #[tracing::instrument(skip_all, fields(%self))] + #[tracing::instrument(name = "RepoActor::LoadConfigFromRepo", skip_all, fields(repo = %self))] fn handle(&mut self, _msg: LoadConfigFromRepo, ctx: &mut Self::Context) -> Self::Result { - info!(%self.details, "Loading .git-next.toml from repo"); + info!("Message Received"); let details = self.details.clone(); let addr = ctx.address(); let forge = self.forge.clone(); config::load(details, addr, forge) + .in_current_span() .into_actor(self) .wait(ctx); } @@ -113,10 +121,10 @@ impl Handler for RepoActor { struct LoadedConfig(pub RepoConfig); impl Handler for RepoActor { type Result = (); - #[tracing::instrument(skip_all, fields(%self))] + #[tracing::instrument(name = "RepoActor::LoadedConfig", skip_all, fields(repo = %self.details, branches = %msg.0))] fn handle(&mut self, msg: LoadedConfig, ctx: &mut Self::Context) -> Self::Result { + info!("Message Received"); let repo_config = msg.0; - info!(%self.details, %repo_config, "Config loaded"); self.details.repo_config.replace(repo_config); if self.webhook_id.is_none() { webhook::register( @@ -125,6 +133,7 @@ impl Handler for RepoActor { ctx.address(), self.net.clone(), ) + .in_current_span() .into_actor(self) .wait(ctx); } @@ -137,13 +146,14 @@ impl Handler for RepoActor { pub struct ValidateRepo; impl Handler for RepoActor { type Result = (); - #[tracing::instrument(skip_all, fields(%self))] + #[tracing::instrument(name = "RepoActor::ValidateRepo", skip_all, fields(repo = %self.details))] fn handle(&mut self, _msg: ValidateRepo, ctx: &mut Self::Context) -> Self::Result { - info!("ValidateRepo"); + info!("Message Received"); if let Some(repo_config) = self.details.repo_config.clone() { let forge = self.forge.clone(); let addr = ctx.address(); async move { forge.branches_validate_positions(repo_config, addr).await } + .in_current_span() .into_actor(self) .wait(ctx); } @@ -160,9 +170,9 @@ pub struct StartMonitoring { } impl Handler for RepoActor { type Result = (); - #[tracing::instrument(skip_all, fields(%self))] + #[tracing::instrument(name = "RepoActor::StartMonitoring", skip_all, fields(repo = %self.details, main = %msg.main, next= %msg.next, dev = %msg.dev))] fn handle(&mut self, msg: StartMonitoring, ctx: &mut Self::Context) -> Self::Result { - info!("StartMonitoring"); + info!("Message Received"); let Some(repo_config) = self.details.repo_config.clone() else { warn!("No config loaded"); return; @@ -170,7 +180,7 @@ impl Handler for RepoActor { let next_ahead_of_main = msg.main != msg.next; let dev_ahead_of_next = msg.next != msg.dev; - info!(%msg.main, %msg.next, %msg.dev, next_ahead_of_main, dev_ahead_of_next, "StartMonitoring"); + info!(next_ahead_of_main, dev_ahead_of_next, "StartMonitoring"); let repo_details = self.details.clone(); let webhook = self.webhook.clone(); @@ -180,14 +190,17 @@ impl Handler for RepoActor { if next_ahead_of_main { status::check_next(msg.next, addr, forge) + .in_current_span() .into_actor(self) .wait(ctx); } else if dev_ahead_of_next { branch::advance_next(msg.next, msg.dev_commit_history, repo_config, forge, addr) + .in_current_span() .into_actor(self) .wait(ctx); } else if self.webhook_id.is_none() { webhook::register(repo_details, webhook, addr, net) + .in_current_span() .into_actor(self) .wait(ctx); } @@ -199,8 +212,9 @@ impl Handler for RepoActor { pub struct WebhookRegistered(pub WebhookId, pub WebhookAuth); impl Handler for RepoActor { type Result = (); - #[tracing::instrument(skip_all, fields(%self))] + #[tracing::instrument(name = "RepoActor::WebhookRegistered", skip_all, fields(webhook_id = %msg.0))] fn handle(&mut self, msg: WebhookRegistered, _ctx: &mut Self::Context) -> Self::Result { + info!("Message Received"); self.webhook_id.replace(msg.0); self.webhook_auth.replace(msg.1); } @@ -211,8 +225,9 @@ impl Handler for RepoActor { pub struct AdvanceMainTo(pub gitforge::Commit); impl Handler for RepoActor { type Result = (); - #[tracing::instrument(skip_all, fields(%self))] + #[tracing::instrument(name = "RepoActor::AdvanceMainTo", skip_all, fields(commit = %msg.0))] fn handle(&mut self, msg: AdvanceMainTo, ctx: &mut Self::Context) -> Self::Result { + info!("Message Received"); let Some(repo_config) = self.details.repo_config.clone() else { warn!("No config loaded"); return; @@ -220,6 +235,7 @@ impl Handler for RepoActor { let forge = self.forge.clone(); let addr = ctx.address(); branch::advance_main(msg.0, repo_config, forge, addr) + .in_current_span() .into_actor(self) .wait(ctx); } diff --git a/src/server/actors/repo/webhook.rs b/src/server/actors/repo/webhook.rs index 0dae1ea..0dcc914 100644 --- a/src/server/actors/repo/webhook.rs +++ b/src/server/actors/repo/webhook.rs @@ -57,12 +57,12 @@ impl Deref for WebhookAuth { } } +#[tracing::instrument(skip_all, fields(%webhook_id))] pub async fn unregister( webhook_id: WebhookId, repo_details: crate::server::config::RepoDetails, net: network::Network, ) { - info!(?webhook_id, "unregister webhook"); let hostname = &repo_details.forge.hostname; let repo_path = repo_details.repo_path; use secrecy::ExposeSecret; @@ -81,11 +81,12 @@ pub async fn unregister( ); let result = net.delete(request).await; match result { - Ok(_) => info!(?webhook_id, "unregistered webhook"), - Err(err) => warn!(?webhook_id, ?err, "Failed to unregister webhook"), + Ok(_) => info!("unregistered webhook"), + Err(err) => warn!(?err, "Failed to unregister webhook"), } } +#[tracing::instrument(skip_all)] pub async fn register( repo_details: crate::server::config::RepoDetails, webhook: Webhook, @@ -103,7 +104,6 @@ pub async fn register( unregister(webhook_id, repo_details.clone(), net.clone()).await; } - info!("Registering webhook"); let hostname = &repo_details.forge.hostname; let repo_path = repo_details.repo_path; use secrecy::ExposeSecret; @@ -138,7 +138,7 @@ pub async fn register( match result { Ok(response) => { if let Some(hook) = response.response_body() { - info!("Webhook registered"); + info!(webhook_id = %hook.id, "Webhook registered"); addr.do_send(WebhookRegistered(hook.id(), authorisation)); } } diff --git a/src/server/actors/webhook/mod.rs b/src/server/actors/webhook/mod.rs index c8b2f8f..f90d271 100644 --- a/src/server/actors/webhook/mod.rs +++ b/src/server/actors/webhook/mod.rs @@ -9,15 +9,19 @@ use actix::prelude::*; pub use message::WebhookMessage; pub use router::AddWebhookRecipient; pub use router::WebhookRouter; +use tracing::Instrument; #[derive(Debug)] pub struct WebhookActor { + span: tracing::Span, spawn_handle: Option, message_receiver: Recipient, } impl WebhookActor { - pub const fn new(message_receiver: Recipient) -> Self { + pub fn new(message_receiver: Recipient) -> Self { + let span = tracing::info_span!("WebhookActor"); Self { + span, message_receiver, spawn_handle: None, } @@ -26,9 +30,10 @@ impl WebhookActor { impl Actor for WebhookActor { type Context = actix::Context; fn started(&mut self, ctx: &mut Self::Context) { + let _gaurd = self.span.enter(); let address: Recipient = self.message_receiver.clone(); let server = server::start(address); - let spawn_handle = ctx.spawn(server.into_actor(self)); + let spawn_handle = ctx.spawn(server.in_current_span().into_actor(self)); self.spawn_handle.replace(spawn_handle); } } diff --git a/src/server/actors/webhook/router.rs b/src/server/actors/webhook/router.rs index 987b90b..9fc169d 100644 --- a/src/server/actors/webhook/router.rs +++ b/src/server/actors/webhook/router.rs @@ -1,17 +1,21 @@ use std::collections::HashMap; use actix::prelude::*; -use tracing::debug; +use tracing::{debug, info}; use crate::server::{actors::webhook::message::WebhookMessage, config::RepoAlias}; -#[derive(Default)] pub struct WebhookRouter { + span: tracing::Span, repos: HashMap>, } impl WebhookRouter { pub fn new() -> Self { - Self::default() + let span = tracing::info_span!("WebhookRouter"); + Self { + span, + repos: Default::default(), + } } } impl Actor for WebhookRouter { @@ -22,10 +26,11 @@ impl Handler for WebhookRouter { type Result = (); fn handle(&mut self, msg: WebhookMessage, _ctx: &mut Self::Context) -> Self::Result { + let _gaurd = self.span.enter(); let repo_alias = RepoAlias(msg.path().clone()); - debug!(?repo_alias, "Router..."); + debug!(repo = %repo_alias, "Router..."); if let Some(recipient) = self.repos.get(&repo_alias) { - debug!("Sending to recipient"); + info!("Sending to Recipient"); recipient.do_send(msg); } } @@ -38,6 +43,8 @@ impl Handler for WebhookRouter { type Result = (); fn handle(&mut self, msg: AddWebhookRecipient, _ctx: &mut Self::Context) -> Self::Result { + let _gaurd = self.span.enter(); + info!(repo = %msg.0, "Register Recipient"); self.repos.insert(msg.0, msg.1); } } diff --git a/src/server/config/load.rs b/src/server/config/load.rs index 4a7a831..e201bb3 100644 --- a/src/server/config/load.rs +++ b/src/server/config/load.rs @@ -1,4 +1,3 @@ -use kxio::network; use terrors::OneOf; use tracing::error; @@ -7,17 +6,6 @@ use crate::server::{ gitforge::{self, ForgeFileError}, }; -#[derive(Debug)] -pub struct RepoConfigFileNotFound; -#[derive(Debug)] -pub struct RepoConfigIsNotFile; -#[derive(Debug)] -pub struct RepoConfigDecodeError; -#[derive(Debug)] -pub struct RepoConfigParseError; -#[derive(Debug)] -pub struct RepoConfigUnknownError(pub network::StatusCode); - pub async fn load( details: &RepoDetails, forge: &gitforge::Forge, diff --git a/src/server/config/mod.rs b/src/server/config/mod.rs index 17fff8f..f7503ef 100644 --- a/src/server/config/mod.rs +++ b/src/server/config/mod.rs @@ -30,8 +30,11 @@ pub struct ServerConfig { forge: HashMap, } impl ServerConfig { + #[tracing::instrument(skip_all)] pub(crate) fn load(fs: &FileSystem) -> Result { - let str = fs.file_read_to_string(&fs.base().join("git-next-server.toml"))?; + let file = fs.base().join("git-next-server.toml"); + info!(?file, ""); + let str = fs.file_read_to_string(&file)?; toml::from_str(&str).map_err(Into::into) } @@ -104,7 +107,7 @@ impl RepoConfig { } impl Display for RepoConfig { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self.branches) + write!(f, "{}", self.branches) } } @@ -138,7 +141,7 @@ impl RepoBranches { } impl Display for RepoBranches { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) + write!(f, "{},{},{}", self.main, self.next, self.dev) } } @@ -180,7 +183,13 @@ impl ForgeConfig { } impl Display for ForgeConfig { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{} - {}@{}", self.forge_type, self.user, self.hostname) + write!( + f, + "{}:{}@{}", + self.forge_type.to_string().to_lowercase(), + self.user, + self.hostname + ) } } @@ -231,7 +240,7 @@ impl AsRef for ServerRepoConfig { } impl Display for ServerRepoConfig { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{} - {}", self.repo, self.branch) + write!(f, "{}@{}", self.repo, self.branch) } } @@ -402,12 +411,12 @@ impl Display for RepoDetails { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, - "{}/{} ({}): {}:{}/{} @ {}", + "{}:{}/{}:{}@{}/{}@{}", + self.forge.forge_type, self.forge.forge_name, self.repo_alias, - self.forge.forge_type, - self.forge.hostname, self.forge.user, + self.forge.hostname, self.repo_path, self.branch, ) @@ -468,7 +477,7 @@ pub enum ForgeType { } impl Display for ForgeType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) + write!(f, "{}", format!("{:?}", self).to_lowercase()) } } @@ -485,12 +494,13 @@ impl GitDir { &self.0 } + #[tracing::instrument(skip_all)] pub fn validate(&self, repo_details: &RepoDetails) -> ValidationResult<()> { let git_remote = repo_details.git_remote(); use gix::remote::Direction; let push_remote = self.find_default_remote(Direction::Push)?; let fetch_remote = self.find_default_remote(Direction::Fetch)?; - info!(gitdir = %self, ?git_remote, ?push_remote, ?fetch_remote, "Gitdir::validate"); + info!(config = %git_remote, push = %push_remote, fetch = %fetch_remote, "Check remotes match"); if git_remote != push_remote { return Err(RepoValidationError::MismatchDefaultPushRemote { found: push_remote, @@ -506,7 +516,7 @@ impl GitDir { Ok(()) } - #[tracing::instrument] + #[tracing::instrument(skip_all, fields(?direction))] fn find_default_remote( &self, direction: gix::remote::Direction, @@ -514,7 +524,6 @@ impl GitDir { let repository = gix::ThreadSafeRepository::open(self.deref()) .map_err(|e| RepoValidationError::UnableToOpenRepo(e.to_string()))? .to_thread_local(); - info!(?repository, from = ?self.deref(), "gix::discover"); let Some(Ok(remote)) = repository.find_default_remote(gix::remote::Direction::Push) else { return Err(RepoValidationError::NoDefaultPushRemote); }; @@ -548,13 +557,11 @@ impl std::fmt::Display for GitDir { } impl From<&str> for GitDir { fn from(value: &str) -> Self { - info!("GitDir::from::<&str>({value:?})"); Self(value.into()) } } impl From for GitDir { fn from(value: PathBuf) -> Self { - info!("GitDir::from::({value:?})"); Self(value) } } diff --git a/src/server/gitforge/forgejo/branch/get_all.rs b/src/server/gitforge/forgejo/branch/get_all.rs index d11a296..1a9cae1 100644 --- a/src/server/gitforge/forgejo/branch/get_all.rs +++ b/src/server/gitforge/forgejo/branch/get_all.rs @@ -1,5 +1,5 @@ use kxio::network::{self, Network}; -use tracing::{error, info}; +use tracing::error; use crate::server::{ config::{BranchName, RepoDetails}, @@ -18,7 +18,7 @@ pub async fn get_all( "https://{hostname}/api/v1/repos/{repo_path}/branches?token={token}" )); - info!(%url, "Listing branches"); + // info!(%url, "Listing branches"); let request = network::NetRequest::new( network::RequestMethod::Get, url, diff --git a/src/server/gitforge/forgejo/branch/mod.rs b/src/server/gitforge/forgejo/branch/mod.rs index cc3b6e4..6b9051f 100644 --- a/src/server/gitforge/forgejo/branch/mod.rs +++ b/src/server/gitforge/forgejo/branch/mod.rs @@ -7,3 +7,4 @@ pub use fetch::fetch; pub use get_all::get_all; pub use reset::reset; pub use validate_positions::validate_positions; +pub use validate_positions::ValidatedPositions; diff --git a/src/server/gitforge/forgejo/branch/validate_positions.rs b/src/server/gitforge/forgejo/branch/validate_positions.rs index b13c1e4..031063f 100644 --- a/src/server/gitforge/forgejo/branch/validate_positions.rs +++ b/src/server/gitforge/forgejo/branch/validate_positions.rs @@ -1,20 +1,37 @@ -use actix::prelude::*; - use kxio::network; use tracing::{debug, error, info, warn}; use crate::server::{ self, - actors::repo::{RepoActor, StartMonitoring}, config::{BranchName, RepoConfig, RepoDetails}, gitforge::{self, ForgeLike}, }; +#[derive(Debug, derive_more::Display)] +pub enum Error { + Network(Box), + #[display("Failed to Reset Branch {branch} to {commit}")] + FailedToResetBranch { + branch: BranchName, + commit: gitforge::Commit, + }, + BranchReset(BranchName), + BranchHasNoCommits(BranchName), + DevBranchNotBasedOn(BranchName), +} +impl std::error::Error for Error {} + +pub struct ValidatedPositions { + pub main: gitforge::Commit, + pub next: gitforge::Commit, + pub dev: gitforge::Commit, + pub dev_commit_history: Vec, +} + pub async fn validate_positions( forge: &gitforge::forgejo::ForgeJoEnv, repo_config: RepoConfig, - addr: Addr, -) { +) -> Result { let repo_details = &forge.repo_details; // Collect Commit Histories for `main`, `next` and `dev` branches let commit_histories = get_commit_histories(repo_details, &repo_config, &forge.net).await; @@ -22,7 +39,7 @@ pub async fn validate_positions( Ok(commit_histories) => commit_histories, Err(err) => { error!(?err, "Failed to get commit histories"); - return; + return Err(Error::Network(Box::new(err))); } }; @@ -32,7 +49,7 @@ pub async fn validate_positions( "No commits on main branch '{}'", repo_config.branches().main() ); - return; + return Err(Error::BranchHasNoCommits(repo_config.branches().main())); }; // verify that next is an ancestor of dev, and force update to it main if it isn't let Some(next) = commit_histories.next.first().cloned() else { @@ -40,7 +57,7 @@ pub async fn validate_positions( "No commits on next branch '{}", repo_config.branches().next() ); - return; + return Err(Error::BranchHasNoCommits(repo_config.branches().next())); }; let next_is_ancestor_of_dev = commit_histories.dev.iter().any(|dev| dev == &next); if !next_is_ancestor_of_dev { @@ -48,11 +65,15 @@ pub async fn validate_positions( if let Err(err) = forge.branch_reset( repo_config.branches().next(), main.into(), - gitforge::Force::From(next.into()), + gitforge::Force::From(next.clone().into()), ) { warn!(?err, "Failed to reset next to main"); + return Err(Error::FailedToResetBranch { + branch: repo_config.branches().next(), + commit: next, + }); } - return; + return Err(Error::BranchReset(repo_config.branches().next())); } let next_commits = commit_histories @@ -69,18 +90,22 @@ pub async fn validate_positions( if let Err(err) = forge.branch_reset( repo_config.branches().next(), main.into(), - gitforge::Force::From(next.into()), + gitforge::Force::From(next.clone().into()), ) { warn!(?err, "Failed to reset next to main"); + return Err(Error::FailedToResetBranch { + branch: repo_config.branches().next(), + commit: next, + }); } - return; + return Err(Error::BranchReset(repo_config.branches().next())); } let Some(next) = next_commits.first().cloned() else { warn!( "No commits on next branch '{}'", repo_config.branches().next() ); - return; + return Err(Error::BranchHasNoCommits(repo_config.branches().next())); }; let dev_has_next = commit_histories .dev @@ -92,7 +117,7 @@ pub async fn validate_positions( repo_config.branches().dev(), repo_config.branches().next() ); - return; // dev is not based on next + return Err(Error::DevBranchNotBasedOn(repo_config.branches().next())); // dev is not based on next } let dev_has_main = commit_histories.dev.iter().any(|commit| commit == &main); if !dev_has_main { @@ -102,21 +127,23 @@ pub async fn validate_positions( repo_config.branches().main(), repo_config.branches().main(), ); - return; + return Err(Error::DevBranchNotBasedOn(repo_config.branches().main())); } let Some(dev) = commit_histories.dev.first().cloned() else { warn!( "No commits on dev branch '{}'", repo_config.branches().dev() ); - return; + return Err(Error::BranchHasNoCommits(repo_config.branches().dev())); }; - addr.do_send(StartMonitoring { + info!("Validation - OK"); + + Ok(ValidatedPositions { main, next, dev, dev_commit_history: commit_histories.dev, - }); + }) } async fn get_commit_histories( diff --git a/src/server/gitforge/forgejo/file/mod.rs b/src/server/gitforge/forgejo/file/mod.rs index 3d5b9c1..7d27b6d 100644 --- a/src/server/gitforge/forgejo/file/mod.rs +++ b/src/server/gitforge/forgejo/file/mod.rs @@ -1,5 +1,5 @@ use kxio::network::{self, Network}; -use tracing::{error, info, warn}; +use tracing::{error, warn}; use crate::server::{ config::{BranchName, RepoDetails}, @@ -21,7 +21,7 @@ pub(super) async fn contents_get( "https://{hostname}/api/v1/repos/{repo_path}/contents/{file_path}?ref={branch}&token={token}" )); - info!(%url, "Loading config"); + // info!("Loading config"); let request = network::NetRequest::new( network::RequestMethod::Get, url, diff --git a/src/server/gitforge/forgejo/mod.rs b/src/server/gitforge/forgejo/mod.rs index 39b810e..078eec6 100644 --- a/src/server/gitforge/forgejo/mod.rs +++ b/src/server/gitforge/forgejo/mod.rs @@ -2,20 +2,22 @@ pub mod branch; mod file; mod repo; +use std::time::Duration; + use actix::prelude::*; use kxio::network::{self, Network}; use tracing::{error, info, warn}; use crate::server::{ - actors::repo::RepoActor, + actors::repo::{RepoActor, StartMonitoring, ValidateRepo}, config::{BranchName, GitDir, RepoConfig, RepoDetails}, - gitforge::{self, RepoCloneError}, + gitforge::{self, forgejo::branch::ValidatedPositions, RepoCloneError}, types::GitRef, }; struct ForgeJo; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ForgeJoEnv { repo_details: RepoDetails, net: Network, @@ -44,7 +46,26 @@ impl super::ForgeLike for ForgeJoEnv { } async fn branches_validate_positions(&self, repo_config: RepoConfig, addr: Addr) { - branch::validate_positions(self, repo_config, addr).await + match branch::validate_positions(self, repo_config).await { + Ok(ValidatedPositions { + main, + next, + dev, + dev_commit_history, + }) => { + addr.do_send(StartMonitoring { + main, + next, + dev, + dev_commit_history, + }); + } + Err(err) => { + warn!("{}", err); + tokio::time::sleep(Duration::from_secs(10)).await; + addr.do_send(ValidateRepo); + } + } } fn branch_reset( @@ -102,16 +123,15 @@ impl super::ForgeLike for ForgeJoEnv { } fn repo_clone(&self, gitdir: GitDir) -> Result<(), RepoCloneError> { - if gitdir.exists() { - info!(%gitdir, "Gitdir already exists - validating..."); - gitdir - .validate(&self.repo_details) - .map_err(|e| RepoCloneError::Validation(e.to_string())) - .inspect(|_| info!(%gitdir, "Validation - OK")) - } else { - info!(%gitdir, "Gitdir doesn't exists - cloning..."); - repo::clone(&self.repo_details, gitdir).inspect(|_| info!("Cloned - OK")) + if !gitdir.exists() { + info!("Local copy not found - cloning..."); + repo::clone(&self.repo_details, gitdir.clone())?; } + info!("Validating..."); + gitdir + .validate(&self.repo_details) + .map_err(|e| RepoCloneError::Validation(e.to_string())) + .inspect(|_| info!("Validation - OK")) } } diff --git a/src/server/gitforge/forgejo/repo/clone.rs b/src/server/gitforge/forgejo/repo/clone.rs index c4b99ec..6309e50 100644 --- a/src/server/gitforge/forgejo/repo/clone.rs +++ b/src/server/gitforge/forgejo/repo/clone.rs @@ -7,14 +7,14 @@ use crate::server::{ gitforge::RepoCloneError, }; +#[tracing::instrument(skip_all)] pub fn clone(repo_details: &RepoDetails, gitdir: GitDir) -> Result<(), RepoCloneError> { use secrecy::ExposeSecret; let origin = repo_details.origin(); - info!("Cloning"); - let (repository, _outcome) = + let (_repository, _outcome) = gix::prepare_clone_bare(origin.expose_secret().as_str(), gitdir.deref())? .fetch_only(gix::progress::Discard, &AtomicBool::new(false))?; - info!(?repository, "Cloned"); + info!("Cloned - OK"); Ok(()) // TODO: (#69) return Repository inside a newtype to store in the RepoActor for reuse else where } diff --git a/src/server/gitforge/mock_forge.rs b/src/server/gitforge/mock_forge.rs index 37579cc..09f406d 100644 --- a/src/server/gitforge/mock_forge.rs +++ b/src/server/gitforge/mock_forge.rs @@ -6,7 +6,7 @@ use crate::server::{ }; struct MockForge; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct MockForgeEnv; impl MockForgeEnv { pub(crate) const fn new() -> Self { diff --git a/src/server/gitforge/mod.rs b/src/server/gitforge/mod.rs index 973fe9c..e69e8cb 100644 --- a/src/server/gitforge/mod.rs +++ b/src/server/gitforge/mod.rs @@ -58,7 +58,7 @@ pub trait ForgeLike { fn repo_clone(&self, gitdir: GitDir) -> Result<(), RepoCloneError>; } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum Forge { Mock(mock_forge::MockForgeEnv), #[allow(clippy::enum_variant_names)] diff --git a/src/server/mod.rs b/src/server/mod.rs index ff6e0f3..45f338a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -69,24 +69,34 @@ pub async fn start(fs: FileSystem, net: Network) { return; } }; - // create data dir if missing + // Server Storage let dir = server_config.storage().path(); if !dir.exists() { + info!(?dir, "server storage doesn't exist - creating it"); if let Err(err) = fs.dir_create(dir) { - error!(?err, ?dir, "Failed to create server storage directory"); + error!(?err, ?dir, "Failed to create server storage"); return; } } + let Ok(canon) = dir.canonicalize() else { + error!(?dir, "Failed to confirm servier storage"); + return; + }; + info!(dir = ?canon, "server storage"); - let webhook_router = webhook::WebhookRouter::new().start(); - let webhook = server_config.webhook(); + // Forge directories in Server Storage let server_storage = server_config.storage(); - if let Err(err) = create_forge_data_directories(&server_config, &fs, &dir) { - error!(?err, "Failure creating forge data directories"); + error!(?err, "Failure creating forge storage"); return; } + // Webhook Server + info!("Starting Webhook Server..."); + let webhook_router = webhook::WebhookRouter::new().start(); + let webhook = server_config.webhook(); + + // Forge Actors for (forge_name, forge_config) in server_config.forges() { create_forge_repos( forge_config, @@ -100,7 +110,9 @@ pub async fn start(fs: FileSystem, net: Network) { .map(|(alias, addr)| webhook::AddWebhookRecipient(alias, addr.recipient())) .for_each(|msg| webhook_router.do_send(msg)); } + let webhook_server = webhook::WebhookActor::new(webhook_router.recipient()).start(); + info!("Server running - Press Ctrl-C to stop..."); let _ = actix_rt::signal::ctrl_c().await; info!("Ctrl-C received, shutting down..."); drop(webhook_server); @@ -119,6 +131,7 @@ fn create_forge_data_directories( return Err(Error::ForgeDirIsNotDirectory { path }); } } else { + info!(%forge_name, ?path, "creating storage"); fs.dir_create_all(&path)?; } } @@ -133,7 +146,8 @@ fn create_forge_repos( webhook: &Webhook, net: &Network, ) -> Vec<(ForgeName, RepoAlias, RepoActor)> { - let span = tracing::info_span!("Forge", %forge_name, %forge_config); + 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![]; @@ -147,9 +161,8 @@ fn create_forge_repos( for (repo_alias, server_repo_config) in forge_config.repos() { let forge_repo = creator((repo_alias, server_repo_config)); info!( - forge = %forge_repo.0, alias = %forge_repo.1, - "created forge repo" + "Created Repo" ); repos.push(forge_repo); } @@ -166,8 +179,9 @@ fn create_actor( let server_storage = server_storage.clone(); let webhook = webhook.clone(); let net = net.clone(); - move |(repo_name, server_repo_config)| { - let span = tracing::info_span!("Repo", %repo_name, %server_repo_config); + 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( @@ -176,7 +190,7 @@ fn create_actor( server_storage .path() .join(forge_name.to_string()) - .join(repo_name.to_string()), + .join(repo_alias.to_string()), ) }, |gitdir| gitdir, @@ -184,15 +198,15 @@ fn create_actor( // 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 = config::RepoDetails::new( - &repo_name, + &repo_alias, server_repo_config, &forge_name, &forge_config, gitdir, ); + info!("Starting Repo Actor"); let actor = actors::repo::RepoActor::new(repo_details, webhook.clone(), net.clone()); - info!("Created Repo"); - (forge_name.clone(), repo_name, actor) + (forge_name.clone(), repo_alias, actor) } } @@ -200,7 +214,7 @@ fn start_actor( actor: (ForgeName, RepoAlias, actors::repo::RepoActor), ) -> (RepoAlias, Addr) { let (forge_name, repo_alias, actor) = actor; - let span = tracing::info_span!("Forge/Repo", %forge_name, %repo_alias); + let span = tracing::info_span!("start_actor", forge = %forge_name, repo = %repo_alias); let _guard = span.enter(); info!("Starting"); let addr = actor.start();