runnerd_core/api/control.rs
1//! The control endpoints' types: pausing with or without cancelling running jobs, what a refused request names,
2//! the config file and its check, and the doctor's checks. Plain data; the engine and the daemon act on them.
3
4use std::fmt;
5
6use serde::{Deserialize, Serialize};
7
8/// A runner of this daemon that holds a slot, and the job it runs, if it took one.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct RunningJob {
11 /// `owner/name` the runner is registered on.
12 pub repository: String,
13 /// The runner's name, also its container's.
14 pub runner: String,
15 /// The job's ID; `None` while the runner waits for a job or before its log shows which it took.
16 pub job: Option<u64>,
17}
18
19impl fmt::Display for RunningJob {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self.job {
22 Some(job) => write!(f, "job {job} on {}", self.runner),
23 None => write!(f, "{}", self.runner),
24 }
25 }
26}
27
28/// The body of a 409 refused because runners use what the request would change. Has the `message` of an
29/// [`ErrorResponse`](super::ErrorResponse), so a client that reads only that still gets it.
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct Busy {
32 /// What went wrong, naming the jobs.
33 pub message: String,
34 /// The runners in the way.
35 pub jobs: Vec<RunningJob>,
36}
37
38/// `POST /v1/repositories/{owner}/{name}/pause` query parameters.
39#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
40pub struct PauseQuery {
41 /// Cancel the repository's running jobs and remove their runners. Without it, a pause while the repository
42 /// runs a job is refused with [`Busy`].
43 #[serde(default)]
44 pub force: bool,
45}
46
47/// `POST /v1/repositories/{owner}/{name}/pause`: the repository is paused.
48#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
49pub struct Paused {
50 /// The runners a forced pause stopped: those whose runs it cancelled go once they report the job cancelled,
51 /// within 90 s, the others at once. Empty without `force`.
52 pub stopped: Vec<RunningJob>,
53}
54
55/// `GET /v1/config`: the config the daemon runs with. Parse `text` with `config::Config::parse` for its fields.
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57pub struct ConfigFile {
58 /// The file it was read from, and `PUT /v1/config` writes.
59 pub path: String,
60 /// The TOML, as last loaded or written by the daemon.
61 pub text: String,
62}
63
64/// `POST /v1/config/check` and `PUT /v1/config`: what `runnerd check-config` prints for a config. `PUT` refuses
65/// an invalid one with 422 and this body, which has an [`ErrorResponse`](super::ErrorResponse)'s `message`.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct ConfigCheck {
68 /// Whether the daemon can run it.
69 pub valid: bool,
70 /// `valid, memory caps sum to …`, or why it is not valid.
71 pub message: String,
72 /// What a valid config should still fix.
73 pub warnings: Vec<String>,
74 /// Bytes every runner and the mirror may use at once, when it parses.
75 pub memory_caps: Option<u64>,
76 /// The `MemoryMax` of the slice it names; `None` when unlimited or unknown.
77 pub memory_max: Option<u64>,
78}
79
80/// `GET /v1/doctor`: what `runnerd doctor` checks, for the config the daemon runs with.
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82pub struct Doctor {
83 /// In the order a new user fixes them.
84 pub checks: Vec<Check>,
85}
86
87/// One thing the host needs to run runners. Its `Display` is the line `runnerd doctor` prints.
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89pub struct Check {
90 /// Such as `Docker` or `sysbox`.
91 pub name: String,
92 /// The outcome.
93 pub status: CheckStatus,
94 /// What was found.
95 pub detail: String,
96 /// What to run or change to pass, for a failed check.
97 pub fix: Option<String>,
98}
99
100/// How a [`Check`] came out.
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
102#[serde(rename_all = "kebab-case")]
103pub enum CheckStatus {
104 /// Ready.
105 Pass,
106 /// Runners cannot run until it is fixed.
107 Fail,
108 /// Works, but unsafe.
109 Warn,
110 /// Not checked, because a check it depends on failed or no config was given.
111 Skip,
112 /// For information.
113 Info,
114}
115
116impl fmt::Display for Check {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 let status = match self.status {
119 CheckStatus::Pass => "ok",
120 CheckStatus::Fail => "FAIL",
121 CheckStatus::Warn => "WARN",
122 CheckStatus::Skip => "skip",
123 CheckStatus::Info => "info",
124 };
125 write!(f, "{status:<5} {}: {}", self.name, self.detail)?;
126 if let Some(fix) = &self.fix {
127 for (i, line) in fix.lines().enumerate() {
128 let label = if i == 0 { "fix:" } else { "" };
129 write!(f, "\n {label:<4} {line}")?;
130 }
131 }
132 Ok(())
133 }
134}