1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::Path;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct HistoryEntry {
8 pub run_id: String,
9 pub finished_at: String,
10 pub status: HistoryStatus,
11 pub jobs: Vec<HistoryJob>,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct HistoryJob {
16 pub name: String,
17 pub stage: String,
18 pub status: HistoryStatus,
19 pub log_hash: String,
20 #[serde(default)]
21 pub log_path: Option<String>,
22 #[serde(default)]
23 pub artifact_dir: Option<String>,
24 #[serde(default)]
25 pub artifacts: Vec<String>,
26 #[serde(default)]
27 pub caches: Vec<HistoryCache>,
28 #[serde(default)]
29 pub container_name: Option<String>,
30 #[serde(default)]
31 pub service_network: Option<String>,
32 #[serde(default)]
33 pub service_containers: Vec<String>,
34 #[serde(default)]
35 pub runtime_summary_path: Option<String>,
36 #[serde(default)]
37 pub env_vars: Vec<String>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, Default)]
41pub struct HistoryCache {
42 pub key: String,
43 pub policy: String,
44 pub host: String,
45 #[serde(default)]
46 pub paths: Vec<String>,
47}
48
49#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
50pub enum HistoryStatus {
51 Success,
52 Failed,
53 Skipped,
54 Running,
55}
56
57pub fn load(path: &Path) -> Result<Vec<HistoryEntry>> {
58 if !path.exists() {
59 return Ok(Vec::new());
60 }
61 let contents =
62 fs::read_to_string(path).with_context(|| format!("failed to read {:?}", path))?;
63 let entries: Vec<HistoryEntry> = serde_json::from_str(&contents)
64 .with_context(|| format!("failed to parse history {:?}", path))?;
65 Ok(entries)
66}
67
68pub fn save(path: &Path, entries: &[HistoryEntry]) -> Result<()> {
69 let serialized =
70 serde_json::to_string_pretty(entries).context("failed to serialize history")?;
71 fs::write(path, serialized).with_context(|| format!("failed to write {:?}", path))
72}