Radish alpha
r
Radicle CI broker
Radicle
Git (anonymous pull)
Log in to clone via SSH
feat: update a status page, if configured
Lars Wirzenius committed 2 years ago
commit d6be37d7eedc2a0e08d729b98253e5ff3d996c09
parent 15ddf5d0e7e6badfe3c0deccd9e848e0d8642d3a
10 files changed +233 -2
modified 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"
modified 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" }
added 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");
+
}
modified 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,
added 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(())
+
}
modified 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)]
modified 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),
}
modified 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
modified 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;
added 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)
+
    }
+
}