Radish alpha
r
rad:zwTxygwuz5LDGBq255RA2CbNGrz8
Radicle CI broker
Radicle
Git
cibtool run add option changes, Internal refactoring.
Merged liw opened 1 year ago
7 files changed +180 -97 81a9a7b3 1e9f6c14
modified ci-broker.md
@@ -454,18 +454,17 @@ given file setup-node.sh
when I run bash radenv.sh bash setup-node.sh

given an installed cibtool
-
when I run cibtool --db x.db run add --id x --repo rad:zwTxygwuz5LDGBq255RA2CbNGrz8 --alias x --url https://x/1 --branch main --commit f1815dde6ae406d8fe3cec0b96c4486766342716 --who x --failure --timestamp 2024-07-09T02:00:00
+
when I run bash radenv.sh cibtool --db x.db run add --repo testy --branch main --commit HEAD --failure

given a directory reports
when I run bash radenv.sh cibtool --db x.db report --output-dir reports

then file reports/index.html exists
-
then file reports/index.html contains "zwTxygwuz5LDGBq255RA2CbNGrz8"
-

-
then file reports/zwTxygwuz5LDGBq255RA2CbNGrz8.html exists
-
then file reports/zwTxygwuz5LDGBq255RA2CbNGrz8.html contains "success"
+
then file reports/index.html contains "testy"
~~~

+
This doesn't check that there is a per-repository HTML file, because
+
we have not convenient way to know the repository ID.

# Acceptance criteria for test tooling

@@ -883,13 +882,17 @@ _Why:_ This is primarily needed for testing.
_Who:_ `cib-devs`

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

given an installed cibtool
when I run cibtool --db x.db run list
then stdout is exactly ""

-
when I run cibtool --db x.db run add --id x --repo rad:zwTxygwuz5LDGBq255RA2CbNGrz8 --alias x --url https://x/1 --branch main --commit f1815dde6ae406d8fe3cec0b96c4486766342716 --who x --triggered --timestamp 2024-07-09T02:00:00
+
when I run bash radenv.sh cibtool --db x.db run add --id xyzzy --repo testy --branch main --commit main --triggered
when I run cibtool --db x.db run list --json
-
then stdout contains "rad:zwTxygwuz5LDGBq255RA2CbNGrz8"
+
then stdout contains "xyzzy"
then stdout contains ""state": "triggered""
~~~

@@ -902,13 +905,17 @@ _Why:_ This is primarily needed for testing.
_Who:_ `cib-dev`.

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

given an installed cibtool
when I run cibtool --db x.db run list
then stdout is exactly ""

-
when I run cibtool --db x.db run add --id x --repo rad:zwTxygwuz5LDGBq255RA2CbNGrz8 --alias x --url https://x/1 --branch main --commit f1815dde6ae406d8fe3cec0b96c4486766342716 --who x --running --timestamp 2024-07-09T02:00:00
+
when I run bash radenv.sh cibtool --db x.db run add --repo testy --url https://x/1 --branch main --commit HEAD --running
when I run cibtool --db x.db run list --json
-
then stdout contains "rad:zwTxygwuz5LDGBq255RA2CbNGrz8"
+
then stdout contains ""repo_name": "testy""
then stdout contains ""state": "running""
~~~

@@ -923,15 +930,19 @@ _Why:_ This is primarily needed for testing.
_Who:_ `cib-dev`.

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

given an installed cibtool
when I run cibtool --db x.db run list
then stdout is exactly ""

-
when I run cibtool --db x.db run add --id x --repo rad:zwTxygwuz5LDGBq255RA2CbNGrz8 --alias x --url https://x/1 --branch main --commit f1815dde6ae406d8fe3cec0b96c4486766342716 --who x --success --timestamp 2024-07-09T02:00:00
+
when I run bash radenv.sh cibtool --db x.db run add --id xyzzy --repo testy --branch main --commit HEAD --success
when I run cibtool --db x.db run list --json
then stdout contains ""state": "finished""
then stdout contains ""result": "success""
-
then stdout contains "rad:zwTxygwuz5LDGBq255RA2CbNGrz8"
+
then stdout contains "xyzzy"
~~~


@@ -944,15 +955,19 @@ _Why:_ This is primarily needed for testing.
_Who:_ `cib-dev`.

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

given an installed cibtool
when I run cibtool --db x.db run list
then stdout is exactly ""

