deploy/upcloud/src/machine.rs
//! **Which machine this door deploys to, and what that machine implies about
//! attaching a medium** — derived from the roster, never remembered.
//!
//! # The fact this module exists to stop restating
//!
//! `korp-installer` probes **only virtio** block devices for the ISO-9660
//! `CD001` magic. The appliance kernel is the `nuked` preset and has no
//! ATA/AHCI/SCSI at all, so `sr0` does not exist for it to look at even if it
//! wanted to. But the firmware — UpCloud is SeaBIOS-only — boots from a
//! **cdrom**. So one ISO has to ride as two devices, and a medium attached only
//! as a cdrom boots the kernel, starts PID 1 and dies with
//!
//! ```text
//! INSTALLER: volume: no block device carries the ISO-9660 magic (CD001) …
//! INSTALLER: FAILED payload: /run/installer/BOOTX64.EFI: No such file or directory
//! ```
//!
//! That fact was written down FIVE times before this file existed — draupnir
//! `519afcf`'s `MediumAttach::{Cdrom,Virtio,Both}`, `installtest/Cargo.toml`'s
//! MEASURED 2026-08-15 note quoting the identical `CD001` line,
//! `installtest/src/mother_kvm.rs`'s "cdrom AND read-only virtio", this crate's
//! own header, and the roster's own `MediumBring::IsoCdrom` — and the cloud
//! path still had to learn it by hand, against the live box, at the cost of an
//! hour. `plan.rs` then encoded it a sixth time as two hardcoded
//! `client.attach` calls: correct, and correct for no reason a reader or a
//! second platform could use.
//!
//! # What deriving it buys
//!
//! The roster ([`nornir_build_thing::target::presets`]) is the one table of
//! named machines in the constellation, and every row says how its platform
//! takes a medium. Reading that word instead of restating it means:
//!
//! * The two attaches are a CONSEQUENCE of `upcloud` being `IsoCdrom`, so a
//! reader of `plan.rs` is pointed at the fact rather than told the answer.
//! * `scaleway-instance` — whose device set is IDENTICAL to UpCloud's,
//! virtio-blk + virtio-net, so no shape check could ever tell them apart — is
//! **refused by name**, because its `MediumBring` is `ImportQcow2` and this
//! verb's whole premise (attach an ISO to a running server) does not exist
//! there. Before, it would have been attempted: two storages uploaded, a live
//! appliance stopped, and a failure three API calls into the destructive
//! window.
//! * A machine added to the roster later joins this door with no edit here.
//!
//! # Why the answer is an enum and not a pair of bools
//!
//! [`Attach`] has exactly ONE variant today, and that is the honest count: one
//! attach shape has ever been executed by this procedure, against one machine.
//! It is an enum rather than a `bool` so that the day the roster grows a
//! platform this door can serve with a different face count, the `match` in
//! [`Attach::faces`] fails to compile until somebody decides — instead of a
//! second hardcoded pair appearing somewhere else.
use nornir_build_thing::target::presets;
use crate::api::DeviceKind;
/// **The machine this door deploys to.** `deploy/upcloud/` is the UpCloud door
/// by name, so naming the roster row here is this crate's identity and not a
/// duplicated fact — everything that FOLLOWS from the name is read off the
/// roster below.
///
/// It is deliberately not read from gunnar's own `[[build.installer]]` rows:
/// none of them names a machine yet, and the reason is measured and recorded in
/// `crates/gunnar-recipe/tests/machine_named.rs` — naming `upcloud` there
/// derives `target.buses ∋ ide-cdrom`, which requires `CONFIG_ATA=y`, which the
/// `nuked` donor preset vetoes by name. The day that block lifts, this constant
/// becomes a lookup and that test says so.
pub const MACHINE: &str = "upcloud";
/// One face of one medium: the device kind it is attached as, the suffix its
/// storage title carries, and why it exists at all.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Face {
pub kind: DeviceKind,
/// The storage-title suffix (`<stem>-<sha8>-cdrom`).
pub suffix: &'static str,
/// What this face is FOR — printed in the step log, so an operator watching
/// the run sees the reason and not just the call.
pub role: &'static str,
}
/// **How a medium must be attached to a machine**, derived from the roster's
/// `MediumBring`. See the module header for why this is an enum with one arm.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Attach {
/// The platform boots an ATTACHED ISO (`MediumBring::IsoCdrom`). It rides
/// twice: as a cdrom, which is what the firmware boots, and as a virtio
/// disk, which is the only bus `korp-installer` probes for its payload.
CdromAndVirtio,
}
impl Attach {
/// The faces, in attach order. The order is load-bearing: the cdrom goes on
/// first because the boot order is set to `cdrom` and the firmware picks
/// the first one it finds.
pub fn faces(self) -> [Face; 2] {
match self {
Attach::CdromAndVirtio => [
Face {
kind: DeviceKind::Cdrom,
suffix: "cdrom",
role: "what the firmware boots",
},
Face {
kind: DeviceKind::Disk,
suffix: "virtio",
role: "what korp-installer reads the payload from — it probes ONLY virtio",
},
],
}
}
}
/// **The derivation.** `Ok` when the named machine takes an attached ISO; a
/// refusal sentence otherwise, naming the machine and how its medium really
/// arrives.
///
/// The `Err` is not an internal error — it is the message an operator reads,
/// and it has to be enough to act on. A platform that imports a disk image is
/// installed by producing that image (the composer writes it from the same
/// cell: `InstallerBuild::cloud_medium`), not by attaching an ISO to a running
/// server, and the refusal says so rather than leaving the reader to work out
/// which of the two doors they wanted.
pub fn attach_for(machine: &str) -> Result<Attach, String> {
let Some(p) = presets::preset(machine) else {
return Err(format!(
"REFUSED: `{machine}` is not a machine in the roster — the declared ones are: {}",
presets::names().join(", ")
));
};
if !p.medium.is_iso_attach() {
return Err(format!(
"REFUSED: machine `{machine}` ({}) takes its medium as {}. This verb attaches an \
installer ISO to a running server, and the INSTALLER path does not exist on that \
platform{}. Its install is a disk IMPORT: compose the cell that names this machine \
and upload the container the composer writes beside the .iso.",
p.cloud,
p.medium.describe(),
match p.gap() {
Some(why) => format!(" — {why}"),
None => String::new(),
}
));
}
Ok(Attach::CdromAndVirtio)
}
#[cfg(test)]
mod tests {
use super::*;
/// The derivation has to be measured against itself: one that answered the
/// same for every input would pass every test above it and decide nothing.
#[test]
fn the_roster_really_splits_and_the_split_is_not_the_device_set() {
let iso: Vec<_> = presets::PRESETS
.iter()
.filter(|p| p.medium.is_iso_attach())
.map(|p| p.name)
.collect();
let imported: Vec<_> = presets::PRESETS
.iter()
.filter(|p| !p.medium.is_iso_attach())
.map(|p| p.name)
.collect();
assert!(
!iso.is_empty() && !imported.is_empty(),
"{iso:?} / {imported:?}"
);
// The point of deriving from the NAME and never the shape: `upcloud`
// and `scaleway-instance` present the same devices and split here.
let up = presets::preset("upcloud").expect("roster");
let scw = presets::preset("scaleway-instance").expect("roster");
assert_eq!(up.boot_disk.as_str(), scw.boot_disk.as_str());
assert_eq!(up.nic.as_str(), scw.nic.as_str());
assert!(attach_for("upcloud").is_ok());
assert!(attach_for("scaleway-instance").is_err());
}
}