//! CI models: run and job states, runners, runs, jobs and artifacts. The stored shape is in //! migrations/0024_ci.sql; the store functions live in store/ci.rs. //! //! Job specs are kept as JSON text (`CiJob::spec_json`). The store reads only the `needs` and //! `runs_on` arrays out of it, so this crate does not depend on forge-ci. use std::str::FromStr; use crate::error::{Error, Result}; /// State of a run. Stored as the `as_str` name. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RunState { WaitingApproval, Queued, Running, Success, Failure, Cancelled, Error, } impl RunState { pub fn as_str(self) -> &'static str { match self { RunState::WaitingApproval => "waiting_approval", RunState::Queued => "queued", RunState::Running => "running", RunState::Success => "success", RunState::Failure => "failure", RunState::Cancelled => "cancelled", RunState::Error => "error", } } /// True for states a run cannot leave. pub fn is_terminal(self) -> bool { matches!( self, RunState::Success | RunState::Failure | RunState::Cancelled | RunState::Error ) } } impl FromStr for RunState { type Err = Error; fn from_str(s: &str) -> Result { Ok(match s { "waiting_approval" => RunState::WaitingApproval, "queued" => RunState::Queued, "running" => RunState::Running, "success" => RunState::Success, "failure" => RunState::Failure, "cancelled" => RunState::Cancelled, "error" => RunState::Error, other => return Err(Error::Database(format!("unknown CI run state {other:?}"))), }) } } /// State of a job. Stored as the `as_str` name. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum JobState { Blocked, Queued, Running, Success, Failure, Cancelled, Error, Skipped, } impl JobState { pub fn as_str(self) -> &'static str { match self { JobState::Blocked => "blocked", JobState::Queued => "queued", JobState::Running => "running", JobState::Success => "success", JobState::Failure => "failure", JobState::Cancelled => "cancelled", JobState::Error => "error", JobState::Skipped => "skipped", } } /// True for states a job cannot leave. pub fn is_terminal(self) -> bool { matches!( self, JobState::Success | JobState::Failure | JobState::Cancelled | JobState::Error | JobState::Skipped ) } } impl FromStr for JobState { type Err = Error; fn from_str(s: &str) -> Result { Ok(match s { "blocked" => JobState::Blocked, "queued" => JobState::Queued, "running" => JobState::Running, "success" => JobState::Success, "failure" => JobState::Failure, "cancelled" => JobState::Cancelled, "error" => JobState::Error, "skipped" => JobState::Skipped, other => return Err(Error::Database(format!("unknown CI job state {other:?}"))), }) } } /// A registered runner. The secret and its hash are never part of this type. #[derive(Debug, Clone, PartialEq, Eq)] pub struct CiRunner { pub id: String, pub name: String, pub labels: Vec, pub created_at: String, pub last_seen_at: Option, pub disabled: bool, } /// One run of the workflow for a commit. #[derive(Debug, Clone, PartialEq, Eq)] pub struct CiRun { pub id: String, pub repo_id: String, pub number: i64, /// `push`, `tag`, `pull_request` or `manual`. pub trigger: String, pub git_ref: String, pub commit_oid: String, pub pull_number: Option, pub actor_id: String, pub state: RunState, pub config_error: Option, pub created_at: String, pub finished_at: Option, } /// One job of a run. `spec_json` is the JobSpec as JSON text. #[derive(Debug, Clone, PartialEq, Eq)] pub struct CiJob { pub id: String, pub run_id: String, pub name: String, pub spec_json: String, pub state: JobState, pub runner_id: Option, pub exit_code: Option, pub message: Option, pub queued_at: Option, pub started_at: Option, pub finished_at: Option, pub heartbeat_at: Option, pub cancel_requested: bool, pub log_bytes: i64, } /// An artifact uploaded by a job. The file itself is stored outside the database. #[derive(Debug, Clone, PartialEq, Eq)] pub struct CiArtifact { pub id: String, pub job_id: String, pub name: String, pub size: i64, pub sha256: String, pub created_at: String, } /// A job to create with a run. `needs` must match the `needs` in `spec_json`. #[derive(Debug, Clone, Copy)] pub struct NewJob<'a> { pub name: &'a str, pub spec_json: &'a str, pub needs: &'a [String], }