-
when I run cibtool --db x.db run add --id x --repo rad:zwTxygwuz5LDGBq255RA2CbNGrz8 --alias x --url https://x/1 --branch main --commit f1815dde6ae406d8fe3cec0b96c4486766342716 --who x --failure --timestamp 2024-07-09T02:00:00
+
when I run bash radenv.sh cibtool --db x.db run add --id xyzzy --repo testy --branch main --commit HEAD --failure
when I run cibtool --db x.db run list --json
then stdout contains ""state": "finished""
then stdout contains ""result": "failure""
-
then stdout contains "rad:zwTxygwuz5LDGBq255RA2CbNGrz8"
+
then stdout contains "xyzzy"

when I run cibtool --db x.db run list --adapter-run-id abracadabra
then stdout is exactly ""
@@ -967,11 +982,15 @@ _Why:_ This is primarily needed for testing.
_Who:_ `cib-dev`.

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

given an installed cibtool
when I run cibtool --db x.db run list
then stdout is exactly ""

-
when I run cibtool --db x.db run add --id x --repo rad:zwTxygwuz5LDGBq255RA2CbNGrz8 --alias x --url https://x/1 --branch main --commit f1815dde6ae406d8fe3cec0b96c4486766342716 --who x --triggered --timestamp 2024-07-09T02:00:00
+
when I run bash radenv.sh cibtool --db x.db run add --id x --repo testy --branch main --commit HEAD --triggered
when I run cibtool --db x.db run list
then stdout has one line
when I run cibtool --db x.db run show x
modified src/bin/cibtool.rs
@@ -35,6 +35,7 @@ use radicle_ci_broker::{
    notif::NotificationChannel,
    pages::{PageBuilder, PageError},
    run::{Run, RunState, Whence},
+
    util::{self, UtilError},
};

