raito
2088f62eac
This is a Rust port of the original Perl script, legacy cruft is removed and it focuses on a modern Hydra deployment. Nonetheless, it knows how to perform migrations based on the channel versions. Signed-off-by: Raito Bezarius <masterancpp@gmail.com>
169 lines
4.8 KiB
Rust
169 lines
4.8 KiB
Rust
use std::{collections::HashMap, path::PathBuf};
|
|
use regex::Regex;
|
|
|
|
use serde::{de::Error, Deserialize, Deserializer};
|
|
|
|
use crate::config::MirrorConfig;
|
|
|
|
pub type ReleaseId = u64;
|
|
pub type EvaluationId = u64;
|
|
pub type BuildId = u64;
|
|
|
|
fn deser_bool_as_int<'de, D>(deserializer: D) -> Result<bool, D::Error>
|
|
where D: Deserializer<'de>
|
|
{
|
|
u8::deserialize(deserializer).map(|b| if b == 1 { true } else { false })
|
|
}
|
|
|
|
fn deser_bool_as_string<'de, D>(deserializer: D) -> Result<bool, D::Error>
|
|
where D: Deserializer<'de>
|
|
{
|
|
let s: &str = Deserialize::deserialize(deserializer)?;
|
|
|
|
match s {
|
|
"false" => Ok(false),
|
|
"true" => Ok(true),
|
|
_ => Err(Error::unknown_variant(s, &["false", "true"]))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Channel {
|
|
pub name: String
|
|
}
|
|
|
|
impl Channel {
|
|
pub fn version(&self) -> String {
|
|
let re = Regex::new("([a-z]+)-(?<ver>.*)").unwrap();
|
|
let caps = re.captures(&self.name).expect("Failed to parse the channel name");
|
|
|
|
caps["ver"].to_string()
|
|
}
|
|
|
|
pub fn prefix(&self) -> String {
|
|
let re = Regex::new("(?<name>[a-z]+)-(.*)").unwrap();
|
|
let caps = re.captures(&self.name).expect("Failed to parse the channel name");
|
|
|
|
caps["name"].to_string()
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
pub struct Release {
|
|
pub id: ReleaseId,
|
|
job: String,
|
|
#[serde(rename = "releasename")]
|
|
release_name: Option<String>,
|
|
#[serde(rename = "starttime")]
|
|
start_time: u64,
|
|
#[serde(rename = "stoptime")]
|
|
stop_time: u64,
|
|
#[serde(rename = "nixname")]
|
|
pub nix_name: String,
|
|
#[serde(rename = "jobsetevals")]
|
|
jobset_evals: Vec<EvaluationId>,
|
|
jobset: String,
|
|
#[serde(deserialize_with = "deser_bool_as_int")]
|
|
finished: bool,
|
|
priority: u64,
|
|
system: String,
|
|
timestamp: u64,
|
|
project: String,
|
|
#[serde(rename = "drvpath")]
|
|
derivation_path: String,
|
|
// ignored: buildproducts, buildoutputs, buildmetrics, buildstatus
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct GitInput {
|
|
uri: String,
|
|
revision: String
|
|
}
|
|
|
|
// FIXME(Raito): for some reason, #[serde(tag = "type"), rename_all = "lowercase"] doesn't behave
|
|
// correctly, and causes deserialization failures in practice. `untagged` is suboptimal but works
|
|
// because of the way responses works...
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(untagged, expecting = "An valid jobset input")]
|
|
pub enum Input {
|
|
Boolean {
|
|
#[serde(deserialize_with = "deser_bool_as_string")]
|
|
value: bool
|
|
},
|
|
Git { uri: String, revision: String },
|
|
Nix { value: String },
|
|
}
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
pub struct Evaluation {
|
|
pub id: EvaluationId,
|
|
#[serde(rename="checkouttime")]
|
|
checkout_time: u64,
|
|
#[serde(rename="evaltime")]
|
|
eval_time: u64,
|
|
flake: Option<String>,
|
|
#[serde(rename="jobsetevalinputs", default)]
|
|
pub jobset_eval_inputs: HashMap<String, Input>,
|
|
timestamp: u64,
|
|
builds: Vec<BuildId>,
|
|
}
|
|
|
|
impl Release {
|
|
pub fn version(&self) -> String {
|
|
let re = Regex::new(".+-(?<ver>[0-9].+)").unwrap();
|
|
let caps = re.captures(&self.nix_name).expect("Failed to parse the release name");
|
|
|
|
caps["ver"].to_string()
|
|
}
|
|
|
|
pub fn evaluation_url(&self, hydra_base_uri: &str) -> String {
|
|
let eval_id = self.jobset_evals.first().expect("Failed to obtain the corresponding evaluation, malformed release?");
|
|
format!("{}/eval/{}", hydra_base_uri, eval_id)
|
|
}
|
|
|
|
/// Directory related to this release.
|
|
fn directory(&self, channel: &Channel) -> String {
|
|
match channel.name.as_str() {
|
|
"nixpkgs-unstable" => "nixpkgs".to_string(),
|
|
_ => format!("{}/{}", channel.prefix(), channel.version())
|
|
}
|
|
}
|
|
|
|
pub fn prefix(&self, channel: &Channel) -> object_store::path::Path {
|
|
format!("{}/{}", self.directory(channel), self.nix_name).into()
|
|
}
|
|
}
|
|
|
|
pub fn release_uri(hydra_uri: &str, job_name: &str) -> String {
|
|
format!("{}/job/{}/latest", hydra_uri, job_name)
|
|
}
|
|
|
|
pub struct HydraClient<'a> {
|
|
pub config: &'a MirrorConfig,
|
|
}
|
|
|
|
impl HydraClient<'_> {
|
|
pub async fn fetch_release(&self, job_name: &str) -> reqwest::Result<Release> {
|
|
let client = reqwest::Client::new();
|
|
let resp = client.get(release_uri(&self.config.hydra_uri, job_name))
|
|
.header("Accept", "application/json")
|
|
// TODO: put a proper version
|
|
.header("User-Agent", "nixos-channel-scripts (rust)")
|
|
.send()
|
|
.await?;
|
|
|
|
resp.json().await
|
|
}
|
|
|
|
pub async fn fetch_evaluation(&self, release: &Release) -> reqwest::Result<Evaluation> {
|
|
let client = reqwest::Client::new();
|
|
let resp = client.get(release.evaluation_url(&self.config.hydra_uri))
|
|
.header("Accept", "application/json")
|
|
.header("User-Agent", "nixos-channel-scripts (rust)")
|
|
.send()
|
|
.await?;
|
|
|
|
resp.json().await
|
|
}
|
|
}
|