Radish alpha
r
Radicle CI broker
Radicle
Git (anonymous pull)
Log in to clone via SSH
This is a combination of 2 commits.
Lars Wirzenius committed 2 years ago
commit b4fb1e347be7db19f0859717062f94116b5bec9f
parent 7b31ac5c465c40559aa796b4913a6749cd1b6d6c
5 files changed +99 -17
modified Cargo.lock
@@ -983,6 +983,7 @@ dependencies = [
 "radicle-git-ext",
 "serde",
 "serde_json",
+
 "serde_yaml",
 "thiserror",
]

@@ -1267,6 +1268,19 @@ dependencies = [
]

[[package]]
+
name = "serde_yaml"
+
version = "0.9.27"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
+
dependencies = [
+
 "indexmap",
+
 "itoa",
+
 "ryu",
+
 "serde",
+
 "unsafe-libyaml",
+
]
+

+
[[package]]
name = "sha2"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1523,6 +1537,12 @@ dependencies = [
]

[[package]]
+
name = "unsafe-libyaml"
+
version = "0.2.9"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
+

+
[[package]]
name = "url"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
modified Cargo.toml
@@ -9,6 +9,7 @@ pretty_env_logger = "0.5.0"
radicle-git-ext = "0.7.0"
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
+
serde_yaml = "0.9.27"
thiserror = "1.0.50"

[dependencies.radicle]
modified src/bin/ci-broker.rs
@@ -1,11 +1,18 @@
+
#![allow(unused_imports)]
+
#![allow(unused_variables)]
+
#![allow(dead_code)]
+

use std::{
+
    collections::HashMap,
    error::Error,
+
    ffi::OsStr,
    io::BufReader,
-
    path::Path,
+
    path::{Path, PathBuf},
    process::{Command, Stdio},
};

use log::{debug, info};
+
use serde::{Deserialize, Serialize};

use radicle::prelude::{Id, Profile};
use radicle_ci_broker::{
@@ -32,22 +39,26 @@ fn fallible_main() -> Result<(), BrokerError> {
    info!("Radicle CI broker starts");

    let mut args = std::env::args().skip(1);
-
    let bin = args.next().unwrap();
-
    info!("using {} as the CI adapter", bin);
+
    let filename: PathBuf = if let Some(filename) = args.next() {
+
        PathBuf::from(filename)
+
    } else {
+
        return Err(BrokerError::Usage);
+
    };

-
    let mut filters = vec![];
-
    for filename in args {
-
        let mut more = EventFilter::from_file(Path::new(&filename))?;
-
        filters.append(&mut more);
-
    }
-
    info!("loaded filters: {:#?}", filters);
+
    let config = Config::load(&filename)?;
+
    debug!("loaded configuration: {:#?}", config);

    let profile = Profile::load()?;
    let mut source = NodeEventSource::new(profile)?;
-
    for filter in filters {
-
        source.allow(filter);
+
    for filter in config.filters.iter() {
+
        source.allow(filter.clone());
    }

+
    let default = &config.default_adapter;
+
    let adapter = config
+
        .adapter(default)
+
        .ok_or(BrokerError::UnknownDefaultAdapter(default.clone()))?;
+

    // This loop ends when there's an error, e.g., failure to read an
    // event from the node.
    let mut counter = 0;
@@ -56,7 +67,7 @@ fn fallible_main() -> Result<(), BrokerError> {
            match e {
                BrokerEvent::RefChanged { rid, name: _, oid } => {
                    counter += 1;
-
                    let ret = match run(&bin, rid, oid)? {
+
                    let ret = match run(adapter, rid, oid)? {
                        None => "unknown".into(),
                        Some(RunResult::Error(e)) => format!("ERROR: {}", e),
                        Some(RunResult::Failure) => "FAILED".into(),
@@ -70,15 +81,16 @@ fn fallible_main() -> Result<(), BrokerError> {
    }
}

-
fn run(bin: &str, repo: Id, commit: Oid) -> Result<Option<RunResult>, BrokerError> {
+
fn run(adapter: &Adapter, repo: Id, commit: Oid) -> Result<Option<RunResult>, BrokerError> {
    info!("Trigger on {}, {}", repo, commit);

    debug!("Spawning child process");
-
    let mut child = Command::new(bin)
+
    let mut child = Command::new(&adapter.command)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
+
        .envs(adapter.envs())
        .spawn()
-
        .map_err(|e| BrokerError::SpawnAdapter(bin.into(), e))?;
+
        .map_err(|e| BrokerError::SpawnAdapter(adapter.command.clone(), e))?;

    {
        let stdin = child.stdin.take().expect("get stdin");
@@ -113,3 +125,34 @@ fn run(bin: &str, repo: Id, commit: Oid) -> Result<Option<RunResult>, BrokerErro

    Ok(ret)
}
+

+
#[derive(Debug, Serialize, Deserialize)]
+
struct Config {
+
    default_adapter: String,
+
    adapters: HashMap<String, Adapter>,
+
    filters: Vec<EventFilter>,
+
}
+

+
impl Config {
+
    fn load(filename: &Path) -> Result<Self, BrokerError> {
+
        let config =
+
            std::fs::read(filename).map_err(|e| BrokerError::ReadConfig(filename.into(), e))?;
+
        serde_yaml::from_slice(&config).map_err(|e| BrokerError::ParseConfig(filename.into(), e))
+
    }
+

+
    fn adapter(&self, name: &str) -> Option<&Adapter> {
+
        self.adapters.get(name)
+
    }
+
}
+

+
#[derive(Debug, Serialize, Deserialize)]
+
struct Adapter {
+
    command: PathBuf,
+
    env: HashMap<String, String>,
+
}
+

+
impl Adapter {
+
    fn envs(&self) -> impl Iterator<Item = (&OsStr, &OsStr)> {
+
        self.env.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))
+
    }
+
}
modified src/error.rs
@@ -1,5 +1,7 @@
//! Possible errors returned by the CI broker library.

+
use std::path::PathBuf;
+

use crate::msg::MessageError;

/// All possible errors from the CI broker messages.
@@ -19,5 +21,21 @@ pub enum BrokerError {

    /// Error from spawning a sub-process.
    #[error("failed to spawn a CI adapter sub-process: {0}")]
-
    SpawnAdapter(String, #[source] std::io::Error),
+
    SpawnAdapter(PathBuf, #[source] std::io::Error),
+

+
    /// Usage error.
+
    #[error("usage: radicle-ci-broker CONFIG")]
+
    Usage,
+

+
    /// Can't read config file.
+
    #[error("could not read config file {0}")]
+
    ReadConfig(PathBuf, #[source] std::io::Error),
+

+
    /// Can't parse config file as YAML.
+
    #[error("failed to parse configuration file as YAML: {0}")]
+
    ParseConfig(PathBuf, #[source] serde_yaml::Error),
+

+
    /// Default adapter is not in list of adapters.
+
    #[error("default adapter is not in list of adapters")]
+
    UnknownDefaultAdapter(String),
}
modified src/filter.rs
@@ -38,7 +38,7 @@ use crate::event::NodeEventError;
/// An event filter for allowing events. Or an "AND" combination of events.
///
/// NOTE: Adding "OR" and "NOT" would be easy, too.
-
#[derive(Debug, Deserialize, Serialize)]
+
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub enum EventFilter {
    /// Event concerns a specific repository.