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