diff options
author | Lars Wirzenius <liw@liw.fi> | 2024-02-15 17:18:06 +0200 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2024-02-15 18:36:00 +0200 |
commit | d6be37d7eedc2a0e08d729b98253e5ff3d996c09 (patch) | |
tree | 07ae0abf9223935966dc701c1f8fdb0620c33ada | |
parent | 15ddf5d0e7e6badfe3c0deccd9e848e0d8642d3a (diff) | |
download | radicle-ci-broker-d6be37d7eedc2a0e08d729b98253e5ff3d996c09.tar.gz |
feat: update a status page, if configured
Signed-off-by: Lars Wirzenius <liw@liw.fi>
-rw-r--r-- | Cargo.lock | 65 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | build.rs | 23 | ||||
-rw-r--r-- | src/bin/ci-broker.rs | 12 | ||||
-rw-r--r-- | src/bin/status.rs | 19 | ||||
-rw-r--r-- | src/config.rs | 5 | ||||
-rw-r--r-- | src/error.rs | 5 | ||||
-rw-r--r-- | src/event.rs | 2 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/status.rs | 101 |
10 files changed, 233 insertions, 2 deletions
@@ -405,6 +405,15 @@ dependencies = [ ] [[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -850,6 +859,12 @@ dependencies = [ ] [[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1004,6 +1019,12 @@ dependencies = [ ] [[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1062,6 +1083,15 @@ dependencies = [ ] [[package]] +name = "qcheck" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b439bd4242da51d62d18c95e6a6add749346756b0d1a587dfd0cc22fa6b5f3f0" +dependencies = [ + "rand", +] + +[[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1087,6 +1117,7 @@ dependencies = [ "multibase", "nonempty 0.9.0", "once_cell", + "qcheck", "radicle-cob", "radicle-crypto", "radicle-git-ext", @@ -1114,6 +1145,7 @@ dependencies = [ "serde_yaml", "tempfile", "thiserror", + "time", "uuid", ] @@ -1143,7 +1175,9 @@ dependencies = [ "amplify", "cyphernet", "ec25519", + "fastrand", "multibase", + "qcheck", "radicle-git-ext", "radicle-ssh", "serde", @@ -1622,6 +1656,37 @@ dependencies = [ ] [[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -14,10 +14,12 @@ serde_yaml = "0.9.27" thiserror = "1.0.50" radicle-surf = { version = "0.18.0", default-features = false, features = ["serde"] } uuid = { version = "1.7.0", features = ["v4"] } +time = { version = "0.3.34", features = ["formatting", "macros"] } [dependencies.radicle] git = "https://seed.radicle.xyz/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git" branch = "master" +features = ["default", "test"] [dev-dependencies] tempfile = { version = "3.9.0" } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..ddda179 --- /dev/null +++ b/build.rs @@ -0,0 +1,23 @@ +use std::process::Command; + +fn main() { + // Set a build-time `GIT_HEAD` env var which includes the commit id; + // such that we can tell which code is running. + let hash = Command::new("git") + .arg("rev-parse") + .arg("--short") + .arg("HEAD") + .output() + .ok() + .and_then(|output| { + if output.status.success() { + String::from_utf8(output.stdout).ok() + } else { + None + } + }) + .unwrap_or_else(|| String::from("unknown")); + + println!("cargo:rustc-env=GIT_HEAD={hash}"); + println!("cargo:rustc-rerun-if-changed=.git/HEAD"); +} diff --git a/src/bin/ci-broker.rs b/src/bin/ci-broker.rs index f09cd14..efa6ce1 100644 --- a/src/bin/ci-broker.rs +++ b/src/bin/ci-broker.rs @@ -1,4 +1,7 @@ -use std::{error::Error, path::PathBuf}; +use std::{ + error::Error, + path::{Path, PathBuf}, +}; use log::{debug, info}; @@ -11,6 +14,7 @@ use radicle_ci_broker::{ event::{BrokerEvent, NodeEventSource}, msg::Request, run::Run, + status::StatusBuilder, }; fn main() { @@ -75,9 +79,14 @@ fn fallible_main() -> Result<(), BrokerError> { // This loop ends when there's an error, e.g., failure to read an // event from the node. let mut counter = 0; + let mut status = StatusBuilder::new(config.status_page().unwrap_or(Path::new("/dev/null"))); loop { + status.write()?; debug!("waiting for event from node"); for e in source.event()? { + status.broker_event(&e); + status.write()?; + counter += 1; debug!("broker event {e:#?}"); let BrokerEvent::RefChanged { @@ -91,6 +100,7 @@ fn fallible_main() -> Result<(), BrokerError> { let mut run = Run::default(); if let Some(adapter) = broker.adapter(&rid) { adapter.run(&req, &mut run)?; + status.ci_run(run.adapter_run_id().unwrap(), run.result().unwrap()); println!( "Run CI run #{}: {}, {} -> {}", counter, diff --git a/src/bin/status.rs b/src/bin/status.rs new file mode 100644 index 0000000..646291b --- /dev/null +++ b/src/bin/status.rs @@ -0,0 +1,19 @@ +use std::{error::Error, path::Path}; + +use radicle_ci_broker::status::*; + +fn main() { + if let Err(e) = fallible_main() { + eprintln!("ERROR: {e}"); + let mut e = e.source(); + while let Some(source) = e { + eprintln!("caused by: {}", source); + e = source.source(); + } + } +} + +fn fallible_main() -> Result<(), StatusError> { + StatusBuilder::new(Path::new("status.json")).write()?; + Ok(()) +} diff --git a/src/config.rs b/src/config.rs index 3a925ba..13afefb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,6 +14,7 @@ pub struct Config { pub default_adapter: String, pub adapters: HashMap<String, Adapter>, pub filters: Vec<EventFilter>, + pub status_page: Option<PathBuf>, } impl Config { @@ -26,6 +27,10 @@ impl Config { pub fn adapter(&self, name: &str) -> Option<&Adapter> { self.adapters.get(name) } + + pub fn status_page(&self) -> Option<&Path> { + self.status_page.as_deref() + } } #[derive(Debug, Serialize, Deserialize)] diff --git a/src/error.rs b/src/error.rs index c70bc1d..5682474 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,6 +12,7 @@ use crate::{ adapter::AdapterError, config::ConfigError, msg::{MessageError, Request}, + status::StatusError, }; /// All possible errors from the CI broker messages. @@ -61,4 +62,8 @@ pub enum BrokerError { /// Could not convert repository ID from string. #[error("failed to understand repository id {0:?}")] BadRepoId(String, #[source] radicle::identity::IdError), + + /// Status page error. + #[error(transparent)] + Status(#[from] StatusError), } diff --git a/src/event.rs b/src/event.rs index d9a28f5..7131a34 100644 --- a/src/event.rs +++ b/src/event.rs @@ -237,7 +237,7 @@ impl TryFrom<&str> for Filters { /// A single node event can represent many git refs having changed, /// but that's hard to process or filter. The broker breaks up such /// complex events to simpler ones that only affect one ref at a time. -#[derive(Debug)] +#[derive(Debug, Clone, Serialize)] pub enum BrokerEvent { /// A git ref in a git repository has changed to refer to a given /// commit. This covers both the case of a new ref, and the case @@ -12,5 +12,6 @@ pub mod error; pub mod event; pub mod msg; pub mod run; +pub mod status; #[cfg(test)] pub mod test; diff --git a/src/status.rs b/src/status.rs new file mode 100644 index 0000000..00bf0c4 --- /dev/null +++ b/src/status.rs @@ -0,0 +1,101 @@ +use std::path::{Path, PathBuf}; + +use serde::Serialize; +use time::{macros::format_description, OffsetDateTime}; + +use crate::{ + event::BrokerEvent, + msg::{RunId, RunResult}, +}; + +#[derive(Serialize)] +pub struct Status { + timestamp: String, + ci_broker_version: &'static str, + ci_broker_git_commit: &'static str, + latest_broker_event: Option<BrokerEvent>, + latest_ci_run: Option<RunId>, + latest_ci_run_result: Option<RunResult>, +} + +impl Status { + pub fn write(&self, filename: &Path) -> Result<(), StatusError> { + let s = serde_json::to_string_pretty(&self).map_err(StatusError::serialize)?; + std::fs::write(filename, s.as_bytes()) + .map_err(|e| StatusError::status_write(filename, e))?; + Ok(()) + } +} + +pub struct StatusBuilder { + filename: PathBuf, + latest_broker_event: Option<BrokerEvent>, + latest_ci_run: Option<RunId>, + latest_ci_run_result: Option<RunResult>, +} + +impl StatusBuilder { + pub fn new(filename: &Path) -> Self { + Self { + filename: filename.into(), + latest_ci_run: None, + latest_ci_run_result: None, + latest_broker_event: None, + } + } + + pub fn broker_event(&mut self, event: &BrokerEvent) { + self.latest_broker_event = Some(event.clone()); + } + + pub fn ci_run(&mut self, run_id: &RunId, result: &RunResult) { + self.latest_ci_run = Some(run_id.clone()); + self.latest_ci_run_result = Some(result.clone()); + } + + pub fn write(&self) -> Result<(), StatusError> { + let status = Status { + timestamp: Self::now()?, + ci_broker_version: env!("CARGO_PKG_VERSION"), + ci_broker_git_commit: env!("GIT_HEAD"), + latest_broker_event: self.latest_broker_event.clone(), + latest_ci_run: self.latest_ci_run.clone(), + latest_ci_run_result: self.latest_ci_run_result.clone(), + }; + status.write(&self.filename)?; + Ok(()) + } + + fn now() -> Result<String, StatusError> { + let fmt = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]Z"); + OffsetDateTime::now_utc() + .format(fmt) + .map_err(StatusError::format_now) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum StatusError { + #[error("failed to format current time stamp")] + FormatNow(#[source] time::error::Format), + + #[error("failed to serialize status as JSON")] + Serizalize(#[source] serde_json::Error), + + #[error("failed to write status to file {0}")] + StatusWrite(PathBuf, #[source] std::io::Error), +} + +impl StatusError { + fn format_now(err: time::error::Format) -> Self { + Self::FormatNow(err) + } + + fn serialize(err: serde_json::Error) -> Self { + Self::Serizalize(err) + } + + fn status_write(filename: &Path, err: std::io::Error) -> Self { + Self::StatusWrite(filename.into(), err) + } +} |