chore(logging): clean up and reformat logging

This commit is contained in:
Paul Campbell 2024-05-04 12:37:35 +01:00
parent 5ba5a126c3
commit 7516ec1dc1
18 changed files with 209 additions and 118 deletions

View file

@ -1,5 +1,4 @@
steps: steps:
todo_check: todo_check:
# INFO: https://woodpecker-ci.org/plugins/TODO-Checker # INFO: https://woodpecker-ci.org/plugins/TODO-Checker
image: codeberg.org/epsilon_02/todo-checker:1.1 image: codeberg.org/epsilon_02/todo-checker:1.1
@ -8,8 +7,8 @@ steps:
branch: next branch: next
settings: settings:
# git-next-woodpecker-todo-checker - read:issue # git-next-woodpecker-todo-checker - read:issue
repository_token: '776a3b928b852472c2af727a360c85c00af64b9f' repository_token: "776a3b928b852472c2af727a360c85c00af64b9f"
prefix_regex: "(#|//) (TODO|FIXME): " prefix_regex: "(#|//) (TODO|FIXME): "
debug: false debug: false
lint_and_build: lint_and_build:
@ -21,7 +20,7 @@ steps:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
commands: commands:
- cargo fmt --all -- --check - 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 - cargo build
test: test:

View file

@ -75,7 +75,7 @@ anyhow = "1.0"
cc-cli = { version = "0.1" } cc-cli = { version = "0.1" }
[lints.clippy] [lints.clippy]
nursery = "warn" nursery = { level = "warn", priority = -1 }
# pedantic = "warn" # pedantic = "warn"
unwrap_used = "warn" unwrap_used = "warn"
expect_used = "warn" expect_used = "warn"

View file

@ -1,18 +1,25 @@
use actix::prelude::*; 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}; use super::{LoadedConfig, RepoActor};
/// Loads the [RepoConfig] from the `.git-next.toml` file in the repository /// Loads the [RepoConfig] from the `.git-next.toml` file in the repository
pub async fn load(repo_details: RepoDetails, addr: Addr<RepoActor>, forge: gitforge::Forge) { #[tracing::instrument(skip_all, fields(branch = %repo_details.branch))]
let repo_config = match crate::server::config::load::load(&repo_details, &forge).await { pub async fn load(
repo_details: config::RepoDetails,
addr: Addr<RepoActor>,
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, Ok(repo_config) => repo_config,
Err(err) => { Err(err) => {
error!(?err, "Failed to load config"); error!(?err, "Failed to load config");
return; return;
} }
}; };
info!("Loaded .git-next.toml from repo");
addr.do_send(LoadedConfig(repo_config)); addr.do_send(LoadedConfig(repo_config));
} }

View file

