Radish alpha
r
rad:zwTxygwuz5LDGBq255RA2CbNGrz8
Radicle CI broker
Radicle
Git
feat: generate RSS feeds for all, failed CI runs
Merged liw opened 1 year ago

This is primarily a stop-gap measure so I can have some notification of CI runs, without having to manually check the report page. It may also be useful for other people, who do not use Radicle themselves, but are interested in CI runs on Radicle nodes.

Signed-off-by: Lars Wirzenius liw@liw.fi

4 files changed +218 -1 dc61d2bb 4caab211
modified Cargo.lock
@@ -212,6 +212,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"

[[package]]
+
name = "atom_syndication"
+
version = "0.12.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2a3a5ed3201df5658d1aa45060c5a57dc9dba8a8ada20d696d67cb0c479ee043"
+
dependencies = [
+
 "chrono",
+
 "derive_builder",
+
 "diligent-date-parser",
+
 "never",
+
 "quick-xml",
+
]
+

+
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -597,6 +610,41 @@ dependencies = [
]

[[package]]
+
name = "darling"
+
version = "0.20.10"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
+
dependencies = [
+
 "darling_core",
+
 "darling_macro",
+
]
+

+
[[package]]
+
name = "darling_core"
+
version = "0.20.10"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
+
dependencies = [
+
 "fnv",
+
 "ident_case",
+
 "proc-macro2",
+
 "quote",
+
 "strsim",
+
 "syn 2.0.87",
+
]
+

+
[[package]]
+
name = "darling_macro"
+
version = "0.20.10"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
+
dependencies = [
+
 "darling_core",
+
 "quote",
+
 "syn 2.0.87",
+
]
+

+
[[package]]
name = "data-encoding"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -642,6 +690,37 @@ dependencies = [
]

[[package]]
+
name = "derive_builder"
+
version = "0.20.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
+
dependencies = [
+
 "derive_builder_macro",
+
]
+

+
[[package]]
+
name = "derive_builder_core"
+
version = "0.20.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
+
dependencies = [
+
 "darling",
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.87",
+
]
+

+
[[package]]
+
name = "derive_builder_macro"
+
version = "0.20.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
+
dependencies = [
+
 "derive_builder_core",
+
 "syn 2.0.87",
+
]
+

+
[[package]]
name = "deunicode"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -660,6 +739,15 @@ dependencies = [
]

[[package]]
+
name = "diligent-date-parser"
+
version = "0.1.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f6cf7fe294274a222363f84bcb63cdea762979a0443b4cf1f4f8fd17c86b1182"
+
dependencies = [
+
 "chrono",
+
]
+

+
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -836,6 +924,12 @@ dependencies = [
]

[[package]]
+
name = "fnv"
+
version = "1.0.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+

+
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1231,6 +1325,12 @@ dependencies = [
]

[[package]]
+
name = "ident_case"
+
version = "1.0.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+

+
[[package]]
name = "idna"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1476,6 +1576,12 @@ dependencies = [
]

[[package]]
+
name = "never"
+
version = "0.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91"
+

+
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1845,6 +1951,16 @@ dependencies = [
]

[[package]]
+
name = "quick-xml"
+
version = "0.36.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe"
+
dependencies = [
+
 "encoding_rs",
+
 "memchr",
+
]
+

