Skip to main content

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` and `POST /v1/repositories/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` and `POST /v1/repositories/pause`: the repository, or every one,
48/// is paused, and stays paused when the daemon restarts.
49#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
50pub struct Paused {
51    /// The runners a forced pause stopped: those whose runs it cancelled go once they report the job cancelled,
52    /// within 90 s, the others at once. Empty without `force`.
53    pub stopped: Vec<RunningJob>,
54}
55
56/// `GET /v1/config`: the config the daemon runs with. Parse `text` with `config::Config::parse` for its fields.
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58pub struct ConfigFile {
59    /// The file it was read from, and `PUT /v1/config` writes.
60    pub path: String,
61    /// The TOML, as last loaded or written by the daemon, with each `[[notify.targets]]` entry's `url` and header
62    /// values shown as `config::SECRET_SHOWN`; `PUT /v1/config` puts them back.
63    pub text: String,
64    /// `config::revision` of `text`: send it as `PUT /v1/config?revision=` and a config replaced meanwhile, as
65    /// by auto sizing, is not overwritten (409). `None` from a daemon before it.
66    #[serde(default)]
67    pub revision: Option<String>,
68}
69
70/// `PUT /v1/config` query parameters.
71#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
72pub struct ReplaceQuery {
73    /// The revision of the config the new text was made from; absent, the text replaces whatever runs.
74    pub revision: Option<String>,
75}
76
77/// `POST /v1/config/check` and `PUT /v1/config`: what `runnerd check-config` prints for a config. `PUT` refuses
78/// an invalid one with 422 and this body, which has an [`ErrorResponse`](super::ErrorResponse)'s `message`.
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80pub struct ConfigCheck {
81    /// Whether the daemon can run it.
82    pub valid: bool,
83    /// `valid, memory caps sum to …`, or why it is not valid.
84    pub message: String,
85    /// What a valid config should still fix.
86    pub warnings: Vec<String>,
87    /// Bytes every runner and the mirror may use at once, when it parses.
88    pub memory_caps: Option<u64>,
89    /// The `MemoryMax` of the slice it names; `None` when unlimited or unknown.
90    pub memory_max: Option<u64>,
91    /// From `PUT /v1/config`, once written: the revision of the text written, to make the next change against.
92    /// `None` from a check, or from a daemon before it.
93    #[serde(default)]
94    pub revision: Option<String>,
95}
96
97/// `GET /v1/doctor`: what `runnerd doctor` checks, for the config the daemon runs with.
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
99pub struct Doctor {
100    /// In the order a new user fixes them.
101    pub checks: Vec<Check>,
102}
103
104/// One thing the host needs to run runners. Its `Display` is the line `runnerd doctor` prints.
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106pub struct Check {
107    /// Such as `Docker` or `sysbox`.
108    pub name: String,
109    /// The outcome.
110    pub status: CheckStatus,
111    /// What was found.
112    pub detail: String,
113    /// What to run or change to pass, for a failed check.
114    pub fix: Option<String>,
115}
116
117/// How a [`Check`] came out.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
119#[serde(rename_all = "kebab-case")]
120pub enum CheckStatus {
121    /// Ready.
122    Pass,
123    /// Runners cannot run until it is fixed.
124    Fail,
125    /// Works, but unsafe.
126    Warn,
127    /// Not checked, because a check it depends on failed or no config was given.
128    Skip,
129    /// For information.
130    Info,
131}
132
133impl fmt::Display for Check {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        let status = match self.status {
136            CheckStatus::Pass => "ok",
137            CheckStatus::Fail => "FAIL",
138            CheckStatus::Warn => "WARN",
139            CheckStatus::Skip => "skip",
140            CheckStatus::Info => "info",
141        };
142        write!(f, "{status:<5} {}: {}", self.name, self.detail)?;
143        if let Some(fix) = &self.fix {
144            for (i, line) in fix.lines().enumerate() {
145                let label = if i == 0 { "fix:" } else { "" };
146                write!(f, "\n      {label:<4} {line}")?;
147            }
148        }
149        Ok(())
150    }
151}