runnerd_core/api.rs
1//! The daemon's JSON API, version 1: the request and response types every client deserializes, and the
2//! principal a transport authenticates. Plain data only: no I/O, no Docker or GitHub types, no secrets.
3//!
4//! Routes, all under the authenticated principal:
5//!
6//! | Route | Body |
7//! |---|---|
8//! | `GET /v1/status` | [`Status`] |
9//! | `POST /v1/repositories/{owner}/{name}/pause?`[`PauseQuery`] | [`Paused`]; 409 [`Busy`] while it runs a job |
10//! | `POST /v1/repositories/{owner}/{name}/resume` | none, 204 |
11//! | `POST /v1/repositories/pause?`[`PauseQuery`] | [`Paused`]; 409 [`Busy`] while any runs a job, pausing none |
12//! | `POST /v1/repositories/resume` | none, 204: every repository resumed |
13//! | `POST /v1/profiles/{name}/reseed` | none, 202; 409 while it reseeds; `cache.reseeded` when done |
14//! | `POST /v1/caches/{name}/clean` | none, 204; 409 [`Busy`] while a runner uses it |
15//! | `GET /v1/config` | [`ConfigFile`] |
16//! | `POST /v1/config/check` | TOML in, [`ConfigCheck`] |
17//! | `PUT /v1/config` | TOML in, [`ConfigCheck`]; 422 [`ConfigCheck`] when invalid |
18//! | `GET /v1/doctor` | [`Doctor`] |
19//! | `POST /v1/shutdown` | [`ShutdownRequest`] in, 202 |
20//! | `GET /v1/jobs?`[`JobQuery`] | [`JobPage`] |
21//! | `GET /v1/jobs/{id}` | [`Job`] |
22//! | `GET /v1/jobs/{id}/samples` | [`SampleSeries`] |
23//! | `GET /v1/jobs/{id}/logs?`[`LogQuery`] | `text/plain`, one line per log line |
24//! | `GET /v1/host/samples?`[`HostSampleQuery`] | [`HostSamples`] |
25//! | `GET /v1/stats?`[`StatsQuery`] | [`Stats`] |
26//! | `GET /v1/caches` | [`Caches`] |
27//! | `GET /v1/caches/history?`[`CacheHistoryQuery`] | [`CacheHistory`] |
28//! | `GET /v1/events?`[`EventQuery`] | `text/event-stream` of [`EventEnvelope`] |
29//!
30//! Errors carry an [`ErrorResponse`].
31
32use serde::{Deserialize, Serialize};
33
34mod control;
35mod events;
36mod jobs;
37
38pub use control::{
39 Busy, Check, CheckStatus, ConfigCheck, ConfigFile, Doctor, PauseQuery, Paused, RunningJob,
40};
41pub use events::{Event, EventEnvelope, EventQuery};
42pub use jobs::{
43 Bucket, CacheHistory, CacheHistoryQuery, CacheKind, CacheSize, CacheVolume, Caches, HostSample,
44 HostSampleQuery, HostSamples, Job, JobMetrics, JobPage, JobQuery, JobRunner, JobState,
45 JobSummary, LogQuery, LogSummary, Sample, SampleSeries, Stats, StatsQuery, StatsRow, Step,
46 Timeline,
47};
48
49/// Who sent a request, as the transport authenticated it. Handlers get it from one place, so a new transport only
50/// has to produce one.
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "kebab-case", tag = "kind")]
53pub enum Principal {
54 /// The user the daemon runs as, on the local Unix socket.
55 LocalOwner {
56 /// The Unix user ID the socket's peer runs as.
57 uid: u32,
58 },
59}
60
61/// `GET /v1/status`: what the daemon serves and runs right now.
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct Status {
64 /// The daemon's version, from its `Cargo.toml`.
65 pub version: String,
66 /// Set once the daemon stops polling to shut down.
67 pub shutting_down: bool,
68 /// The memory budget and what runners use of it.
69 pub budget: Budget,
70 /// Every configured profile with its slots.
71 pub profiles: Vec<ProfileStatus>,
72 /// Every configured repository.
73 pub repositories: Vec<RepositoryStatus>,
74 /// Queued jobs the repositories' runners can take and none holds yet, at the last poll, oldest first. No event
75 /// carries the list: `job.queued` announces a job, `job.started` and `job.finished` its leaving the queue.
76 /// A repository whose last listing failed, as while GitHub rate-limits runnerd, or that is no longer polled
77 /// because runnerd is shutting down, shows no jobs here, while the history keeps its jobs queued.
78 #[serde(default)]
79 pub queued: Vec<QueuedJob>,
80}
81
82/// The memory budget runners and the mirror share, in bytes.
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84pub struct Budget {
85 /// The systemd slice runner containers run in.
86 pub slice: String,
87 /// The slice's `MemoryMax`; `None` when it is unlimited.
88 pub memory_max: Option<u64>,
89 /// Memory caps of the running containers: runners, seed containers and the mirror.
90 pub memory_in_use: u64,
91 /// The mirror's part of `memory_in_use`: its cap while it is enabled or still running, else 0.
92 #[serde(default)]
93 pub mirror_memory: u64,
94 /// The seed containers' part of `memory_in_use`, one per profile that reseeds, by profile name.
95 #[serde(default)]
96 pub seed_memory: Vec<SeedMemory>,
97}
98
99/// A running seed container's memory cap: its profile's limit when the reseed started.
100#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101pub struct SeedMemory {
102 /// The profile it reseeds.
103 pub profile: String,
104 /// Its cap in bytes.
105 pub memory: u64,
106}
107
108/// A runner profile and what runs in its slots.
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub struct ProfileStatus {
111 /// The name under `[profiles]` in `runnerd.toml`.
112 pub name: String,
113 /// The image its runners run, such as `runnerd-runner:base`.
114 pub image: String,
115 /// CPUs per slot.
116 pub cpus: u32,
117 /// Bytes per slot.
118 pub memory: u64,
119 /// Set while slot 1's layers become the profile's snapshot.
120 pub reseeding: bool,
121 /// Index 0 is slot 1.
122 pub slots: Vec<SlotStatus>,
123}
124
125/// One slot of a profile: room for one job.
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127pub struct SlotStatus {
128 /// 1-based.
129 pub slot: usize,
130 /// `None` when the slot is free.
131 pub runner: Option<RunnerStatus>,
132}
133
134/// A runner registered for one job, in its container.
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136pub struct RunnerStatus {
137 /// `owner/name` the runner is registered on.
138 pub repository: String,
139 /// The runner's name, also its container's.
140 pub name: String,
141 /// The runner's ID on GitHub.
142 pub runner_id: u64,
143 /// The Docker container ID.
144 pub container: String,
145 /// Whether GitHub reports it running a job.
146 pub busy: bool,
147 /// Seconds since the Unix epoch.
148 pub started_at: u64,
149}
150
151/// A configured repository and its queue.
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153pub struct RepositoryStatus {
154 /// `owner/name` on GitHub.
155 pub name: String,
156 /// The profile its jobs run in.
157 pub profile: String,
158 /// The labels its runners register with.
159 pub labels: Vec<String>,
160 /// Paused repositories get no new runners; their running jobs finish.
161 pub paused: bool,
162 /// Queued jobs whose labels these runners match, at the last poll.
163 pub queued_jobs: usize,
164}
165
166/// A queued job runners of this daemon can take, as GitHub listed it at the last poll.
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
168pub struct QueuedJob {
169 /// GitHub's job ID.
170 pub id: u64,
171 /// The repository whose runners can take it, `owner/name`.
172 pub repository: String,
173 /// The workflow run it belongs to.
174 pub run_id: u64,
175 /// 1 for the first attempt of the run, higher for re-runs.
176 pub run_attempt: u32,
177 /// The workflow's name.
178 pub workflow: Option<String>,
179 /// The job's name in the workflow.
180 pub name: String,
181 /// The branch the run is for.
182 pub branch: Option<String>,
183 /// The job's page on GitHub.
184 pub url: Option<String>,
185 /// When GitHub queued it, in seconds since the Unix epoch; 0 if GitHub did not say.
186 pub queued_at: u64,
187}
188
189/// `POST /v1/shutdown`.
190#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
191pub struct ShutdownRequest {
192 /// Kill running jobs and delete their runners instead of waiting for them.
193 #[serde(default)]
194 pub force: bool,
195}
196
197/// The body of every error response.
198#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
199pub struct ErrorResponse {
200 /// What went wrong, for people to read.
201 pub message: String,
202}