1use crate::{Error, Result};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct Family {
22 pub name: &'static str,
24 pub versions: &'static [(&'static str, &'static str)],
26 pub default: &'static str,
29}
30
31pub const FAMILIES: &[Family] = &[
33 Family {
34 name: "debian",
35 versions: &[("11", "debian-11"), ("12", "debian-12"), ("13", "debian-13")],
36 default: "12",
37 },
38 Family {
39 name: "ubuntu",
40 versions: &[
41 ("14.04", "ubuntu-14.04"),
42 ("16.04", "ubuntu-16.04"),
43 ("18.04", "ubuntu-18.04"),
44 ("20.04", "ubuntu-20.04"),
45 ("22.04", "ubuntu-22.04"),
46 ("24.04", "ubuntu-24.04"),
47 ],
48 default: "24.04",
49 },
50 Family {
51 name: "rocky",
52 versions: &[("9", "rocky-9")],
53 default: "9",
54 },
55 Family {
56 name: "alma",
57 versions: &[("9", "almalinux-9")],
58 default: "9",
59 },
60 Family {
61 name: "alpine",
62 versions: &[("3.19", "alpine-3.19"), ("3.24", "alpine-3.24")],
63 default: "3.19",
64 },
65 Family {
66 name: "kali",
67 versions: &[("rolling", "kali")],
68 default: "rolling",
69 },
70 Family {
71 name: "fedora",
72 versions: &[("42", "fedora-42")],
73 default: "42",
74 },
75 Family {
76 name: "centos",
77 versions: &[("7", "centos-7")],
78 default: "7",
79 },
80 Family {
81 name: "windows",
82 versions: &[("10", "windows-10"), ("11", "windows-11")],
83 default: "11",
84 },
85 Family {
86 name: "windows-server",
87 versions: &[
88 ("2008r2", "windows-server-2008r2"),
89 ("2012r2", "windows-server-2012r2"),
90 ("2016", "windows-server-2016"),
91 ("2019", "windows-server-2019"),
92 ("2022", "windows-server-2022"),
93 ("2025", "windows-server-2025"),
94 ],
95 default: "2022",
96 },
97];
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
101pub struct Os {
102 pub family: &'static str,
104 pub version: &'static str,
106 pub name: &'static str,
108}
109
110impl Os {
111 #[must_use]
113 pub fn id(&self) -> String {
114 format!("{}@{}", self.family, self.version)
115 }
116}
117
118impl Family {
119 fn get(&self, version: &str) -> Option<Os> {
121 self.versions.iter().find(|(v, _)| *v == version).map(|(v, n)| Os {
122 family: self.name,
123 version: v,
124 name: n,
125 })
126 }
127
128 fn known(&self) -> String {
130 self.versions.iter().map(|(v, _)| *v).collect::<Vec<_>>().join(", ")
131 }
132}
133
134pub fn resolve(text: &str) -> Result<Os> {
142 let (family, version) = text.split_once('@').map_or((text, None), |(f, v)| (f, Some(v)));
143 let Some(f) = FAMILIES.iter().find(|f| f.name == family) else {
144 return Err(Error::UnknownOs {
145 text: text.to_owned(),
146 known: FAMILIES.iter().map(|f| format!("{} ({})", f.name, f.known())).collect::<Vec<_>>().join(", "),
147 });
148 };
149 let version = version.unwrap_or(f.default);
150 f.get(version).ok_or_else(|| Error::UnknownOsVersion {
151 family: f.name,
152 version: version.to_owned(),
153 known: f.known(),
154 default: f.default,
155 })
156}
157
158#[must_use]
160pub fn by_name(name: &str) -> Option<Os> {
161 FAMILIES
162 .iter()
163 .find_map(|f| f.versions.iter().find(|(_, n)| *n == name).and_then(|(v, _)| f.get(v)))
164}
165
166#[must_use]
169pub fn spellings() -> Vec<String> {
170 let mut out: Vec<String> = Vec::new();
171 for f in FAMILIES {
172 out.push(f.name.to_owned());
173 out.extend(f.versions.iter().map(|(v, _)| format!("{}@{v}", f.name)));
174 }
175 out
176}
177
178mod containers;
179
180pub use containers::container_image;
181
182#[cfg(test)]
183mod tests;