summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2024-02-15 17:18:06 +0200
committerLars Wirzenius <liw@liw.fi>2024-02-15 18:36:00 +0200
commitd6be37d7eedc2a0e08d729b98253e5ff3d996c09 (patch)
tree07ae0abf9223935966dc701c1f8fdb0620c33ada
parent15ddf5d0e7e6badfe3c0deccd9e848e0d8642d3a (diff)
downloadradicle-ci-broker-d6be37d7eedc2a0e08d729b98253e5ff3d996c09.tar.gz
feat: update a status page, if configured
Signed-off-by: Lars Wirzenius <liw@liw.fi>
-rw-r--r--Cargo.lock65
-rw-r--r--Cargo.toml2
-rw-r--r--build.rs23
-rw-r--r--src/bin/ci-broker.rs12
-rw-r--r--src/bin/status.rs19
-rw-r--r--src/config.rs5
-rw-r--r--src/error.rs5
-rw-r--r--src/event.rs2
-rw-r--r--src/lib.rs1
-rw-r--r--src/status.rs101
10 files changed, 233 insertions, 2 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 34934b9..140c1ba 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index f25d1dd..427b509 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
diff --git a/src/lib.rs b/src/lib.rs
index 428d3de..a39ff25 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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)
+ }
+}