use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Family {
pub name: &'static str,
pub versions: &'static [(&'static str, &'static str)],
pub default: &'static str,
}
pub const FAMILIES: &[Family] = &[
Family {
name: "debian",
versions: &[("11", "debian-11"), ("12", "debian-12"), ("13", "debian-13")],
default: "12",
},
Family {
name: "ubuntu",
versions: &[
("14.04", "ubuntu-14.04"),
("16.04", "ubuntu-16.04"),
("18.04", "ubuntu-18.04"),
("20.04", "ubuntu-20.04"),
("22.04", "ubuntu-22.04"),
("24.04", "ubuntu-24.04"),
],
default: "24.04",
},
Family {
name: "rocky",
versions: &[("9", "rocky-9")],
default: "9",
},
Family {
name: "alma",
versions: &[("9", "almalinux-9")],
default: "9",
},
Family {
name: "alpine",
versions: &[("3.19", "alpine-3.19"), ("3.24", "alpine-3.24")],
default: "3.19",
},
Family {
name: "kali",
versions: &[("rolling", "kali")],
default: "rolling",
},
Family {
name: "fedora",
versions: &[("42", "fedora-42")],
default: "42",
},
Family {
name: "centos",
versions: &[("7", "centos-7")],
default: "7",
},
Family {
name: "windows",
versions: &[("10", "windows-10"), ("11", "windows-11")],
default: "11",
},
Family {
name: "windows-server",
versions: &[
("2008r2", "windows-server-2008r2"),
("2012r2", "windows-server-2012r2"),
("2016", "windows-server-2016"),
("2019", "windows-server-2019"),
("2022", "windows-server-2022"),
("2025", "windows-server-2025"),
],
default: "2022",
},
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Os {
pub family: &'static str,
pub version: &'static str,
pub name: &'static str,
}
impl Os {
#[must_use]
pub fn id(&self) -> String {
format!("{}@{}", self.family, self.version)
}
}
impl Family {
fn get(&self, version: &str) -> Option<Os> {
self.versions.iter().find(|(v, _)| *v == version).map(|(v, n)| Os {
family: self.name,
version: v,
name: n,
})
}
fn known(&self) -> String {
self.versions.iter().map(|(v, _)| *v).collect::<Vec<_>>().join(", ")
}
}
pub fn resolve(text: &str) -> Result<Os> {
let (family, version) = text.split_once('@').map_or((text, None), |(f, v)| (f, Some(v)));
let Some(f) = FAMILIES.iter().find(|f| f.name == family) else {
return Err(Error::UnknownOs {
text: text.to_owned(),
known: FAMILIES.iter().map(|f| format!("{} ({})", f.name, f.known())).collect::<Vec<_>>().join(", "),
});
};
let version = version.unwrap_or(f.default);
f.get(version).ok_or_else(|| Error::UnknownOsVersion {
family: f.name,
version: version.to_owned(),
known: f.known(),
default: f.default,
})
}
#[must_use]
pub fn by_name(name: &str) -> Option<Os> {
FAMILIES
.iter()
.find_map(|f| f.versions.iter().find(|(_, n)| *n == name).and_then(|(v, _)| f.get(v)))
}
#[must_use]
pub fn spellings() -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for f in FAMILIES {
out.push(f.name.to_owned());
out.extend(f.versions.iter().map(|(v, _)| format!("{}@{v}", f.name)));
}
out
}
mod containers;
pub use containers::container_image;
#[cfg(test)]
mod tests;