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}
37
38#[derive(Debug, Clone, Serialize, Deserialize, Default)]
39pub struct HistoryCache {
40 pub key: String,
41 pub policy: String,
42 pub host: String,
43 #[serde(default)]
44 pub paths: Vec<String>,
45}
46
47#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
48pub enum HistoryStatus {
49 Success,
50 Failed,
51 Skipped,
52 Running,
53}
54
55pub fn load(path: &Path) -> Result<Vec<HistoryEntry>> {
56 if !path.exists() {
57 return Ok(Vec::new());
58 }
59 let contents =
60 fs::read_to_string(path).with_context(|| format!("failed to read {:?}", path))?;
61 let entries: Vec<HistoryEntry> = serde_json::from_str(&contents)
62 .with_context(|| format!("failed to parse history {:?}", path))?;
63 Ok(entries)
64}
65
66pub fn save(path: &Path, entries: &[HistoryEntry]) -> Result<()> {
67 let serialized =
68 serde_json::to_string_pretty(entries).context("failed to serialize history")?;
69 fs::write(path, serialized).with_context(|| format!("failed to write {:?}", path))
70}