pub mod amazon;
pub use amazon::Amazon;
use std::io::ErrorKind;
use std::path::Path;
use thiserror::Error;
#[derive(Debug, Error)]
#[error("platform auto-detection failed")]
pub struct AutodetectError {
#[from]
source: std::io::Error,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Autodetect {
Amazon(Amazon),
Other,
}
impl Autodetect {
const DMI_DIR: &'static str = "/sys/devices/virtual/dmi/id";
const HYPERVISOR_UUID_PATH: &'static str = "/sys/hypervisor/uuid";
pub fn detect() -> Result<Self, AutodetectError> {
Self::detect_at(
Path::new(Self::DMI_DIR),
Path::new(Self::HYPERVISOR_UUID_PATH),
)
}
fn detect_at(dmi_dir: &Path, hypervisor_uuid_path: &Path) -> Result<Self, AutodetectError> {
let sys_vendor = match read_dmi_field(dmi_dir, "sys_vendor") {
Ok(value) => value,
Err(e) if e.kind() == ErrorKind::NotFound => {
return Ok(Autodetect::Other);
}
Err(e) => return Err(AutodetectError::from(e)),
};
match sys_vendor.trim() {
Amazon::SYS_VENDOR_NITRO => match Amazon::detect_nitro_at(dmi_dir)? {
Some(amazon) => Ok(Autodetect::Amazon(amazon)),
None => Ok(Autodetect::Other),
},
Amazon::SYS_VENDOR_XEN => match Amazon::detect_xen_at(hypervisor_uuid_path) {
Some(amazon) => Ok(Autodetect::Amazon(amazon)),
None => Ok(Autodetect::Other),
},
_ => Ok(Autodetect::Other),
}
}
}
fn read_dmi_field(dmi_dir: &Path, file: &str) -> std::io::Result<String> {
let path = dmi_dir.join(file);
Ok(std::fs::read_to_string(path)?.trim().to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::write;
use tempfile::tempdir;
fn dmi_dir_with(fields: &[(&str, &str)]) -> tempfile::TempDir {
let dir = tempdir().expect("create temp DMI dir");
for (file, contents) in fields {
write(dir.path().join(file), contents).expect("write DMI field");
}
dir
}
#[test]
fn nitro_nonmetal_detected() {
let dmi = dmi_dir_with(&[
("sys_vendor", "Amazon EC2\n"),
("product_name", "m5zn.12xlarge\n"),
]);
let result = Autodetect::detect_at(dmi.path(), Path::new("/nonexistent/uuid"));
assert_eq!(
result.unwrap(),
Autodetect::Amazon(Amazon::Nitro("m5zn.12xlarge".to_string()))
);
}
#[test]
fn nitro_metal_detected() {
let dmi = dmi_dir_with(&[
("sys_vendor", "Amazon EC2\n"),
("product_name", "m7i.metal-24xlarge\n"),
]);
let result = Autodetect::detect_at(dmi.path(), Path::new("/nonexistent/uuid"));
let autodetect = result.unwrap();
assert_eq!(
autodetect,
Autodetect::Amazon(Amazon::Nitro("m7i.metal-24xlarge".to_string()))
);
if let Autodetect::Amazon(ref amazon) = autodetect {
assert!(amazon.is_metal());
}
}
#[test]
fn xen_with_ec2_uuid_detected() {
let dmi = dmi_dir_with(&[("sys_vendor", "Xen\n")]);
let uuid_dir = dmi_dir_with(&[("uuid", "ec26d825-3b17-deadbeef\n")]);
let uuid_path = uuid_dir.path().join("uuid");
let result = Autodetect::detect_at(dmi.path(), &uuid_path);
assert_eq!(result.unwrap(), Autodetect::Amazon(Amazon::Xen));
}
#[test]
fn xen_without_ec2_uuid_is_other() {
let dmi = dmi_dir_with(&[("sys_vendor", "Xen\n")]);
let uuid_dir = dmi_dir_with(&[("uuid", "abcd1234-generic-xen\n")]);
let uuid_path = uuid_dir.path().join("uuid");
let result = Autodetect::detect_at(dmi.path(), &uuid_path);
assert_eq!(result.unwrap(), Autodetect::Other);
}
#[test]
fn xen_missing_uuid_is_other() {
let dmi = dmi_dir_with(&[("sys_vendor", "Xen\n")]);
let result = Autodetect::detect_at(dmi.path(), Path::new("/nonexistent/uuid"));
assert_eq!(result.unwrap(), Autodetect::Other);
}
#[test]
fn other_vendor_is_other() {
let dmi = dmi_dir_with(&[("sys_vendor", "QEMU\n")]);
let result = Autodetect::detect_at(dmi.path(), Path::new("/nonexistent/uuid"));
assert_eq!(result.unwrap(), Autodetect::Other);
}
#[test]
fn missing_sys_vendor_is_other() {
let dmi = dmi_dir_with(&[]);
let result = Autodetect::detect_at(dmi.path(), Path::new("/nonexistent/uuid"));
assert_eq!(result.unwrap(), Autodetect::Other);
}
#[test]
fn vendor_tolerates_trailing_whitespace() {
let dmi = dmi_dir_with(&[
("sys_vendor", "Amazon EC2\n"),
("product_name", "c5.xlarge\n"),
]);
let result = Autodetect::detect_at(dmi.path(), Path::new("/nonexistent/uuid"));
assert_eq!(
result.unwrap(),
Autodetect::Amazon(Amazon::Nitro("c5.xlarge".to_string()))
);
}
}