OXYGENHUB
# 3007d8f011fa oxygenflow / crates / flow-core / src / api.rs
6.6 KiB · Rust · last changed Register runners with a token; describe them in claims 1 month ago source blame history raw
e0918b88 Andy McCall 1
//! The runner API between OxygenHub and `flow runner` (docs/m0.md §5).
2
//! Both sides use these types, so they cannot drift.
3
4
use crate::pipeline::{Job, Sandbox};
5
use crate::receipt::Receipt;
6
use serde::{Deserialize, Serialize};
7
8
/// `POST /api/v1/flow/claim`.
9
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
10
pub struct ClaimRequest {
11
    /// The runner's name, shown on job pages.
12
    pub runner: String,
13
    /// Tiers this runner can run (`microvm`, `syscall`).
14
    pub runtimes: Vec<String>,
bb643c25 Andy McCall ↶ 15
    /// Memory the runner has free for another job, in MiB: the hub hands
16
    /// out only a job whose sandbox fits. Absent: no limit.
17
    #[serde(default, skip_serializing_if = "Option::is_none")]
18
    pub free_memory_mib: Option<u32>,
19
    /// vCPUs the runner has free for another job. Absent: no limit.
20
    #[serde(default, skip_serializing_if = "Option::is_none")]
21
    pub free_cpus: Option<u32>,
f98a8f78 Andy McCall ↶ 22
    /// What the runner is, for the hub's Runners page.
23
    #[serde(default, skip_serializing_if = "Option::is_none")]
24
    pub info: Option<RunnerInfo>,
25
}
26
27
/// A runner's description: shown on the hub's Runners page, and what
28
/// scheduling by label and architecture will match.
29
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
30
pub struct RunnerInfo {
31
    /// `x86_64`, `aarch64`, `riscv64`, …
32
    pub arch: String,
33
    pub os: String,
34
    /// The `flow` version.
35
    pub version: String,
36
    /// From flow.toml `[runner] labels`.
37
    #[serde(default)]
38
    pub labels: Vec<String>,
39
    /// Jobs at once, and the memory (MiB) and vCPUs they share.
40
    pub max_jobs: u32,
41
    #[serde(default)]
42
    pub memory_mib: Option<u32>,
43
    #[serde(default)]
44
    pub cpus: Option<u32>,
45
}
46
47
impl RunnerInfo {
48
    /// This machine and build, with the given labels and capacity.
49
    pub fn here(labels: Vec<String>, max_jobs: u32, memory_mib: Option<u32>, cpus: Option<u32>) -> RunnerInfo {
50
        RunnerInfo {
51
            arch: std::env::consts::ARCH.into(),
52
            os: std::env::consts::OS.into(),
53
            version: env!("CARGO_PKG_VERSION").into(),
54
            labels,
55
            max_jobs,
56
            memory_mib,
57
            cpus,
58
        }
59
    }
60
}
61
62
/// `POST /api/v1/flow/runners/register`: exchange a one-time registration
63
/// token from the hub for a runner credential, binding the runner's
64
/// signing identity to it.
65
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
66
pub struct Register {
67
    pub token: String,
68
    pub name: String,
69
    /// The runner's Oxygen identity, hex of its encoding: its key signs
70
    /// every receipt, and the hub accepts no other for this runner.
71
    pub identity: String,
72
    pub info: RunnerInfo,
73
}
74
75
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
76
pub struct Registered {
77
    pub id: i64,
78
    pub name: String,
79
    /// The runner's bearer token from now on; shown once.
80
    pub credential: String,
e0918b88 Andy McCall 81
}
82
c23d5191 Andy McCall ↶ 83
/// A pinned subrepo to put in the workspace: `repo`'s state `heads`, at `path`.
84
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
85
pub struct SubrepoInput {
86
    pub path: String,
87
    /// `owner/name` on the hub.
88
    pub repo: String,
89
    pub clone_url: String,
90
    pub heads: Vec<String>,
91
}
92
e0918b88 Andy McCall 93
/// A claimed job: everything the runner needs, nothing more.
94
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
95
pub struct Claim {
96
    pub job_id: i64,
97
    pub run_id: i64,
98
    /// `owner/name`.
99
    pub repo: String,
100
    /// Where to fetch the repository from (`oxy` wire protocol).
101
    pub clone_url: String,
102
    /// Read access to this repository only, until the job finishes.
103
    pub token: String,
104
    /// The state to build, as full change hashes.
105
    pub heads: Vec<String>,
106
    /// Hex BLAKE3 of the canonical pipeline.
107
    pub pipeline_hash: String,
108
    pub job: Job,
109
    pub sandbox: Sandbox,
110
    /// The hub's limit for this job.
111
    pub timeout_s: u64,
c23d5191 Andy McCall ↶ 112
    /// Subrepos the state pins (Oxygen spec §7.8) that the job may read,
113
    /// fetched with the same token and written at their paths.
114
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
115
    pub subrepos: Vec<SubrepoInput>,
c4285535 Andy McCall ↶ 116
    /// The state's hash on the hub (what checks are keyed by).
117
    #[serde(default, skip_serializing_if = "String::is_empty")]
118
    pub state: String,
119
    /// The bookmark the run is for.
120
    #[serde(default, skip_serializing_if = "String::is_empty")]
121
    pub bookmark: String,
122
    /// The run's number within its repository.
123
    #[serde(default)]
124
    pub run_number: i64,
125
}
126
127
impl Claim {
128
    /// What a job is told about what it builds (docs/m0.md §4):
129
    /// `FLOW_REPO`, `FLOW_STATE`, `FLOW_HEADS` (full hashes joined with
130
    /// `+`), `FLOW_BOOKMARK`, `FLOW_RUN` and `FLOW_JOB`. Unknown values are
131
    /// left out.
132
    pub fn flow_env(&self) -> Vec<(String, String)> {
133
        let mut v = vec![
134
            ("FLOW_REPO".to_string(), self.repo.clone()),
135
            ("FLOW_HEADS".to_string(), self.heads.join("+")),
136
            ("FLOW_JOB".to_string(), self.job.name.clone()),
137
        ];
138
        if !self.state.is_empty() {
139
            v.push(("FLOW_STATE".into(), self.state.clone()));
140
        }
141
        if !self.bookmark.is_empty() {
142
            v.push(("FLOW_BOOKMARK".into(), self.bookmark.clone()));
143
        }
144
        if self.run_number > 0 {
145
            v.push(("FLOW_RUN".into(), self.run_number.to_string()));
146
        }
147
        v
148
    }
e0918b88 Andy McCall 149
}
150
151
/// One line of output (docs/m0.md §2).
152
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
153
pub struct Record {
154
    /// Milliseconds since the job started.
155
    pub t: u64,
156
    /// `out`, `err`, `console` (the VM outside the job) or `flow` (the runner).
157
    pub stream: String,
158
    pub text: String,
159
}
160
161
/// `POST /api/v1/flow/jobs/{id}/log`.
162
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
163
pub struct LogBatch {
164
    pub records: Vec<Record>,
165
}
166
167
/// `POST /api/v1/flow/jobs/{id}/finish`.
168
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
169
pub struct Finish {
170
    /// The job's exit code; absent when the runner could not run it.
171
    pub exit_code: Option<i32>,
172
    pub duration_ms: u64,
173
    pub receipt: Option<Receipt>,
47c1d64d Andy McCall ↶ 174
    /// The receipt bound to the job's state and signed by the runner
175
    /// (Oxygen spec §14.6), hex of the signed receipt's bytes.
176
    #[serde(default, skip_serializing_if = "Option::is_none")]
177
    pub signed_receipt: Option<String>,
e0918b88 Andy McCall 178
    /// Why the runner could not run the job (rootfs missing, VM failed).
179
    pub error: Option<String>,
180
}
181
182
/// Longest text of one record the hub keeps; longer lines are cut.
183
pub const MAX_RECORD_TEXT: usize = 16 * 1024;
184
/// Most records the hub keeps per job; later ones are dropped with a note.
185
pub const MAX_RECORDS: usize = 50_000;