Skip to content

Commit 05c1f39

Browse files
committed
monitor: tokenless runner select route
1 parent e31145e commit 05c1f39

File tree

3 files changed

+106
-3
lines changed

3 files changed

+106
-3
lines changed

monitor/src/github.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ pub struct ApiRunnerLabel {
3030
pub name: String,
3131
}
3232

33+
#[derive(Clone, Debug, Deserialize, Serialize)]
34+
pub struct ApiWorkflowRunArtifactsResponse {
35+
pub artifacts: Vec<ApiArtifact>,
36+
}
37+
38+
#[derive(Clone, Debug, Deserialize, Serialize)]
39+
pub struct ApiArtifact {
40+
pub name: String,
41+
pub archive_download_url: String,
42+
}
43+
3344
impl ApiRunner {
3445
pub fn labels(&self) -> impl Iterator<Item = &str> {
3546
self.labels.iter().map(|label| label.name.as_str())
@@ -130,3 +141,19 @@ pub fn unregister_runner(id: usize) -> eyre::Result<()> {
130141

131142
Ok(())
132143
}
144+
145+
pub fn list_workflow_run_artifacts(
146+
qualified_repo: &str,
147+
run_id: &str,
148+
) -> eyre::Result<Vec<ApiArtifact>> {
149+
// FIXME: breaks if we have more than 100 artifacts
150+
let result = run_fun!(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28"
151+
"/repos/$qualified_repo/actions/runs/$run_id/artifacts?per_page=100")?;
152+
let result: ApiWorkflowRunArtifactsResponse =
153+
serde_json::from_str(&result).wrap_err("Failed to parse JSON")?;
154+
Ok(result.artifacts)
155+
}
156+
157+
pub fn download_artifact_string(url: &str) -> eyre::Result<String> {
158+
Ok(run_fun!(gh api -- $url | funzip)?)
159+
}

monitor/src/main.rs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use std::{
2323
use askama::Template;
2424
use askama_web::WebTemplate;
2525
use crossbeam_channel::{Receiver, Sender};
26-
use jane_eyre::eyre::{self, eyre, Context, OptionExt};
26+
use jane_eyre::eyre::{self, bail, eyre, Context, OptionExt};
2727
use mktemp::Temp;
2828
use rocket::{
2929
delete,
@@ -47,7 +47,10 @@ use web::{
4747
use crate::{
4848
dashboard::Dashboard,
4949
data::{get_profile_data_path, get_runner_data_path, run_migrations},
50-
github::{list_registered_runners_for_host, Cache},
50+
github::{
51+
download_artifact_string, list_registered_runners_for_host, list_workflow_run_artifacts,
52+
Cache,
53+
},
5154
id::IdGen,
5255
image::{start_libvirt_guest, Rebuilds},
5356
libvirt::list_runner_guests,
@@ -230,6 +233,77 @@ fn take_runners_route(
230233
Ok(RawJson(result))
231234
}
232235

236+
#[post("/select-runner?<unique_id>&<qualified_repo>&<run_id>")]
237+
fn select_runner_route(
238+
unique_id: String,
239+
qualified_repo: String,
240+
run_id: String,
241+
) -> rocket_eyre::Result<RawJson<String>> {
242+
let artifacts = list_workflow_run_artifacts(&qualified_repo, &run_id)?;
243+
let done_artifact = format!("servo-ci-runners_{unique_id}_done");
244+
if artifacts
245+
.iter()
246+
.find(|artifact| artifact.name == done_artifact)
247+
.is_some()
248+
{
249+
Err(EyreReport::InternalServerError(eyre!(
250+
"Job has artifact marking it as done: {done_artifact}"
251+
)))?;
252+
}
253+
let args_artifact = format!("servo-ci-runners_{unique_id}");
254+
let Some(args_artifact) = artifacts
255+
.into_iter()
256+
.find(|artifact| artifact.name == args_artifact)
257+
else {
258+
Err(EyreReport::InternalServerError(eyre!(
259+
"No args artifact found: {args_artifact}"
260+
)))?
261+
};
262+
let args_artifact = download_artifact_string(&args_artifact.archive_download_url)?;
263+
let mut args = args_artifact
264+
.lines()
265+
.flat_map(|line| line.split_once("="))
266+
.collect::<BTreeMap<&str, &str>>();
267+
if args.remove("unique_id") != Some(&*unique_id) {
268+
Err(EyreReport::InternalServerError(eyre!(
269+
"Wrong unique_id in artifact"
270+
)))?;
271+
}
272+
if args.remove("qualified_repo") != Some(&*qualified_repo) {
273+
Err(EyreReport::InternalServerError(eyre!(
274+
"Wrong qualified_repo in artifact"
275+
)))?;
276+
}
277+
if args.remove("run_id") != Some(&*run_id) {
278+
Err(EyreReport::InternalServerError(eyre!(
279+
"Wrong run_id in artifact"
280+
)))?;
281+
}
282+
let Some(profile_key) = args.remove("self_hosted_image_name") else {
283+
Err(EyreReport::InternalServerError(eyre!(
284+
"Wrong run_id in artifact"
285+
)))?
286+
};
287+
288+
let (response_tx, response_rx) = crossbeam_channel::bounded(0);
289+
REQUEST.sender.send_timeout(
290+
Request::TakeRunners {
291+
response_tx,
292+
profile_key: profile_key.to_owned(),
293+
query: TakeRunnerQuery {
294+
unique_id,
295+
qualified_repo,
296+
run_id,
297+
},
298+
count: 1,
299+
},
300+
DOTENV.monitor_thread_send_timeout,
301+
)?;
302+
let result = response_rx.recv_timeout(DOTENV.monitor_thread_recv_timeout)?;
303+
304+
Ok(RawJson(result))
305+
}
306+
233307
#[get("/policy/override")]
234308
fn get_override_policy_route() -> rocket_eyre::Result<Json<Option<Override>>> {
235309
let (response_tx, response_rx) = crossbeam_channel::bounded(0);
@@ -388,6 +462,7 @@ async fn main() -> eyre::Result<()> {
388462
dashboard_json_route,
389463
take_runner_route,
390464
take_runners_route,
465+
select_runner_route,
391466
get_override_policy_route,
392467
override_policy_route,
393468
delete_override_policy_route,

server/nixos/monitor.nix

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
libvirt,
1717
openssh,
1818
time,
19+
unzip,
1920
virt-manager,
2021
zfs,
2122
zsh,
@@ -55,10 +56,10 @@ in stdenv.mkDerivation rec {
5556
libvirt # for virsh(1)
5657
openssh # for ssh(1)
5758
time # for time(1)
59+
unzip # for funzip(1)
5860
virt-manager # for virt-clone(1)
5961
zfs
6062
zsh
61-
# TODO: list other commands needed by scripts here
6263
];
6364

6465
# Some of the scripts run in the guest, like {macos13,ubuntu2204}/init.sh.

0 commit comments

Comments
 (0)