Skip to main content

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}