text/x-rust
•
5.57 KB
•
204 lines
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use uuid::Uuid;
use super::common::format_relative_time;
/// Status of a CI job
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum CiJobStatus {
#[default]
Pending,
Running,
Success,
Failed,
}
impl CiJobStatus {
pub fn as_str(&self) -> &'static str {
match self {
Self::Pending => "pending",
Self::Running => "running",
Self::Success => "success",
Self::Failed => "failed",
}
}
}
/// Execution state of a single job within a CI run
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CiJobResult {
pub name: String,
pub status: CiJobStatus,
#[serde(default, with = "time::serde::rfc3339::option")]
pub started_at: Option<OffsetDateTime>,
#[serde(default, with = "time::serde::rfc3339::option")]
pub finished_at: Option<OffsetDateTime>,
pub exit_code: Option<i32>,
}
impl CiJobResult {
pub fn new(name: String) -> Self {
Self {
name,
status: CiJobStatus::Pending,
started_at: None,
finished_at: None,
exit_code: None,
}
}
}
/// A CI job record
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CiJob {
pub id: Uuid,
pub owner: String,
pub project: String,
pub workflow_name: String,
pub branch: String,
pub commit_hash: String,
pub status: CiJobStatus,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
#[serde(default, with = "time::serde::rfc3339::option")]
pub started_at: Option<OffsetDateTime>,
#[serde(default, with = "time::serde::rfc3339::option")]
pub finished_at: Option<OffsetDateTime>,
pub exit_code: Option<i32>,
#[serde(default)]
pub jobs: Vec<CiJobResult>,
}
impl CiJob {
/// Create a new pending job
pub fn new(
owner: String,
project: String,
workflow_name: String,
branch: String,
commit_hash: String,
) -> Self {
Self {
id: Uuid::now_v7(),
owner,
project,
workflow_name,
branch,
commit_hash,
status: CiJobStatus::Pending,
created_at: OffsetDateTime::now_utc(),
started_at: None,
finished_at: None,
exit_code: None,
jobs: Vec::new(),
}
}
/// Get the directory path for this job's data
pub fn job_dir(&self, ci_root: &std::path::Path) -> PathBuf {
ci_root
.join(&self.owner)
.join(&self.project)
.join(self.id.to_string())
}
/// Get the path to the job metadata file
pub fn job_file(&self, ci_root: &std::path::Path) -> PathBuf {
self.job_dir(ci_root).join("job.json")
}
/// Get the directory path for a specific job's results
pub fn job_result_dir(&self, ci_root: &std::path::Path, job_name: &str) -> PathBuf {
self.job_dir(ci_root).join("jobs").join(job_name)
}
/// Get the path to the stdout log file for a specific job
pub fn job_stdout_file(&self, ci_root: &std::path::Path, job_name: &str) -> PathBuf {
self.job_result_dir(ci_root, job_name).join("stdout.log")
}
/// Get the path to the stderr log file for a specific job
pub fn job_stderr_file(&self, ci_root: &std::path::Path, job_name: &str) -> PathBuf {
self.job_result_dir(ci_root, job_name).join("stderr.log")
}
/// Format the created_at date for display
pub fn relative_time(&self) -> String {
let now = OffsetDateTime::now_utc();
let diff = now - self.created_at;
format_relative_time(diff.whole_seconds())
}
/// Get a short version of the commit hash
pub fn short_hash(&self) -> &str {
if self.commit_hash.len() > 8 {
&self.commit_hash[..8]
} else {
&self.commit_hash
}
}
/// URI for viewing this job
pub fn uri(&self) -> String {
format!("/~{}/{}/ci/{}", self.owner, self.project, self.id)
}
}
/// A single job in a workflow definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkflowJob {
pub name: String,
pub script: String,
}
/// Workflow configuration parsed from .rubhub/workflows/*.yaml
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CiWorkflow {
pub name: String,
#[serde(default)]
pub packages: Vec<String>,
pub jobs: Vec<WorkflowJob>,
#[serde(default)]
pub branches: Vec<String>,
}
impl CiWorkflow {
/// Check if this workflow should run on the given branch
pub fn should_run_on_branch(&self, branch: &str) -> bool {
// Empty branches list means run on all branches
self.branches.is_empty() || self.branches.iter().any(|b| b == branch)
}
}
/// Request to run a CI job (sent through the channel)
#[derive(Debug, Clone)]
pub struct CiJobRequest {
pub owner: String,
pub project: String,
pub branch: String,
pub commit_hash: String,
pub workflow_name: String,
pub workflow: CiWorkflow,
}
/// JSON response for CI run status (used by polling endpoint)
#[derive(Debug, Clone, Serialize)]
pub struct CiRunStatusResponse {
pub id: String,
pub status: String,
pub jobs: Vec<CiJobResultResponse>,
}
/// JSON response for a single job result
#[derive(Debug, Clone, Serialize)]
pub struct CiJobResultResponse {
pub name: String,
pub status: String,
pub exit_code: Option<i32>,
pub stdout: String,
pub stderr: String,
}