| + |
# Implementing a new event filter
|
| + |
|
| + |
Overview:
|
| + |
|
| + |
* in `src/filter.rs`, add a new variant to `enum EventFilter`, with whatever
|
| + |
fields are needed for that filter
|
| + |
* in `sr/filter.rs`, in the `EventFilter::decide` method, add a new `match` arm
|
| + |
for the new filter variant
|
| + |
* in `ci-broker.md` add a new section `## Filter predicte XXX`, like the existing
|
| + |
ones, to verify the new filter works
|
| + |
* run `make` at the root of the source tree to make sure everything works
|
| + |
|
| + |
## Example
|
| + |
|
| + |
The patch to add a `AnyDelegate` filter looks like below.
|
| + |
|
| + |
~~~~~~diff
|
| + |
commit 94fbd9f72fc4db779422fa97786c99bb9b3b14d4
|
| + |
Author: Lars Wirzenius <liw@liw.fi>
|
| + |
Date: Mon Nov 10 14:00:53 2025 +0200
|
| + |
|
| + |
feat: add the AnyDelegate filter
|
| + |
|
| + |
diff --git a/ci-broker.md b/ci-broker.md
|
| + |
index f86748e..ac217d0 100644
|
| + |
--- a/ci-broker.md
|
| + |
+++ b/ci-broker.md
|
| + |
@@ -1234,6 +1234,43 @@ sed -i "s/NODEID/$rid/g" "$yaml"
|
| + |
~~~
|
| + |
|
| + |
|
| + |
+## Filter predicate `AnyDelegate`
|
| + |
+
|
| + |
+_Want:_ We can allow an event that originates in a node for any delegate.
|
| + |
+
|
| + |
+_Why:_ We want to constrain CI to privileged developers for a repository.
|
| + |
+
|
| + |
+~~~scenario
|
| + |
+given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
|
| + |
+given a Git repository xyzzy in the Radicle node
|
| + |
+given a Git repository other in the Radicle node
|
| + |
+
|
| + |
+given file config.yaml from filter-anydelegate.yaml
|
| + |
+given file update-nodeid.sh
|
| + |
+when I run bash update-nodeid.sh xyzzy config.yaml
|
| + |
+
|
| + |
+when I run cibtool --db ci-broker.db trigger --repo xyzzy --commit HEAD
|
| + |
+when I run cibtool --db ci-broker.db trigger --repo other --commit HEAD --node z6MkgEMYod7Hxfy9qCvDv5hYHkZ4ciWmLFgfvm3Wn1b2w2FV
|
| + |
+
|
| + |
+when I run ./env.sh cib --config config.yaml queued
|
| + |
+
|
| + |
+when I run cibtool --db ci-broker.db run list --json
|
| + |
+then stdout contains ""repo_name": "xyzzy""
|
| + |
+then stdout doesn't contain ""repo_name": "other""
|
| + |
+~~~
|
| + |
+
|
| + |
+~~~{#filter-anydelegate.yaml .file .json}
|
| + |
+db: ci-broker.db
|
| + |
+adapters:
|
| + |
+ default:
|
| + |
+ command: ./adapter.sh
|
| + |
+triggers:
|
| + |
+ - adapter: default
|
| + |
+ filters:
|
| + |
+ - !AnyDelegate
|
| + |
+~~~
|
| + |
+
|
| + |
+
|
| + |
## Filter predicate `Tag`
|
| + |
|
| + |
_Want:_ We can allow an event that is about a specific tag.
|
| + |
diff --git a/src/filter.rs b/src/filter.rs
|
| + |
index 188c8bd..86ea648 100644
|
| + |
--- a/src/filter.rs
|
| + |
+++ b/src/filter.rs
|
| + |
@@ -1,5 +1,6 @@
|
| + |
use std::path::{Path, PathBuf};
|
| + |
|
| + |
+use radicle_crypto::PublicKey;
|
| + |
use regex::Regex;
|
| + |
use serde::{Deserialize, Serialize};
|
| + |
|
| + |
@@ -8,7 +9,7 @@ use radicle::{
|
| + |
git::{BranchName, Oid, raw::ObjectType},
|
| + |
node::NodeId,
|
| + |
prelude::{Profile, RepoId},
|
| + |
- storage::git::Repository,
|
| + |
+ storage::{ReadRepository, git::Repository},
|
| + |
};
|
| + |
|
| + |
use crate::{
|
| + |
@@ -92,6 +93,11 @@ pub enum EventFilter {
|
| + |
/// Change originated from specific node.
|
| + |
Node(NodeId),
|
| + |
|
| + |
+ /// Change originated from any delegate node. Note that will change to
|
| + |
+ /// "from delegate" once Radicle separates the "user" and "node"
|
| + |
+ /// concepts.
|
| + |
+ AnyDelegate,
|
| + |
+
|
| + |
/// Commit in change contains a file or directory with this name.
|
| + |
HasFile(PathBuf),
|
| + |
|
| + |
@@ -147,6 +153,25 @@ impl EventFilter {
|
| + |
format!("wanted={wanted} actual={actual:?}"),
|
| + |
)
|
| + |
}
|
| + |
+ #[allow(clippy::unwrap_used)]
|
| + |
+ Self::AnyDelegate => {
|
| + |
+ let repo_id = event.repository().unwrap();
|
| + |
+ let radicle = crate::ergo::Radicle::new().unwrap();
|
| + |
+ let repo = radicle.repository(repo_id).unwrap();
|
| + |
+ let origin = event.from_node().unwrap();
|
| + |
+ let delegates: Vec<PublicKey> = repo
|
| + |
+ .delegates()
|
| + |
+ .iter()
|
| + |
+ .flatten()
|
| + |
+ .map(|d| *d.as_key())
|
| + |
+ .collect();
|
| + |
+ let allowed = delegates.contains(origin);
|
| + |
+ Decision::string(
|
| + |
+ "AnyDelegate",
|
| + |
+ allowed,
|
| + |
+ format!("wanted={origin} delegates={delegates:?}",),
|
| + |
+ )
|
| + |
+ }
|
| + |
Self::Repository(wanted) => {
|
| + |
let actual = event.repository();
|
| + |
let allowed = Some(wanted) == actual;
|
| + |
~~~~~~
|