Radish alpha
r
Radicle CI broker
Radicle
Git (anonymous pull)
Log in to clone via SSH
feat: add cibtool subcommand to use filters on recorded events
Lars Wirzenius committed 1 year ago
commit c7b0b393679bfa249f22013232b0549b1deb36d3
parent 6981c483c6cc81b7a3a9f842db4292fdba5e8eaf
4 files changed +122 -3
modified ci-broker.md
@@ -1179,6 +1179,51 @@ when I run bash radenv.sh cibtool event broker --output broker-events.json node-
then file broker-events.json contains "RefChanged""
~~~

+

+
## Filter recorded broker events
+

+
_What:_ Node operator can see what broker events an event filter
+
allow.
+

+
_Why:_ This is helpful so that node operators can see verify their
+
event filter works as they expect.
+

+
_Who:_ `cib-dev`, `node-ops`
+

+
~~~scenario
+
given file radenv.sh
+
given file setup-node.sh
+
when I run bash radenv.sh bash setup-node.sh
+

+
given an installed synthetic-events
+
given file refsfetched.json
+
given file set-rid
+
when I run bash radenv.sh env HOME=../homedir python3 set-rid refsfetched.json testy
+
when I run synthetic-events synt.sock refsfetched.json --log log.txt
+

+
given an installed cibtool
+
when I run bash radenv.sh cibtool event record --output node-events.json
+
when I run bash radenv.sh cibtool event broker --output broker-events.json node-events.json
+

+
given file allow.yaml
+
when I run cibtool event filter  allow.yaml broker-events.json
+
then stdout contains "RefChanged"
+

+
given file deny.yaml
+
when I run cibtool event filter deny.yaml broker-events.json
+
then stdout is exactly ""
+
~~~
+

