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";