OXYGENHUB
# a67b97e9a717 oxygenflow / crates / flow-core / src / api.rs
4.6 KiB · Rust · last changed Runners sign their receipts 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>,
e0918b88 Andy McCall 22
}
23
c23d5191 Andy McCall ↶ 24
/// A pinned subrepo to put in the workspace: `repo`'s state `heads`, at `path`.
25
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
26
pub struct SubrepoInput {
27
    pub path: String,
28
    /// `owner/name` on the hub.
29
    pub repo: String,
30
    pub clone_url: String,
31
    pub heads: Vec<String>,
32
}
33
e0918b88 Andy McCall 34
/// A claimed job: everything the runner needs, nothing more.
35
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
36
pub struct Claim {
37
    pub job_id: i64,
38
    pub run_id: i64,
39
    /// `owner/name`.
40
    pub repo: String,
41
    /// Where to fetch the repository from (`oxy` wire protocol).
42
    pub clone_url: String,
43
    /// Read access to this repository only, until the job finishes.
44
    pub token: String,
45
    /// The state to build, as full change hashes.
46
    pub heads: Vec<String>,
47
    /// Hex BLAKE3 of the canonical pipeline.
48
    pub pipeline_hash: String,
49
    pub job: Job,
50
    pub sandbox: Sandbox,
51
    /// The hub's limit for this job.
52
    pub timeout_s: u64,
c23d5191 Andy McCall ↶ 53
    /// Subrepos the state pins (Oxygen spec §7.8) that the job may read,
54
    /// fetched with the same token and written at their paths.
55
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
56
    pub subrepos: Vec<SubrepoInput>,
c4285535 Andy McCall ↶ 57
    /// The state's hash on the hub (what checks are keyed by).
58
    #[serde(default, skip_serializing_if = "String::is_empty")]
59
    pub state: String,
60
    /// The bookmark the run is for.
61
    #[serde(default, skip_serializing_if = "String::is_empty")]
62
    pub bookmark: String,
63
    /// The run's number within its repository.
64
    #[serde(default)]
65
    pub run_number: i64,
66
}
67
68
impl Claim {
69
    /// What a job is told about what it builds (docs/m0.md §4):
70
    /// `FLOW_REPO`, `FLOW_STATE`, `FLOW_HEADS` (full hashes joined with
71
    /// `+`), `FLOW_BOOKMARK`, `FLOW_RUN` and `FLOW_JOB`. Unknown values are
72
    /// left out.
73
    pub fn flow_env(&self) -> Vec<(String, String)> {
74
        let mut v = vec![
75
            ("FLOW_REPO".to_string(), self.repo.clone()),
76
            ("FLOW_HEADS".to_string(), self.heads.join("+")),
77
            ("FLOW_JOB".to_string(), self.job.name.clone()),
78
        ];
79
        if !self.state.is_empty() {
80
            v.push(("FLOW_STATE".into(), self.state.clone()));
81
        }
82
        if !self.bookmark.is_empty() {
83
            v.push(("FLOW_BOOKMARK".into(), self.bookmark.clone()));
84
        }
85
        if self.run_number > 0 {
86
            v.push(("FLOW_RUN".into(), self.run_number.to_string()));
87
        }
88
        v
89
    }
e0918b88 Andy McCall 90
}
91
92
/// One line of output (docs/m0.md §2).
93
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
94
pub struct Record {
95
    /// Milliseconds since the job started.
96
    pub t: u64,
97
    /// `out`, `err`, `console` (the VM outside the job) or `flow` (the runner).
98
    pub stream: String,
99
    pub text: String,
100
}
101
102
/// `POST /api/v1/flow/jobs/{id}/log`.
103
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
104
pub struct LogBatch {
105
    pub records: Vec<Record>,
106
}
107
108
/// `POST /api/v1/flow/jobs/{id}/finish`.
109
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
110
pub struct Finish {
111
    /// The job's exit code; absent when the runner could not run it.
112
    pub exit_code: Option<i32>,
113
    pub duration_ms: u64,
114
    pub receipt: Option<Receipt>,
47c1d64d Andy McCall ↶ 115
    /// The receipt bound to the job's state and signed by the runner
116
    /// (Oxygen spec §14.6), hex of the signed receipt's bytes.
117
    #[serde(default, skip_serializing_if = "Option::is_none")]
118
    pub signed_receipt: Option<String>,
e0918b88 Andy McCall 119
    /// Why the runner could not run the job (rootfs missing, VM failed).
120
    pub error: Option<String>,
121
}
122
123
/// Longest text of one record the hub keeps; longer lines are cut.
124
pub const MAX_RECORD_TEXT: usize = 16 * 1024;
125
/// Most records the hub keeps per job; later ones are dropped with a note.
126
pub const MAX_RECORDS: usize = 50_000;