+
~~~{#allow.yaml .file .yaml}
+
filters:
+
- !Branch "main"
+
~~~
+

+
~~~{#deny.yaml .file .yaml}
+
filters:
+
- !Branch "this-does-not-exist"
+
~~~
+

# Acceptance criteria for logging

The CI broker writes log messages to its standard error output
modified src/bin/cibtool.rs
@@ -30,7 +30,7 @@ use radicle_git_ext::Oid;
use radicle_ci_broker::{
    broker::BrokerError,
    db::{Db, DbError, QueueId, QueuedEvent},
-
    event::BrokerEvent,
+
    event::{BrokerEvent, NodeEventError},
    logger,
    msg::{RunId, RunResult},
    notif::NotificationChannel,
@@ -183,6 +183,7 @@ impl Subcommand for EventCmd {
            EventSubCmd::Remove(x) => x.run(args)?,
            EventSubCmd::Record(x) => x.run(args)?,
            EventSubCmd::Broker(x) => x.run(args)?,
+
            EventSubCmd::Filter(x) => x.run(args)?,
        }
        Ok(())
    }
@@ -200,6 +201,7 @@ enum EventSubCmd {
    Remove(cibtoolcmd::RemoveEvent),
    Record(cibtoolcmd::RecordEvents),
    Broker(cibtoolcmd::BrokerEvents),
+
    Filter(cibtoolcmd::FilterEvents),
}

#[derive(Parser)]
@@ -326,12 +328,24 @@ enum CibToolError {
    #[error("failed to read node events from file {0}")]
    ReadEvents(PathBuf, #[source] std::io::Error),

+
    #[error("failed to read broker events from file {0}")]
+
    ReadBrokerEvents(PathBuf, #[source] std::io::Error),
+

    #[error("failed to read node events as UTF8 from file {0}")]
    NodeEventNotUtf8(PathBuf, #[source] std::string::FromUtf8Error),

+
    #[error("failed to read broker events as UTF8 from file {0}")]
+
    BrokerEventNotUtf8(PathBuf, #[source] std::string::FromUtf8Error),
+

    #[error("failed to read node events as JSON from file {0}")]
    JsonToNodeEvent(PathBuf, #[source] serde_json::Error),

    #[error("failed to create file for broker events: {0}")]
    CreateBrokerEventsFile(PathBuf, #[source] std::io::Error),
+

+
    #[error("failed to read filters from YAML file {0}")]
+
    ReadFilters(PathBuf, #[source] radicle_ci_broker::event::NodeEventError),
+

+
    #[error("failed to check if event is allowed: {0:#?}")]
+
    EventIsAllowed(BrokerEvent, #[source] NodeEventError),
}
modified src/bin/cibtoolcmd/event.rs
@@ -1,6 +1,6 @@
use std::io::Write;

-
use radicle_ci_broker::event::NodeEventSource;
+
use radicle_ci_broker::event::{EventFilter, NodeEventSource};

use super::*;

@@ -410,3 +410,50 @@ impl Leaf for BrokerEvents {
        Ok(())
    }
}
+

+
/// Filter broker events recorded in a file.
+
///
+
/// Those broker events allowed by the filter are written to the
+
/// standard output, as one JSON object per line.
+
#[derive(Parser)]
+
pub struct FilterEvents {
+
    /// Read event filter from this YAML file.
+
    filters: PathBuf,
+

+
    /// Read broker events from this file.
+
    input: PathBuf,
+
}
+

+
impl Leaf for FilterEvents {
+
    fn run(&self, _args: &Args) -> Result<(), CibToolError> {
+
        let filters = EventFilter::from_yaml_file(&self.filters)
+
            .map_err(|e| CibToolError::ReadFilters(self.filters.clone(), e))?;
+

+
        let bytes = std::fs::read(&self.input)
+
            .map_err(|e| CibToolError::ReadBrokerEvents(self.input.clone(), e))?;
+
        let text = String::from_utf8(bytes)
+
            .map_err(|e| CibToolError::BrokerEventNotUtf8(self.input.clone(), e))?;
+

+
        let mut broker_events = vec![];
+
        for line in text.lines() {
+
            let event: BrokerEvent = serde_json::from_str(line)
+
                .map_err(|e| CibToolError::JsonToNodeEvent(self.input.clone(), e))?;
+
            broker_events.push(event);
+
        }
+

+
        for event in broker_events.iter() {
+
            for filter in filters.iter() {
+
                if event
+
                    .is_allowed(filter)
+
                    .map_err(|e| CibToolError::EventIsAllowed(event.clone(), e))?
+
                {
+
                    let json = serde_json::to_string_pretty(event)
+
                        .map_err(|e| CibToolError::EventToJson(event.clone(), e))?;
+
                    println!("{json}");
+
                }
+
            }
+
        }
+

+
        Ok(())
+
    }
+
}
modified src/event.rs
@@ -191,6 +191,10 @@ pub enum NodeEventError {
    #[error("failed to parser filters file: {0}")]
    FiltersJsonFile(PathBuf, #[source] serde_json::Error),

+
    /// An error parsing YAML as filters, when read from a file.
+
    #[error("failed to parser filters file: {0}")]
+
    FiltersYamlFile(PathBuf, #[source] serde_yml::Error),
+

    /// An error parsing JSON as filters, from an in-memory string.
    #[error("failed to parser filters as JSON")]
    FiltersJsonString(#[source] serde_json::Error),
@@ -280,6 +284,15 @@ impl EventFilter {
            .map_err(|e| NodeEventError::FiltersJsonFile(filename.into(), e))?;
        Ok(filters.filters)
    }
+

+
    /// Read filters from a YAML file.
+
    pub fn from_yaml_file(filename: &Path) -> Result<Vec<Self>, NodeEventError> {
+
        let filters =
+
            read(filename).map_err(|e| NodeEventError::ReadFilterFile(filename.into(), e))?;
+
        let filters: Filters = serde_yml::from_slice(&filters)
+
            .map_err(|e| NodeEventError::FiltersYamlFile(filename.into(), e))?;
+
        Ok(filters.filters)
+
    }
}

/// A set of filters for [`NodeEventSource`] to use. This struct
@@ -371,7 +384,7 @@ impl BrokerEvent {
    }

    /// Is this broker event allowed by a filter?
-
    fn is_allowed(&self, filter: &EventFilter) -> Result<bool, NodeEventError> {
+
    pub fn is_allowed(&self, filter: &EventFilter) -> Result<bool, NodeEventError> {
        let res = self.is_allowed_helper(filter)?;
        Ok(res)
    }