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    pub shells: Vec<u64>,
51}
52
53impl Records {
54    /// The disk all records take together.
55    pub fn bytes(&self) -> u64 {
56        self.records.iter().map(|record| record.bytes).sum()
57    }
58}
59
60/// Why a record was removed.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(rename_all = "kebab-case")]
63pub enum RecordRemoval {
64    /// A newer record would have passed `max_count`.
65    Count,
66    /// A newer record would have passed `max_total`.
67    Size,
68    /// It was older than `max_age`.
69    Age,
70    /// Someone removed it.
71    User,
72}
73
74impl std::fmt::Display for RecordRemoval {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        f.write_str(match self {
77            RecordRemoval::Count => "removed to keep within max_count",
78            RecordRemoval::Size => "removed to keep within max_total",
79            RecordRemoval::Age => "removed past max_age",
80            RecordRemoval::User => "removed by hand",
81        })
82    }
83}
84
85/// What the job history keeps about a failed job's record, after the record is gone too.
86#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
87pub struct RecordNote {
88    /// When the record was made; `None` when it was not.
89    pub created_at: Option<u64>,
90    /// Its size when made.
91    pub bytes: Option<u64>,
92    /// Why no record was made, such as too little free disk.
93    pub skipped: Option<String>,
94    /// When it was removed.
95    pub removed_at: Option<u64>,
96    /// Why it was removed.
97    pub removed_by: Option<RecordRemoval>,
98}
99
100/// `DELETE /v1/records` query parameters: exactly one of them.
101#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
102pub struct RemoveRecordsQuery {
103    /// Remove every record.
104    #[serde(default)]
105    pub all: bool,
106    /// Remove the records older than this, such as `2d` or `12h`.
107    pub older_than: Option<String>,
108}
109
110/// `DELETE /v1/records…`: what was removed.
111#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
112pub struct RemovedRecords {
113    /// The jobs whose records were removed.
114    pub jobs: Vec<u64>,
115    /// The disk they took.
116    pub bytes: u64,
117}
118
119/// `POST /v1/records/{job}/shell` query parameters.
120#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
121pub struct ShellQuery {
122    /// The step whose command the shell shows, by GitHub's number; default the step that failed.
123    pub step: Option<u32>,
124    /// The terminal's width in columns.
125    pub cols: Option<u16>,
126    /// The terminal's height in rows.
127    pub rows: Option<u16>,
128}
129
130/// The `Upgrade` protocol of `POST /v1/records/{job}/shell`: after `101 Switching Protocols` the connection carries
131/// the shell's terminal, raw bytes both ways.
132pub const SHELL_PROTOCOL: &str = "runnerd-tty";