+
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1899,6 +2015,7 @@ dependencies = [
 "radicle-git-ext",
 "radicle-surf",
 "regex",
+
 "rss",
 "serde",
 "serde_json",
 "serde_yml",
@@ -2160,6 +2277,18 @@ dependencies = [
]

[[package]]
+
name = "rss"
+
version = "2.0.9"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "27e92048f840d98c6d6dd870af9101610ea9ff413f11f1bcebf4f4c31d96d957"
+
dependencies = [
+
 "atom_syndication",
+
 "derive_builder",
+
 "never",
+
 "quick-xml",
+
]
+

+
[[package]]
name = "rust_decimal"
version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
modified Cargo.toml
@@ -18,6 +18,7 @@ html-page = "0.4.0"
radicle-git-ext = "0.8.0"
radicle-surf = { version = "0.22.0", default-features = false, features = ["serde"] }
regex = "1.10.5"
+
rss = "2.0.9"
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.121"
serde_yml = "0.0.11"
modified ci-broker.md
@@ -262,6 +262,8 @@ then stderr contains "loaded configuration"
then stderr contains "CI broker ends successfully"
then file reports/index.html exists
then file reports/status.json exists
+
then file reports/index.rss exists
+
when I run tidy -xml -indent -quiet reports/index.rss

when I run cibtool --db ci-broker.db event list
then stdout is empty
modified src/pages.rs
@@ -17,6 +17,7 @@ use std::{
};

use html_page::{Element, HtmlPage, Tag};
+
use rss::{Channel, ChannelBuilder, Guid, Item, ItemBuilder};
use serde::Serialize;
use time::{macros::format_description, OffsetDateTime};

@@ -31,11 +32,13 @@ use crate::{
    ci_event::{CiEvent, CiEventV1},
    db::{Db, DbError, QueuedCiEvent},
    logger,
-
    msg::RunId,
+
    msg::{RunId, RunResult},
    notif::NotificationReceiver,
    run::{Run, RunState, Whence},
};

+
const BROKER_RSS: &str = "index.rss";
+
const FAILURE_RSS: &str = "failed.rss";
const CSS: &str = include_str!("radicle-ci.css");
const REFERESH_INTERVAL: &str = "300";
const UPDATE_INTERVAL: Duration = Duration::from_secs(60);
@@ -97,6 +100,22 @@ impl PageData {

        doc.push_to_body(Element::new(Tag::H1).with_text(&title));

+
        doc.push_to_body(
+
            Element::new(Tag::P)
+
                .with_text("RSS feeds: ")
+
                .with_child(
+
                    Element::new(Tag::A)
+
                        .with_text("all")
+
                        .with_attribute("href", BROKER_RSS),
+
                )
+
                .with_text(" ")
+
                .with_child(
+
                    Element::new(Tag::A)
+
                        .with_text("failed")
+
                        .with_attribute("href", FAILURE_RSS),
+
                ),
+
        );
+

        Self::h1(&mut doc, "Broker status");
        doc.push_to_body(
            Element::new(Tag::P)
@@ -512,6 +531,62 @@ impl PageData {
        }
        value
    }
+

+
    fn status_as_rss(&self) -> Result<Channel, PageError> {
+
        let mut channel = ChannelBuilder::default();
+
        channel
+
            .title("Radicle CI broker run information")
+
            .description("All CI runs known to this instance of the Radicle CI broker.")
+
            .link("FIXME:link");
+
        for (_alias, repo_id) in self.repos() {
+
            for run in self.runs(repo_id) {
+
                channel.item(Self::rss_item_from_run(run));
+
            }
+
        }
+
        Ok(channel.build())
+
    }
+

+
    fn failed_as_rss(&self) -> Result<Channel, PageError> {
+
        let mut channel = ChannelBuilder::default();
+
        channel
+
            .title("Radicle CI broker run information")
+
            .description("All CI runs known to this instance of the Radicle CI broker.")
+
            .link("FIXME:link");
+
        for (_alias, repo_id) in self.repos() {
+
            for run in self.runs(repo_id) {
+
                if run.state() == RunState::Finished && run.result() == Some(&RunResult::Failure) {
+
                    channel.item(Self::rss_item_from_run(run));
+
                }
+
            }
+
        }
+
        Ok(channel.build())
+
    }
+

+
    fn rss_item_from_run(run: &Run) -> Item {
+
        let mut guid = Guid::default();
+
        guid.set_value(run.broker_run_id().to_string());
+

+
        let state = if run.state() == RunState::Finished {
+
            match run.result() {
+
                Some(result) => result.to_string(),
+
                None => "unknown".to_string(),
+
            }
+
        } else {
+
            run.state().to_string()
+
        };
+
        let title = format!("{state}: {} run {}", run.repo_alias(), run.broker_run_id());
+

+
        let mut item = ItemBuilder::default()
+
            .title(Some(title))
+
            .guid(Some(guid))
+
            .build();
+

+
        if let Some(url) = run.adapter_info_url() {
+
            item.set_link(Some(url.into()));
+
        };
+

+
        item
+
    }
}

/// Data for status pages for CI broker.
@@ -647,6 +722,16 @@ impl StatusPage {
            let filename = dirname.join(STATUS_JSON);
            let json = data.status_page_as_json()?;
            Self::write_file(&filename, &json)?;
+

+
            let filename = dirname.join(BROKER_RSS);
+
            let channel = data.status_as_rss()?;
+
            let rss = channel.to_string();
+
            Self::write_file(&filename, &rss)?;
+

+
            let filename = dirname.join(FAILURE_RSS);
+
            let channel = data.failed_as_rss()?;
+
            let rss = channel.to_string();
+
            Self::write_file(&filename, &rss)?;
        }
        Ok(())
    }