@ -5,7 +5,7 @@ pub mod webhook;
use actix::prelude::*; use actix::prelude::*;
use kxio::network::Network; use kxio::network::Network;
use tracing::{info, warn}; use tracing::{debug, info, warn, Instrument};
use crate::server::{ use crate::server::{
actors::repo::webhook::WebhookAuth, actors::repo::webhook::WebhookAuth,
@ -16,6 +16,7 @@ use crate::server::{
use self::webhook::WebhookId; use self::webhook::WebhookId;
pub struct RepoActor { pub struct RepoActor {
span: tracing::Span,
details: RepoDetails, details: RepoDetails,
webhook: Webhook, webhook: Webhook,
webhook_id: Option<WebhookId>, // INFO: if [None] then no webhook is configured webhook_id: Option<WebhookId>, // INFO: if [None] then no webhook is configured
@ -28,6 +29,7 @@ pub struct RepoActor {
} }
impl RepoActor { impl RepoActor {
pub(crate) fn new(details: RepoDetails, webhook: Webhook, net: Network) -> Self { 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 { let forge = match details.forge.forge_type {
#[cfg(feature = "forgejo")] #[cfg(feature = "forgejo")]
crate::server::config::ForgeType::ForgeJo => { crate::server::config::ForgeType::ForgeJo => {
@ -36,7 +38,9 @@ impl RepoActor {
#[cfg(test)] #[cfg(test)]
crate::server::config::ForgeType::MockForge => gitforge::Forge::new_mock(), crate::server::config::ForgeType::MockForge => gitforge::Forge::new_mock(),
}; };
debug!(?forge, "new");
Self { Self {
span,
details, details,
webhook, webhook,
webhook_id: None, webhook_id: None,
@ -52,11 +56,14 @@ impl RepoActor {
impl Actor for RepoActor { impl Actor for RepoActor {
type Context = Context<Self>; type Context = Context<Self>;
fn stopping(&mut self, ctx: &mut Self::Context) -> Running { fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
let _gaurd = self.span.enter();
info!("Checking webhook");
match self.webhook_id.take() { match self.webhook_id.take() {
Some(webhook_id) => { Some(webhook_id) => {
let repo_details = self.details.clone(); let repo_details = self.details.clone();
let net = self.net.clone(); let net = self.net.clone();
webhook::unregister(webhook_id, repo_details, net) webhook::unregister(webhook_id, repo_details, net)
.in_current_span()
.into_actor(self) .into_actor(self)
.wait(ctx); .wait(ctx);
Running::Continue Running::Continue
@ -69,7 +76,7 @@ impl std::fmt::Display for RepoActor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
"RepoActor: {}/{}", "{}/{}",
self.details.forge.forge_name, self.details.repo_alias self.details.forge.forge_name, self.details.repo_alias
) )
} }
@ -80,9 +87,9 @@ impl std::fmt::Display for RepoActor {
pub struct CloneRepo; pub struct CloneRepo;
impl Handler<CloneRepo> for RepoActor { impl Handler<CloneRepo> for RepoActor {
type Result = (); 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 { 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(); let gitdir = self.details.gitdir.clone();
match self.forge.repo_clone(gitdir) { match self.forge.repo_clone(gitdir) {
Ok(_) => ctx.address().do_send(LoadConfigFromRepo), Ok(_) => ctx.address().do_send(LoadConfigFromRepo),
@ -96,13 +103,14 @@ impl Handler<CloneRepo> for RepoActor {
pub struct LoadConfigFromRepo; pub struct LoadConfigFromRepo;
impl Handler<LoadConfigFromRepo> for RepoActor { impl Handler<LoadConfigFromRepo> for RepoActor {
type Result = (); 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 { 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 details = self.details.clone();
let addr = ctx.address(); let addr = ctx.address();
let forge = self.forge.clone(); let forge = self.forge.clone();
config::load(details, addr, forge) config::load(details, addr, forge)
.in_current_span()
.into_actor(self) .into_actor(self)
.wait(ctx); .wait(ctx);
} }
@ -113,10 +121,10 @@ impl Handler<LoadConfigFromRepo> for RepoActor {
struct LoadedConfig(pub RepoConfig); struct LoadedConfig(pub RepoConfig);
impl Handler<LoadedConfig> for RepoActor { impl Handler<LoadedConfig> for RepoActor {
type Result = (); 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 { fn handle(&mut self, msg: LoadedConfig, ctx: &mut Self::Context) -> Self::Result {
info!("Message Received");
let repo_config = msg.0; let repo_config = msg.0;
info!(%self.details, %repo_config, "Config loaded");
self.details.repo_config.replace(repo_config); self.details.repo_config.replace(repo_config);
if self.webhook_id.is_none() { if self.webhook_id.is_none() {
webhook::register( webhook::register(
@ -125,6 +133,7 @@ impl Handler<LoadedConfig> for RepoActor {
ctx.address(), ctx.address(),
self.net.clone(), self.net.clone(),
) )
.in_current_span()
.into_actor(self) .into_actor(self)
.wait(ctx); .wait(ctx);
} }
@ -137,13 +146,14 @@ impl Handler<LoadedConfig> for RepoActor {
pub struct ValidateRepo; pub struct ValidateRepo;
impl Handler<ValidateRepo> for RepoActor { impl Handler<ValidateRepo> for RepoActor {
type Result = (); 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 { 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() { if let Some(repo_config) = self.details.repo_config.clone() {
let forge = self.forge.clone(); let forge = self.forge.clone();
let addr = ctx.address(); let addr = ctx.address();
async move { forge.branches_validate_positions(repo_config, addr).await } async move { forge.branches_validate_positions(repo_config, addr).await }
.in_current_span()
.into_actor(self) .into_actor(self)
.wait(ctx); .wait(ctx);
} }
@ -160,9 +170,9 @@ pub struct StartMonitoring {
} }
impl Handler<StartMonitoring> for RepoActor { impl Handler<StartMonitoring> for RepoActor {
type Result = (); 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 { 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 { let Some(repo_config) = self.details.repo_config.clone() else {
warn!("No config loaded"); warn!("No config loaded");
return; return;
@ -170,7 +180,7 @@ impl Handler<StartMonitoring> for RepoActor {
let next_ahead_of_main = msg.main != msg.next; let next_ahead_of_main = msg.main != msg.next;
let dev_ahead_of_next = msg.next != msg.dev; 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 repo_details = self.details.clone();
let webhook = self.webhook.clone(); let webhook = self.webhook.clone();
@ -180,14 +190,17 @@ impl Handler<StartMonitoring> for RepoActor {
if next_ahead_of_main { if next_ahead_of_main {
status::check_next(msg.next, addr, forge) status::check_next(msg.next, addr, forge)
.in_current_span()
.into_actor(self) .into_actor(self)
.wait(ctx); .wait(ctx);
} else if dev_ahead_of_next { } else if dev_ahead_of_next {
branch::advance_next(msg.next, msg.dev_commit_history, repo_config, forge, addr) branch::advance_next(msg.next, msg.dev_commit_history, repo_config, forge, addr)
.in_current_span()
.into_actor(self) .into_actor(self)
.wait(ctx); .wait(ctx);
} else if self.webhook_id.is_none() { } else if self.webhook_id.is_none() {
webhook::register(repo_details, webhook, addr, net) webhook::register(repo_details, webhook, addr, net)
.in_current_span()
.into_actor(self) .into_actor(self)
.wait(ctx); .wait(ctx);
} }
@ -199,8 +212,9 @@ impl Handler<StartMonitoring> for RepoActor {
pub struct WebhookRegistered(pub WebhookId, pub WebhookAuth); pub struct WebhookRegistered(pub WebhookId, pub WebhookAuth);
impl Handler<WebhookRegistered> for RepoActor { impl Handler<WebhookRegistered> for RepoActor {
type Result = (); 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 { fn handle(&mut self, msg: WebhookRegistered, _ctx: &mut Self::Context) -> Self::Result {
info!("Message Received");
self.webhook_id.replace(msg.0); self.webhook_id.replace(msg.0);
self.webhook_auth.replace(msg.1); self.webhook_auth.replace(msg.1);
} }
@ -211,8 +225,9 @@ impl Handler<WebhookRegistered> for RepoActor {
pub struct AdvanceMainTo(pub gitforge::Commit); pub struct AdvanceMainTo(pub gitforge::Commit);
impl Handler<AdvanceMainTo> for RepoActor { impl Handler<AdvanceMainTo> for RepoActor {
type Result = (); 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 { 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 { let Some(repo_config) = self.details.repo_config.clone() else {
warn!("No config loaded"); warn!("No config loaded");
return; return;
@ -220,6 +235,7 @@ impl Handler<AdvanceMainTo> for RepoActor {
let forge = self.forge.clone(); let forge = self.forge.clone();
let addr = ctx.address(); let addr = ctx.address();
branch::advance_main(msg.0, repo_config, forge, addr) branch::advance_main(msg.0, repo_config, forge, addr)
.in_current_span()
.into_actor(self) .into_actor(self)
.wait(ctx); .wait(ctx);
} }

View file

@ -57,12 +57,12 @@ impl Deref for WebhookAuth {
} }
} }
#[tracing::instrument(skip_all, fields(%webhook_id))]
pub async fn unregister( pub async fn unregister(
webhook_id: WebhookId, webhook_id: WebhookId,
repo_details: crate::server::config::RepoDetails, repo_details: crate::server::config::RepoDetails,
net: network::Network, net: network::Network,
) { ) {
info!(?webhook_id, "unregister webhook");
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;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
@ -81,11 +81,12 @@ pub async fn unregister(
); );
let result = net.delete(request).await; let result = net.delete(request).await;
match result { match result {
Ok(_) => info!(?webhook_id, "unregistered webhook"), Ok(_) => info!("unregistered webhook"),
Err(err) => warn!(?webhook_id, ?err, "Failed to unregister webhook"), Err(err) => warn!(?err, "Failed to unregister webhook"),
} }
} }
#[tracing::instrument(skip_all)]
pub async fn register( pub async fn register(
repo_details: crate::server::config::RepoDetails, repo_details: crate::server::config::RepoDetails,
webhook: Webhook, webhook: Webhook,
@ -103,7 +104,6 @@ pub async fn register(
unregister(webhook_id, repo_details.clone(), net.clone()).await; unregister(webhook_id, repo_details.clone(), net.clone()).await;
} }
info!("Registering webhook");
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;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
@ -138,7 +138,7 @@ pub async fn register(
match result { match result {
Ok(response) => { Ok(response) => {
if let Some(hook) = response.response_body() { if let Some(hook) = response.response_body() {
info!("Webhook registered"); info!(webhook_id = %hook.id, "Webhook registered");
addr.do_send(WebhookRegistered(hook.id(), authorisation)); addr.do_send(WebhookRegistered(hook.id(), authorisation));
} }
} }

View file

@ -9,15 +9,19 @@ use actix::prelude::*;
pub use message::WebhookMessage; pub use message::WebhookMessage;
pub use router::AddWebhookRecipient; pub use router::AddWebhookRecipient;
pub use router::WebhookRouter; pub use router::WebhookRouter;
use tracing::Instrument;
#[derive(Debug)] #[derive(Debug)]
pub struct WebhookActor { pub struct WebhookActor {
span: tracing::Span,
spawn_handle: Option<actix::SpawnHandle>, spawn_handle: Option<actix::SpawnHandle>,
message_receiver: Recipient<WebhookMessage>, message_receiver: Recipient<WebhookMessage>,
} }
impl WebhookActor { impl WebhookActor {
pub const fn new(message_receiver: Recipient<WebhookMessage>) -> Self { pub fn new(message_receiver: Recipient<WebhookMessage>) -> Self {
let span = tracing::info_span!("WebhookActor");
Self { Self {
span,
message_receiver, message_receiver,
spawn_handle: None, spawn_handle: None,
} }
@ -26,9 +30,10 @@ impl WebhookActor {
impl Actor for WebhookActor { impl Actor for WebhookActor {
type Context = actix::Context<Self>; type Context = actix::Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) { fn started(&mut self, ctx: &mut Self::Context) {
let _gaurd = self.span.enter();
let address: Recipient<WebhookMessage> = self.message_receiver.clone(); let address: Recipient<WebhookMessage> = self.message_receiver.clone();
let server = server::start(address); 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); self.spawn_handle.replace(spawn_handle);
} }
} }

View file

@ -1,17 +1,21 @@
use std::collections::HashMap; use std::collections::HashMap;
use actix::prelude::*; use actix::prelude::*;
use tracing::debug; use tracing::{debug, info};
use crate::server::{actors::webhook::message::WebhookMessage, config::RepoAlias}; use crate::server::{actors::webhook::message::WebhookMessage, config::RepoAlias};
#[derive(Default)]
pub struct WebhookRouter { pub struct WebhookRouter {
span: tracing::Span,
repos: HashMap<RepoAlias, Recipient<WebhookMessage>>, repos: HashMap<RepoAlias, Recipient<WebhookMessage>>,
} }
impl WebhookRouter { impl WebhookRouter {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() let span = tracing::info_span!("WebhookRouter");
Self {
span,
repos: Default::default(),
}
} }
} }
impl Actor for WebhookRouter { impl Actor for WebhookRouter {
@ -22,10 +26,11 @@ impl Handler<WebhookMessage> for WebhookRouter {
type Result = (); type Result = ();
fn handle(&mut self, msg: WebhookMessage, _ctx: &mut Self::Context) -> Self::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()); 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) { if let Some(recipient) = self.repos.get(&repo_alias) {
debug!("Sending to recipient"); info!("Sending to Recipient");
recipient.do_send(msg); recipient.do_send(msg);
} }
} }
@ -38,6 +43,8 @@ impl Handler<AddWebhookRecipient> for WebhookRouter {
type Result = (); type Result = ();
fn handle(&mut self, msg: AddWebhookRecipient, _ctx: &mut Self::Context) -> Self::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); self.repos.insert(msg.0, msg.1);
} }
} }

View file

@ -1,4 +1,3 @@
use kxio::network;
use terrors::OneOf; use terrors::OneOf;
use tracing::error; use tracing::error;
@ -7,17 +6,6 @@ use crate::server::{
gitforge::{self, ForgeFileError}, 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( pub async fn load(
details: &RepoDetails, details: &RepoDetails,
forge: &gitforge::Forge, forge: &gitforge::Forge,

View file

@ -30,8 +30,11 @@ pub struct ServerConfig {
forge: HashMap<String, ForgeConfig>, forge: HashMap<String, ForgeConfig>,
} }
impl ServerConfig { impl ServerConfig {
#[tracing::instrument(skip_all)]
pub(crate) fn load(fs: &FileSystem) -> Result<Self> { pub(crate) fn load(fs: &FileSystem) -> Result<Self> {
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) toml::from_str(&str).map_err(Into::into)
} }
@ -104,7 +107,7 @@ impl RepoConfig {
} }
impl Display for RepoConfig { impl Display for RepoConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 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 { impl Display for RepoBranches {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 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 { impl Display for ForgeConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 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<Self> for ServerRepoConfig {
} }
impl Display for ServerRepoConfig { impl Display for ServerRepoConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 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 { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
"{}/{} ({}): {}:{}/{} @ {}", "{}:{}/{}:{}@{}/{}@{}",
self.forge.forge_type,
self.forge.forge_name, self.forge.forge_name,
self.repo_alias, self.repo_alias,
self.forge.forge_type,
self.forge.hostname,
self.forge.user, self.forge.user,
self.forge.hostname,
self.repo_path, self.repo_path,
self.branch, self.branch,
) )
@ -468,7 +477,7 @@ pub enum ForgeType {
} }
impl Display for ForgeType { impl Display for ForgeType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 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 &self.0
} }
#[tracing::instrument(skip_all)]
pub fn validate(&self, repo_details: &RepoDetails) -> ValidationResult<()> { pub fn validate(&self, repo_details: &RepoDetails) -> ValidationResult<()> {
let git_remote = repo_details.git_remote(); let git_remote = repo_details.git_remote();
use gix::remote::Direction; use gix::remote::Direction;
let push_remote = self.find_default_remote(Direction::Push)?; let push_remote = self.find_default_remote(Direction::Push)?;
let fetch_remote = self.find_default_remote(Direction::Fetch)?; 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 { if git_remote != push_remote {
return Err(RepoValidationError::MismatchDefaultPushRemote { return Err(RepoValidationError::MismatchDefaultPushRemote {
found: push_remote, found: push_remote,
@ -506,7 +516,7 @@ impl GitDir {
Ok(()) Ok(())
} }
#[tracing::instrument] #[tracing::instrument(skip_all, fields(?direction))]
fn find_default_remote( fn find_default_remote(
&self, &self,
direction: gix::remote::Direction, direction: gix::remote::Direction,
@ -514,7 +524,6 @@ impl GitDir {
let repository = gix::ThreadSafeRepository::open(self.deref()) let repository = gix::ThreadSafeRepository::open(self.deref())
.map_err(|e| RepoValidationError::UnableToOpenRepo(e.to_string()))? .map_err(|e| RepoValidationError::UnableToOpenRepo(e.to_string()))?
.to_thread_local(); .to_thread_local();
info!(?repository, from = ?self.deref(), "gix::discover");
let Some(Ok(remote)) = repository.find_default_remote(gix::remote::Direction::Push) else { let Some(Ok(remote)) = repository.find_default_remote(gix::remote::Direction::Push) else {
return Err(RepoValidationError::NoDefaultPushRemote); return Err(RepoValidationError::NoDefaultPushRemote);
}; };
@ -548,13 +557,11 @@ impl std::fmt::Display for GitDir {
} }
impl From<&str> for GitDir { impl From<&str> for GitDir {
fn from(value: &str) -> Self { fn from(value: &str) -> Self {
info!("GitDir::from::<&str>({value:?})");
Self(value.into()) Self(value.into())
} }
} }
impl From<PathBuf> for GitDir { impl From<PathBuf> for GitDir {
fn from(value: PathBuf) -> Self { fn from(value: PathBuf) -> Self {
info!("GitDir::from::<PathBuf>({value:?})");
Self(value) Self(value)
} }
} }

View file

@ -1,5 +1,5 @@
use kxio::network::{self, Network}; use kxio::network::{self, Network};
use tracing::{error, info}; use tracing::error;
use crate::server::{ use crate::server::{
config::{BranchName, RepoDetails}, config::{BranchName, RepoDetails},
@ -18,7 +18,7 @@ pub async fn get_all(
"https://{hostname}/api/v1/repos/{repo_path}/branches?token={token}" "https://{hostname}/api/v1/repos/{repo_path}/branches?token={token}"
)); ));
info!(%url, "Listing branches"); // info!(%url, "Listing branches");
let request = network::NetRequest::new( let request = network::NetRequest::new(
network::RequestMethod::Get, network::RequestMethod::Get,
url, url,

View file

@ -7,3 +7,4 @@ pub use fetch::fetch;
pub use get_all::get_all; pub use get_all::get_all;
pub use reset::reset; pub use reset::reset;
pub use validate_positions::validate_positions; pub use validate_positions::validate_positions;
pub use validate_positions::ValidatedPositions;

View file

@ -1,20 +1,37 @@
use actix::prelude::*;
use kxio::network; use kxio::network;
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use crate::server::{ use crate::server::{
self, self,
actors::repo::{RepoActor, StartMonitoring},
config::{BranchName, RepoConfig, RepoDetails}, config::{BranchName, RepoConfig, RepoDetails},
gitforge::{self, ForgeLike}, gitforge::{self, ForgeLike},
}; };
#[derive(Debug, derive_more::Display)]
pub enum Error {
Network(Box<kxio::network::NetworkError>),
#[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<gitforge::Commit>,
}
pub async fn validate_positions( pub async fn validate_positions(
forge: &gitforge::forgejo::ForgeJoEnv, forge: &gitforge::forgejo::ForgeJoEnv,
repo_config: RepoConfig, repo_config: RepoConfig,
addr: Addr<RepoActor>, ) -> Result<ValidatedPositions, Error> {
) {
let repo_details = &forge.repo_details; let repo_details = &forge.repo_details;
// Collect Commit Histories for `main`, `next` and `dev` branches // Collect Commit Histories for `main`, `next` and `dev` branches
let commit_histories = get_commit_histories(repo_details, &repo_config, &forge.net).await; 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, Ok(commit_histories) => commit_histories,
Err(err) => { Err(err) => {
error!(?err, "Failed to get commit histories"); 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 '{}'", "No commits on main branch '{}'",
repo_config.branches().main() 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 // 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 { let Some(next) = commit_histories.next.first().cloned() else {
@ -40,7 +57,7 @@ pub async fn validate_positions(
"No commits on next branch '{}", "No commits on next branch '{}",
repo_config.branches().next() 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); let next_is_ancestor_of_dev = commit_histories.dev.iter().any(|dev| dev == &next);
if !next_is_ancestor_of_dev { if !next_is_ancestor_of_dev {
@ -48,11 +65,15 @@ pub async fn validate_positions(
if let Err(err) = forge.branch_reset( if let Err(err) = forge.branch_reset(
repo_config.branches().next(), repo_config.branches().next(),
main.into(), main.into(),
gitforge::Force::From(next.into()), gitforge::Force::From(next.clone().into()),
) { ) {
warn!(?err, "Failed to reset next to main"); 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 let next_commits = commit_histories
@ -69,18 +90,22 @@ pub async fn validate_positions(
if let Err(err) = forge.branch_reset( if let Err(err) = forge.branch_reset(
repo_config.branches().next(), repo_config.branches().next(),
main.into(), main.into(),
gitforge::Force::From(next.into()), gitforge::Force::From(next.clone().into()),
) { ) {
warn!(?err, "Failed to reset next to main"); 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 { let Some(next) = next_commits.first().cloned() else {
warn!( warn!(
"No commits on next branch '{}'", "No commits on next branch '{}'",
repo_config.branches().next() repo_config.branches().next()
); );
return; return Err(Error::BranchHasNoCommits(repo_config.branches().next()));
}; };
let dev_has_next = commit_histories let dev_has_next = commit_histories
.dev .dev
@ -92,7 +117,7 @@ pub async fn validate_positions(
repo_config.branches().dev(), repo_config.branches().dev(),
repo_config.branches().next() 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); let dev_has_main = commit_histories.dev.iter().any(|commit| commit == &main);
if !dev_has_main { if !dev_has_main {
@ -102,21 +127,23 @@ pub async fn validate_positions(
repo_config.branches().main(), repo_config.branches().main(),
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 { let Some(dev) = commit_histories.dev.first().cloned() else {
warn!( warn!(
"No commits on dev branch '{}'", "No commits on dev branch '{}'",
repo_config.branches().dev() repo_config.branches().dev()
); );
return; return Err(Error::BranchHasNoCommits(repo_config.branches().dev()));
}; };
addr.do_send(StartMonitoring { info!("Validation - OK");
Ok(ValidatedPositions {
main, main,
next, next,
dev, dev,
dev_commit_history: commit_histories.dev, dev_commit_history: commit_histories.dev,
}); })
} }
async fn get_commit_histories( async fn get_commit_histories(

View file

@ -1,5 +1,5 @@
use kxio::network::{self, Network}; use kxio::network::{self, Network};
use tracing::{error, info, warn}; use tracing::{error, warn};
use crate::server::{ use crate::server::{
config::{BranchName, RepoDetails}, 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}" "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( let request = network::NetRequest::new(
network::RequestMethod::Get, network::RequestMethod::Get,
url, url,

View file

@ -2,20 +2,22 @@ pub mod branch;
mod file; mod file;
mod repo; mod repo;
use std::time::Duration;
use actix::prelude::*; use actix::prelude::*;
use kxio::network::{self, Network}; use kxio::network::{self, Network};
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use crate::server::{ use crate::server::{
actors::repo::RepoActor, actors::repo::{RepoActor, StartMonitoring, ValidateRepo},
config::{BranchName, GitDir, RepoConfig, RepoDetails}, config::{BranchName, GitDir, RepoConfig, RepoDetails},
gitforge::{self, RepoCloneError}, gitforge::{self, forgejo::branch::ValidatedPositions, RepoCloneError},
types::GitRef, types::GitRef,
}; };
struct ForgeJo; struct ForgeJo;
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct ForgeJoEnv { pub struct ForgeJoEnv {
repo_details: RepoDetails, repo_details: RepoDetails,
net: Network, net: Network,
@ -44,7 +46,26 @@ impl super::ForgeLike for ForgeJoEnv {
} }
async fn branches_validate_positions(&self, repo_config: RepoConfig, addr: Addr<RepoActor>) { async fn branches_validate_positions(&self, repo_config: RepoConfig, addr: Addr<RepoActor>) {
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( fn branch_reset(
@ -102,16 +123,15 @@ impl super::ForgeLike for ForgeJoEnv {
} }
fn repo_clone(&self, gitdir: GitDir) -> Result<(), RepoCloneError> { fn repo_clone(&self, gitdir: GitDir) -> Result<(), RepoCloneError> {
if gitdir.exists() { if !gitdir.exists() {
info!(%gitdir, "Gitdir already exists - validating..."); info!("Local copy not found - cloning...");
gitdir repo::clone(&self.repo_details, gitdir.clone())?;
.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"))
} }
info!("Validating...");
gitdir
.validate(&self.repo_details)
.map_err(|e| RepoCloneError::Validation(e.to_string()))
.inspect(|_| info!("Validation - OK"))
} }
} }

View file

@ -7,14 +7,14 @@ use crate::server::{
gitforge::RepoCloneError, gitforge::RepoCloneError,
}; };
#[tracing::instrument(skip_all)]
pub fn clone(repo_details: &RepoDetails, gitdir: GitDir) -> Result<(), RepoCloneError> { pub fn clone(repo_details: &RepoDetails, gitdir: GitDir) -> Result<(), RepoCloneError> {
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
let origin = repo_details.origin(); let origin = repo_details.origin();
info!("Cloning"); let (_repository, _outcome) =
let (repository, _outcome) =
gix::prepare_clone_bare(origin.expose_secret().as_str(), gitdir.deref())? gix::prepare_clone_bare(origin.expose_secret().as_str(), gitdir.deref())?
.fetch_only(gix::progress::Discard, &AtomicBool::new(false))?; .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 Ok(()) // TODO: (#69) return Repository inside a newtype to store in the RepoActor for reuse else where
} }

View file

@ -6,7 +6,7 @@ use crate::server::{
}; };
struct MockForge; struct MockForge;
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct MockForgeEnv; pub struct MockForgeEnv;
impl MockForgeEnv { impl MockForgeEnv {
pub(crate) const fn new() -> Self { pub(crate) const fn new() -> Self {

View file

@ -58,7 +58,7 @@ pub trait ForgeLike {
fn repo_clone(&self, gitdir: GitDir) -> Result<(), RepoCloneError>; fn repo_clone(&self, gitdir: GitDir) -> Result<(), RepoCloneError>;
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub enum Forge { pub enum Forge {
Mock(mock_forge::MockForgeEnv), Mock(mock_forge::MockForgeEnv),
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]

View file

@ -69,24 +69,34 @@ pub async fn start(fs: FileSystem, net: Network) {
return; return;
} }
}; };
// create data dir if missing // Server Storage
let dir = server_config.storage().path(); let dir = server_config.storage().path();
if !dir.exists() { if !dir.exists() {
info!(?dir, "server storage doesn't exist - creating it");
if let Err(err) = fs.dir_create(dir) { 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; 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(); // Forge directories in Server Storage
let webhook = server_config.webhook();
let server_storage = server_config.storage(); let server_storage = server_config.storage();
if let Err(err) = create_forge_data_directories(&server_config, &fs, &dir) { 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; 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() { for (forge_name, forge_config) in server_config.forges() {
create_forge_repos( create_forge_repos(
forge_config, forge_config,
@ -100,7 +110,9 @@ pub async fn start(fs: FileSystem, net: Network) {
.map(|(alias, addr)| webhook::AddWebhookRecipient(alias, addr.recipient())) .map(|(alias, addr)| webhook::AddWebhookRecipient(alias, addr.recipient()))
.for_each(|msg| webhook_router.do_send(msg)); .for_each(|msg| webhook_router.do_send(msg));
} }
let webhook_server = webhook::WebhookActor::new(webhook_router.recipient()).start(); 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; let _ = actix_rt::signal::ctrl_c().await;
info!("Ctrl-C received, shutting down..."); info!("Ctrl-C received, shutting down...");
drop(webhook_server); drop(webhook_server);
@ -119,6 +131,7 @@ fn create_forge_data_directories(
return Err(Error::ForgeDirIsNotDirectory { path }); return Err(Error::ForgeDirIsNotDirectory { path });
} }
} else { } else {
info!(%forge_name, ?path, "creating storage");
fs.dir_create_all(&path)?; fs.dir_create_all(&path)?;
} }
} }
@ -133,7 +146,8 @@ fn create_forge_repos(
webhook: &Webhook, webhook: &Webhook,
net: &Network, net: &Network,
) -> Vec<(ForgeName, RepoAlias, RepoActor)> { ) -> 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(); let _guard = span.enter();
info!("Creating Forge"); info!("Creating Forge");
let mut repos = vec![]; let mut repos = vec![];
@ -147,9 +161,8 @@ fn create_forge_repos(
for (repo_alias, server_repo_config) in forge_config.repos() { for (repo_alias, server_repo_config) in forge_config.repos() {
let forge_repo = creator((repo_alias, server_repo_config)); let forge_repo = creator((repo_alias, server_repo_config));
info!( info!(
forge = %forge_repo.0,
alias = %forge_repo.1, alias = %forge_repo.1,
"created forge repo" "Created Repo"
); );
repos.push(forge_repo); repos.push(forge_repo);
} }
@ -166,8 +179,9 @@ fn create_actor(
let server_storage = server_storage.clone(); let server_storage = server_storage.clone();
let webhook = webhook.clone(); let webhook = webhook.clone();
let net = net.clone(); let net = net.clone();
move |(repo_name, server_repo_config)| { move |(repo_alias, server_repo_config)| {
let span = tracing::info_span!("Repo", %repo_name, %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();
info!("Creating Repo"); info!("Creating Repo");
let gitdir = server_repo_config.gitdir().map_or_else( let gitdir = server_repo_config.gitdir().map_or_else(
@ -176,7 +190,7 @@ fn create_actor(
server_storage server_storage
.path() .path()
.join(forge_name.to_string()) .join(forge_name.to_string())
.join(repo_name.to_string()), .join(repo_alias.to_string()),
) )
}, },
|gitdir| gitdir, |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 // INFO: can't canonicalise gitdir as the path needs to exist to do that and we may not
// have cloned the repo yet // have cloned the repo yet
let repo_details = config::RepoDetails::new( let repo_details = config::RepoDetails::new(
&repo_name, &repo_alias,
server_repo_config, server_repo_config,
&forge_name, &forge_name,
&forge_config, &forge_config,
gitdir, gitdir,
); );
info!("Starting Repo Actor");
let actor = actors::repo::RepoActor::new(repo_details, webhook.clone(), net.clone()); let actor = actors::repo::RepoActor::new(repo_details, webhook.clone(), net.clone());
info!("Created Repo"); (forge_name.clone(), repo_alias, actor)
(forge_name.clone(), repo_name, actor)
} }
} }
@ -200,7 +214,7 @@ fn start_actor(
actor: (ForgeName, RepoAlias, actors::repo::RepoActor), actor: (ForgeName, RepoAlias, actors::repo::RepoActor),
) -> (RepoAlias, Addr<actors::repo::RepoActor>) { ) -> (RepoAlias, Addr<actors::repo::RepoActor>) {
let (forge_name, repo_alias, actor) = actor; 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(); let _guard = span.enter();
info!("Starting"); info!("Starting");
let addr = actor.start(); let addr = actor.start();