use std::collections::HashSet;
use radicle_ci_broker::{ergo, run::RunBuilder};
use radicle_job::JobId;
use super::*;
#[derive(Parser)]
pub struct AddRun {
/// Set the adapter run ID.
#[clap(long)]
id: Option<RunId>,
/// Set optional URL to information about the CI run.
#[clap(long)]
url: Option<String>,
/// Set the repository the event refers to. Can be a RID, or the
/// repository name.
#[clap(long)]
repo: String,
/// Set timestamp of the CI run.
#[clap(long)]
timestamp: Option<String>,
/// Set the Git branch used by the CI run.
#[clap(long)]
branch: String,
/// Set the commit SHA ID used by the CI run.
#[clap(long)]
commit: String,
/// Set the author of the commit used by the CI run.
#[clap(long)]
who: Option<String>,
/// Set the state of the CI run to "triggered".
#[clap(long, required_unless_present_any = ["running", "success", "failure"])]
triggered: bool,
/// Set the state of the CI run to "running".
#[clap(long)]
#[clap(long, required_unless_present_any = ["triggered", "success", "failure"])]
running: bool,
/// Mark the CI run as finished and successful.
#[clap(long, required_unless_present_any = ["triggered", "running", "failure"])]
success: bool,
/// Mark the CI run as finished and failed.
#[clap(long, required_unless_present_any = ["triggered", "running", "success"])]
failure: bool,
/// Record job COB ID created for this run.
#[clap(long)]
job: Option<JobId>,
}
impl Leaf for AddRun {
fn run(&self, args: &Args) -> Result<(), CibToolError> {
let db = args.open_db()?;
let r = ergo::Radicle::new().map_err(RunError::Ergonomic)?;
let repo = r
.repository_by_name(&self.repo)
.map_err(RunError::Ergonomic)?;
let repo_name = r
.project(&repo.id)
.map_err(RunError::Ergonomic)?
.name()
.to_string();
let oid = r
.resolve_commit(&repo.id, &self.commit)
.map_err(RunError::Ergonomic)?;
let ts = self.timestamp.clone().unwrap_or(util::now()?);
let whence = Whence::Branch {
name: self.branch.clone(),
commit: oid,
who: self.who.clone(),
};
let mut run = RunBuilder::default()
.broker_run_id(RunId::default())
.repo_id(repo.id)
.repo_name(&repo_name)
.whence(whence)
.timestamp(ts)
.build();
let id = self.id.clone().unwrap_or_default();
run.set_adapter_run_id(id);
if let Some(url) = &self.url {
run.set_adapter_info_url(url);
}
if let Some(job) = &self.job {
run.set_job_id(*job);
}
if self.triggered {
run.set_state(RunState::Triggered);
run.unset_result();
} else if self.running {
run.set_state(RunState::Running);
run.unset_result();
} else if self.success {
run.set_result(RunResult::Success);
run.set_state(RunState::Finished);
} else if self.failure {
run.set_result(RunResult::Failure);
run.set_state(RunState::Finished);
} else {
return Err(CibToolError::AddRunConfusion);
}
db.push_run(&run)?;
Ok(())
}
}
#[derive(Parser)]
pub struct UpdateRun {
/// ID of run to update.
#[clap(long)]
id: RunId,
/// Set the state of the CI run to "triggered".
#[clap(long, required_unless_present_any = ["running", "success", "failure"])]
triggered: bool,
/// Set the state of the CI run to "running".
#[clap(long)]
#[clap(long, required_unless_present_any = ["triggered", "success", "failure"])]
running: bool,
/// Mark the CI run as finished and successful.
#[clap(long, required_unless_present_any = ["triggered", "running", "failure"])]
success: bool,
/// Mark the CI run as finished and failed.
#[clap(long, required_unless_present_any = ["triggered", "running", "success"])]
failure: bool,
}
impl Leaf for UpdateRun {
fn run(&self, args: &Args) -> Result<(), CibToolError> {
let db = args.open_db()?;
let runs = db.find_runs(&self.id)?;
if runs.is_empty() {
Err(CibToolError::RunNotFound(self.id.clone()))
} else {
let mut run = runs.first().unwrap().clone(); // this is safe: runs is not empty
run.unset_result();
if self.triggered {
run.set_state(RunState::Triggered);
} else if self.running {
run.set_state(RunState::Running);
} else if self.success {
run.set_state(RunState::Finished);
run.set_result(RunResult::Success);
} else if self.failure {
run.set_state(RunState::Finished);
run.set_result(RunResult::Failure);
}
db.update_run(&run)?;
Ok(())
}
}
}
#[derive(Parser)]
pub struct ShowRun {
/// Broker or adapter run ID.
id: RunId,
}
impl Leaf for ShowRun {
fn run(&self, args: &Args) -> Result<(), CibToolError> {
let db = args.open_db()?;
let runs = db.find_runs(&self.id)?;
let json = serde_json::to_string_pretty(&runs).map_err(CibToolError::RunToJson)?;
println!("{json}");
Ok(())
}
}
#[derive(Parser)]
pub struct RemoveRun {
/// Broker or adapter run IDs of runs to remove.
ids: Vec<RunId>,
/// The run ids are from the adapter, not the CI broker.
#[clap(long)]
adapter: bool,
}
impl Leaf for RemoveRun {
fn run(&self, args: &Args) -> Result<(), CibToolError> {
let db = args.open_db()?;
if self.adapter {
let unwanted: HashSet<RunId> = HashSet::from_iter(self.ids.iter().cloned());
let remove: Vec<Run> = db
.get_all_runs()?
.iter()
.filter(|run| {
if let Some(id) = run.adapter_run_id() {
unwanted.contains(id)
} else {
unwanted.contains(run.broker_run_id())
}
})
.cloned()
.collect();
for run in remove.iter() {
db.remove_run(run.broker_run_id())?;
}
} else {
for run_id in self.ids.iter() {
db.remove_run(run_id)?;
println!("removed run {run_id}");
}
}
Ok(())
}
}
#[derive(Parser)]
pub struct ListRuns {
#[clap(long)]
json: bool,
#[clap(long)]
adapter_run_id: Option<RunId>,
}
impl Leaf for ListRuns {
fn run(&self, args: &Args) -> Result<(), CibToolError> {
let db = args.open_db()?;
let runs = if let Some(wanted) = &self.adapter_run_id {
db.find_runs(wanted)?
} else {
db.get_all_runs()?
};
if self.json {
println!(
"{}",
serde_json::to_string_pretty(&runs).map_err(CibToolError::RunToJson)?
);
} else {
for run in runs {
println!(
"{} {}",
run.broker_run_id(),
run.adapter_run_id()
.map(|id| id.to_string())
.unwrap_or("unknown".into())
);
}
}
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum RunError {
#[error(transparent)]
Ergonomic(#[from] radicle_ci_broker::ergo::ErgoError),
}