deploy/upcloud/src/medium.rs
//! **Does this medium read its own CD? Its record says — or the answer is no.**
//!
//! `cargo xtask iso-installer` writes `<variant>.medium.json` beside every ISO
//! it composes (gunnar `xtask/src/medium.rs`): the ISO's sha256, the
//! korp-installer version it carries, the kernel it was given, and a verdict
//! `cd_driver` — true only when BOTH halves are proven (korp-installer ≥ 0.1.14,
//! which asks optical drives first, and a donor kernel with tunnr `939dbe3`'s
//! ATA/PIIX/AHCI/SR block).
//!
//! This module reads that record for the ISO about to be uploaded and answers
//! ONE question. Every doubt — no record, a record for another ISO (sha256
//! mismatch), a record that is not JSON, `cd_driver: false` — is "no": the
//! medium is uploaded twice, as it always was. That is the safe direction by
//! measurement: a medium WITHOUT the driver uploaded only as a cdrom sits in
//! `maintenance` for ever (T16's negative control on mock-upcloud-kvm), while a
//! medium WITH it uploaded twice merely costs the ~4 minutes it always did.
use std::path::{Path, PathBuf};
use crate::upload::Iso;
/// Where the record lives: beside the ISO, `<stem>.medium.json`.
pub fn record_path(iso: &Path) -> PathBuf {
iso.with_extension("medium.json")
}
/// `Ok(why)` — this medium reads its own CD; `Err(why)` — upload it twice.
pub fn reads_own_cd(iso: &Iso) -> Result<String, String> {
let path = record_path(&iso.path);
let text = std::fs::read_to_string(&path)
.map_err(|e| format!("no medium record at {} ({e})", path.display()))?;
judge(&text, &iso.sha256).map_err(|e| format!("{}: {e}", path.display()))
}
/// The pure half: a record's text against the ISO's sha256.
pub fn judge(record: &str, iso_sha256: &str) -> Result<String, String> {
let v: serde_json::Value =
serde_json::from_str(record).map_err(|e| format!("not JSON ({e})"))?;
let bound = v["iso_sha256"].as_str().unwrap_or("");
if !bound.eq_ignore_ascii_case(iso_sha256) {
return Err(format!(
"the record is for ANOTHER medium (sha256 {bound:?}, this ISO is {iso_sha256}) — a record is \
only evidence about the bytes it names"
));
}
let why = v["why"].as_str().unwrap_or("").to_string();
match v["cd_driver"].as_bool() {
Some(true) => Ok(why),
Some(false) => Err(why),
None => Err("the record carries no `cd_driver` verdict".to_string()),
}
}
/// **The whole decision**: one upload only when the medium reads its own CD
/// AND powers off after INSTALL-OK. Without the power-off, the loop ejects the
/// cdrom mid-pass (the never-loop primitive), and a cd-only installer would
/// lose the medium it reads its payload from. Returns `(cd_only, the line)`.
pub fn decide(record: Result<String, String>, medium_powers_off: bool) -> (bool, String) {
match (record, medium_powers_off) {
(Ok(why), true) => (true, format!("ONE upload (cdrom only) — {why}")),
(Ok(why), false) => (
false,
format!(
"TWO uploads — the medium reads its own CD ({why}), but it does not power off after \
INSTALL-OK, so the cdrom is ejected mid-pass and the payload must also ride on virtio"
),
),
(Err(why), _) => (false, format!("TWO uploads (cdrom + virtio) — {why}")),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn record(sha: &str, cd: bool) -> String {
serde_json::json!({"iso_sha256": sha, "korp_installer": "0.1.14", "cd_driver": cd, "why": if cd { "reads optical first" } else { "no CD block" }}).to_string()
}
#[test]
fn a_record_for_this_medium_saying_yes_is_yes() {
assert_eq!(
judge(&record("ABCD", true), "abcd").unwrap(),
"reads optical first"
);
}
/// Every doubt is "upload it twice".
#[test]
fn every_doubt_is_two_uploads() {
assert_eq!(
judge(&record("abcd", false), "abcd").unwrap_err(),
"no CD block"
);
assert!(
judge(&record("ffff", true), "abcd")
.unwrap_err()
.contains("ANOTHER medium"),
"a yes about other bytes is no"
);
assert!(judge("{not json", "abcd").unwrap_err().contains("not JSON"));
assert!(judge(r#"{"iso_sha256":"abcd"}"#, "abcd")
.unwrap_err()
.contains("no `cd_driver`"));
}
/// One upload needs BOTH: the driver (the record) and the power-off.
#[test]
fn one_upload_needs_the_driver_and_the_power_off() {
assert!(decide(Ok("yes".into()), true).0);
let (one, line) = decide(Ok("yes".into()), false);
assert!(!one && line.contains("ejected mid-pass"), "{line}");
assert!(!decide(Err("no record".into()), true).0);
assert!(!decide(Err("no record".into()), false).0);
}
#[test]
fn the_record_sits_beside_the_iso() {
assert_eq!(
record_path(Path::new("/g/.iso-out/gunnar-installer-metered.iso")),
PathBuf::from("/g/.iso-out/gunnar-installer-metered.medium.json")
);
}
}