Skip to main content

runnerd_core/api/
records.rs

1//! Records of failed jobs as clients read and remove them: a record is a snapshot of a failed job's runner, kept as
2//! a Docker image so `runnerctl shell` can open a shell where the job failed. What the job history keeps about a
3//! job's record once it is gone, and the queries that remove records and open shells. Sizes are bytes, times are
4//! seconds since the Unix epoch.
5
6use serde::{Deserialize, Serialize};
7
8/// One record: the runner of a failed job as the job left it.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct Record {
11    /// The failed job's ID.
12    pub job: u64,
13    /// `owner/name` on GitHub.
14    pub repository: String,
15    /// The profile the job ran in.
16    pub profile: String,
17    /// The workflow's name, when the history knew it.
18    pub workflow: Option<String>,
19    /// The job's name in the workflow, when the history knew it.
20    pub name: Option<String>,
21    /// When it was made.
22    pub created_at: u64,
23    /// The disk it takes on its own: what removing it frees.
24    pub bytes: u64,
25    /// The Docker image, `runnerd-record:<job>`.
26    pub image: String,
27}
28
29/// The limits on records, from `[records]` in the config.
30#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
31pub struct RecordLimits {
32    /// The most disk all records may take together.
33    pub max_total: u64,
34    /// The most records kept.
35    pub max_count: u32,
36    /// How long a record is kept, in seconds.
37    pub max_age: u64,
38    /// The disk Docker must keep free: no record is made that would leave less.
39    pub min_free: u64,
40}
41
42/// `GET /v1/records`: every record, oldest first, and the limits they are kept within.
43#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
44pub struct Records {
45    /// Oldest first.
46    pub records: Vec<Record>,
47    /// The limits.
48    pub limits: RecordLimits,
49    /// The jobs whose record a shell is open in.
50    #[serde(default)]
51    pub shells: Vec<u64>,
52}
53
54impl Records {
55    /// The disk all records take together.
56    pub fn bytes(&self) -> u64 {
57        self.records.iter().map(|record| record.bytes).sum()
58    }
59}
60
61/// Why a record was removed.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(rename_all = "kebab-case")]
64pub enum RecordRemoval {
65    /// A newer record would have passed `max_count`.
66    Count,
67    /// A newer record would have passed `max_total`.
68    Size,
69    /// It was older than `max_age`.
70    Age,
71    /// Someone removed it.
72    User,
73}
74
75impl std::fmt::Display for RecordRemoval {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        f.write_str(match self {
78            RecordRemoval::Count => "removed to keep within max_count",
79            RecordRemoval::Size => "removed to keep within max_total",
80            RecordRemoval::Age => "removed past max_age",
81            RecordRemoval::User => "removed by hand",
82        })
83    }
84}
85
86/// What the job history keeps about a failed job's record, after the record is gone too.
87#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
88pub struct RecordNote {
89    /// When the record was made; `None` when it was not.
90    pub created_at: Option<u64>,
91    /// Its size when made.
92    pub bytes: Option<u64>,
93    /// Why no record was made, such as too little free disk.
94    pub skipped: Option<String>,
95    /// When it was removed.
96    pub removed_at: Option<u64>,
97    /// Why it was removed.
98    pub removed_by: Option<RecordRemoval>,
99}
100
101/// `DELETE /v1/records` query parameters: exactly one of them.
102#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
103pub struct RemoveRecordsQuery {
104    /// Remove every record.
105    #[serde(default)]
106    pub all: bool,
107    /// Remove the records older than this, such as `2d` or `12h`.
108    pub older_than: Option<String>,
109}
110
111/// `DELETE /v1/records…`: what was removed.
112#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
113pub struct RemovedRecords {
114    /// The jobs whose records were removed.
115    pub jobs: Vec<u64>,
116    /// The disk they took.
117    pub bytes: u64,
118}
119
120/// `POST /v1/records/{job}/shell` query parameters.
121#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
122pub struct ShellQuery {
123    /// The step whose command the shell shows, by GitHub's number; default the step that failed.
124    pub step: Option<u32>,
125    /// The terminal's width in columns.
126    pub cols: Option<u16>,
127    /// The terminal's height in rows.
128    pub rows: Option<u16>,
129}
130
131/// The `Upgrade` protocol of `POST /v1/records/{job}/shell`: after `101 Switching Protocols` the connection carries
132/// the shell's terminal, raw bytes both ways.
133pub const SHELL_PROTOCOL: &str = "runnerd-tty";