mod cibtoolcmd;
@@ -290,4 +291,7 @@ enum CibToolError {

    #[error("programming error: confused about state and result of run")]
    AddRunConfusion,
+

+
    #[error(transparent)]
+
    Util(#[from] UtilError),
}
modified src/bin/cibtoolcmd/run.rs
@@ -6,21 +6,18 @@ pub struct AddRun {
    #[clap(long)]
    id: Option<RunId>,

-
    /// Set alias of node that performed the CI run.
-
    #[clap(long)]
-
    alias: String,
-

    /// Set optional URL to information about the CI run.
    #[clap(long)]
    url: Option<String>,

-
    /// Set the repository ID that the CI run for.
+
    /// Set the repository the event refers to. Can be a RID, or the
+
    /// repository name.
    #[clap(long)]
-
    repo: RepoId,
+
    repo: String,

    /// Set timestamp of the CI run.
    #[clap(long)]
-
    timestamp: String,
+
    timestamp: Option<String>,

    /// Set the Git branch used by the CI run.
    #[clap(long)]
@@ -28,7 +25,7 @@ pub struct AddRun {

    /// Set the commit SHA ID used by the CI run.
    #[clap(long)]
-
    commit: Oid,
+
    commit: String,

    /// Set the author of the commit used by the CI run.
    #[clap(long)]
@@ -56,12 +53,17 @@ impl Leaf for AddRun {
    fn run(&self, args: &Args) -> Result<(), CibToolError> {
        let db = args.open_db()?;

+
        let profile = util::load_profile()?;
+
        let (rid, repo_name) = util::lookup_repo(&profile, &self.repo)?;
+
        let oid = util::oid_from_cli_arg(&profile, rid, &self.commit)?;
+
        let ts = self.timestamp.clone().unwrap_or(util::now()?);
+

        let whence = Whence::Branch {
            name: self.branch.clone(),
-
            commit: self.commit,
+
            commit: oid,
            who: self.who.clone(),
        };
-
        let mut run = Run::new(self.repo, &self.alias, whence, self.timestamp.clone());
+
        let mut run = Run::new(rid, &repo_name, whence, ts);

        let id = self.id.clone().unwrap_or_default();
        run.set_adapter_run_id(id);
modified src/bin/cibtoolcmd/trigger.rs
@@ -24,80 +24,17 @@ pub struct TriggerCmd {
    id_file: Option<PathBuf>,
}

-
impl TriggerCmd {
-
    fn lookup_nid(&self) -> Result<NodeId, CibToolError> {
-
        let profile = Profile::load().map_err(CibToolError::Profile)?;
-
        Ok(*profile.id())
-
    }
-

-
    fn lookup_rid(&self, wanted: &str) -> Result<RepoId, CibToolError> {
-
        let profile = Profile::load().map_err(CibToolError::Profile)?;
-
        let storage =
-
            Storage::open(profile.storage(), profile.info()).map_err(CibToolError::Storage)?;
-

-
        let mut rid = None;
-
        let repo_infos = storage.repositories().map_err(CibToolError::Repositories)?;
-
        for ri in repo_infos {
-
            let project = ri
-
                .doc
-
                .project()
-
                .map_err(|e| CibToolError::Project(ri.rid, e))?;
-

-
            if project.name() == wanted {
-
                if rid.is_some() {
-
                    return Err(CibToolError::DuplicateRepositories(wanted.into()));
-
                }
-
                rid = Some(ri.rid);
-
            }
-
        }
-

-
        if let Some(rid) = rid {
-
            Ok(rid)
-
        } else {
-
            Err(CibToolError::NotFound(wanted.into()))
-
        }
-
    }
-

-
    fn lookup_commit(&self, rid: RepoId, gitref: &str) -> Result<Oid, CibToolError> {
-
        let profile = Profile::load().map_err(CibToolError::Profile)?;
-
        let storage =
-
            Storage::open(profile.storage(), profile.info()).map_err(CibToolError::Storage)?;
-
        let repo = storage
-
            .repository(rid)
-
            .map_err(|e| CibToolError::RepoOpen(rid, e))?;
-
        let object = repo
-
            .backend
-
            .revparse_single(gitref)
-
            .map_err(|e| CibToolError::RevParse(gitref.into(), e))?;
-

-
        Ok(object.id().into())
-
    }
-
}
-

impl Leaf for TriggerCmd {
    fn run(&self, args: &Args) -> Result<(), CibToolError> {
-
        let rid = if let Ok(rid) = RepoId::from_urn(&self.repo) {
-
            rid
-
        } else {
-
            self.lookup_rid(&self.repo)?
-
        };
-

-
        let oid = if let Ok(rid) = Oid::from_str(&self.commit) {
-
            rid
-
        } else {
-
            self.lookup_commit(rid, &self.commit)?
-
        };
+
        let profile = util::load_profile()?;
+
        let nid = util::lookup_nid(&profile)?;
+
        let (rid, _repo_name) = util::lookup_repo(&profile, &self.repo)?;
+
        let oid = util::oid_from_cli_arg(&profile, rid, &self.commit)?;

-
        let name = format!(
-
            "refs/namespaces/{}/refs/heads/{}",
-
            self.lookup_nid()?,
-
            self.name.as_str()
-
        );
+
        let base = util::lookup_commit(&profile, rid, &format!("{oid}^")).unwrap_or(oid);
+
        let name = format!("refs/namespaces/{nid}/refs/heads/{}", self.name.as_str());
        let name =
            RefString::try_from(name.clone()).map_err(|e| CibToolError::RefString(name, e))?;
-

-
        let base = self.lookup_commit(rid, &format!("{oid}^")).unwrap_or(oid);
-

        let event = BrokerEvent::new(&rid, &name, &oid, Some(base));

        let db = args.open_db()?;
modified src/lib.rs
@@ -18,3 +18,4 @@ pub mod queueproc;
pub mod run;
#[cfg(test)]
pub mod test;
+
pub mod util;
modified src/run.rs
@@ -13,7 +13,7 @@ pub struct Run {
    adapter_run_id: Option<RunId>,
    adapter_info_url: Option<String>,
    repo_id: RepoId,
-
    repo_alias: String,
+
    repo_name: String,
    timestamp: String,
    whence: Whence,
    state: RunState,
@@ -22,13 +22,13 @@ pub struct Run {

impl Run {
    /// Create a new run.
-
    pub fn new(repo_id: RepoId, alias: &str, whence: Whence, timestamp: String) -> Self {
+
    pub fn new(repo_id: RepoId, name: &str, whence: Whence, timestamp: String) -> Self {
        Self {
            broker_run_id: RunId::default(),
            adapter_run_id: None,
            adapter_info_url: None,
            repo_id,
-
            repo_alias: alias.into(),
+
            repo_name: name.into(),
            timestamp,
            whence,
            state: RunState::Triggered,
@@ -38,7 +38,7 @@ impl Run {

    /// Return the repo alias.
    pub fn repo_alias(&self) -> &str {
-
        &self.repo_alias
+
        &self.repo_name
    }

    /// Return the repo id.
added src/util.rs
@@ -0,0 +1,120 @@
+
use std::str::FromStr;
+

+
use time::{macros::format_description, OffsetDateTime};
+

+
use radicle::{
+
    prelude::{NodeId, RepoId},
+
    storage::ReadStorage,
+
    Profile, Storage,
+
};
+
use radicle_git_ext::Oid;
+

+
pub fn lookup_repo(profile: &Profile, wanted: &str) -> Result<(RepoId, String), UtilError> {
+
    let storage = Storage::open(profile.storage(), profile.info()).map_err(UtilError::Storage)?;
+

+
    let repos = storage.repositories().map_err(UtilError::Repositories)?;
+
    let mut rid = None;
+

+
    if let Ok(wanted_rid) = RepoId::from_urn(wanted) {
+
        for ri in repos {
+
            let project = ri
+
                .doc
+
                .project()
+
                .map_err(|e| UtilError::Project(ri.rid, e))?;
+

+
            if ri.rid == wanted_rid {
+
                if rid.is_some() {
+
                    return Err(UtilError::DuplicateRepositories(wanted.into()));
+
                }
+
                rid = Some((ri.rid, project.name().to_string()));
+
            }
+
        }
+
    } else {
+
        for ri in repos {
+
            let project = ri
+
                .doc
+
                .project()
+
                .map_err(|e| UtilError::Project(ri.rid, e))?;
+

+
            if project.name() == wanted {
+
                if rid.is_some() {
+
                    return Err(UtilError::DuplicateRepositories(wanted.into()));
+
                }
+
                rid = Some((ri.rid, project.name().to_string()));
+
            }
+
        }
+
    }
+

+
    if let Some(rid) = rid {
+
        Ok(rid)
+
    } else {
+
        Err(UtilError::NotFound(wanted.into()))
+
    }
+
}
+

+
pub fn oid_from_cli_arg(profile: &Profile, rid: RepoId, commit: &str) -> Result<Oid, UtilError> {
+
    if let Ok(oid) = Oid::from_str(commit) {
+
        Ok(oid)
+
    } else {
+
        lookup_commit(profile, rid, commit)
+
    }
+
}
+

+
pub fn load_profile() -> Result<Profile, UtilError> {
+
    Profile::load().map_err(UtilError::Profile)
+
}
+

+
pub fn lookup_nid(profile: &Profile) -> Result<NodeId, UtilError> {
+
    Ok(*profile.id())
+
}
+

+
pub fn lookup_commit(profile: &Profile, rid: RepoId, gitref: &str) -> Result<Oid, UtilError> {
+
    let storage = Storage::open(profile.storage(), profile.info()).map_err(UtilError::Storage)?;
+
    let repo = storage
+
        .repository(rid)
+
        .map_err(|e| UtilError::RepoOpen(rid, e))?;
+
    let object = repo
+
        .backend
+
        .revparse_single(gitref)
+
        .map_err(|e| UtilError::RevParse(gitref.into(), e))?;
+

+
    Ok(object.id().into())
+
}
+

+
pub fn now() -> Result<String, UtilError> {
+
    let fmt = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]Z");
+
    OffsetDateTime::now_utc()
+
        .format(fmt)
+
        .map_err(UtilError::TimeFormat)
+
}
+

+
#[derive(Debug, thiserror::Error)]
+
#[allow(clippy::large_enum_variant)]
+
pub enum UtilError {
+
    #[error("failed to look up node profile")]
+
    Profile(#[source] radicle::profile::Error),
+

+
    #[error("failed to look up open node storage")]
+
    Storage(#[source] radicle::storage::Error),
+

+
    #[error("failed to list repositories in node storage")]
+
    Repositories(#[source] radicle::storage::Error),
+

+
    #[error("failed to look up project info for repository {0}")]
+
    Project(RepoId, #[source] radicle::identity::doc::PayloadError),
+

+
    #[error("node has more than one repository called {0}")]
+
    DuplicateRepositories(String),
+

+
    #[error("node has no repository called: {0}")]
+
    NotFound(String),
+

+
    #[error("failed to open git repository in node storage: {0}")]
+
    RepoOpen(RepoId, #[source] radicle::storage::RepositoryError),
+

+
    #[error("failed to parse git ref as a commit id: {0}")]
+
    RevParse(String, #[source] radicle::git::raw::Error),
+

+
    #[error("failed to format time stamp")]
+
    TimeFormat(#[source] time::error::Format),
+
}