Skip to main content

debian_analyzer/
release_info.rs

1//! Debian and Ubuntu release information.
2
3pub use breezyshim::debian::Vendor;
4use chrono::{NaiveDate, Utc};
5use distro_info::DistroInfo;
6
7/// Pocket names for Debian.
8pub const DEBIAN_POCKETS: &[&str] = &["", "-security", "-proposed-updates", "-backports"];
9
10/// Pocket names for Ubuntu.
11pub const UBUNTU_POCKETS: &[&str] = &["", "-proposed", "-updates", "-security", "-backports"];
12
13/// List of all Debian releases.
14pub fn debian_releases() -> Vec<String> {
15    let debian = distro_info::DebianDistroInfo::new().unwrap();
16    debian
17        .all_at(Utc::now().naive_utc().date())
18        .into_iter()
19        .map(|r| r.series().to_string())
20        .collect()
21}
22
23/// List of all Ubuntu releases.
24pub fn ubuntu_releases() -> Vec<String> {
25    let ubuntu = distro_info::UbuntuDistroInfo::new().unwrap();
26    ubuntu
27        .all_at(Utc::now().naive_utc().date())
28        .into_iter()
29        .map(|r| r.series().to_string())
30        .collect()
31}
32
33/// Infer the distribution from a suite.
34///
35/// When passed the name of a suite (anything in the distributions field of
36/// a changelog) it will infer the distribution from that (i.e. Debian or
37/// Ubuntu).
38///
39/// # Arguments
40/// * `suite`: the string containing the suite
41pub fn suite_to_distribution(suite: &str) -> Option<Vendor> {
42    let all_debian = debian_releases()
43        .iter()
44        .flat_map(|r| DEBIAN_POCKETS.iter().map(move |t| format!("{}{}", r, t)))
45        .collect::<Vec<_>>();
46    let all_ubuntu = ubuntu_releases()
47        .iter()
48        .flat_map(|r| UBUNTU_POCKETS.iter().map(move |t| format!("{}{}", r, t)))
49        .collect::<Vec<_>>();
50    if all_debian.contains(&suite.to_string()) {
51        return Some(Vendor::Debian);
52    }
53    if all_ubuntu.contains(&suite.to_string()) {
54        return Some(Vendor::Ubuntu);
55    }
56
57    if suite == "kali" || suite.starts_with("kali-") {
58        return Some(Vendor::Kali);
59    }
60
61    None
62}
63
64/// Find aliases for a particular release.
65pub fn release_aliases(name: &str, date: Option<NaiveDate>) -> Vec<String> {
66    let mut ret = vec![];
67    let debian_info = distro_info::DebianDistroInfo::new().unwrap();
68    let all_released = debian_info.released(date.unwrap_or(Utc::now().naive_utc().date()));
69    if all_released[0].series() == name {
70        ret.push("stable".to_string());
71    }
72    if all_released[1].series() == name {
73        ret.push("oldstable".to_string());
74    }
75    if all_released[2].series() == name {
76        ret.push("oldoldstable".to_string());
77    }
78
79    if name == "sid" {
80        ret.push("unstable".to_string());
81    }
82
83    let ubuntu_info = distro_info::UbuntuDistroInfo::new().unwrap();
84
85    let all_released = ubuntu_info.released(date.unwrap_or(Utc::now().naive_utc().date()));
86    for series in all_released.iter() {
87        if series.codename() == name {
88            ret.push(series.series().to_string());
89        }
90    }
91
92    ret
93}
94
95/// Resolve a release codename or series name to a series name.
96pub fn resolve_release_codename(name: &str, date: Option<NaiveDate>) -> Option<String> {
97    let date = date.unwrap_or(Utc::now().naive_utc().date());
98    let (distro, mut name) = if let Some((distro, name)) = name.split_once('/') {
99        (Some(distro), name)
100    } else {
101        (None, name)
102    };
103    let active = |x: &Option<NaiveDate>| x.map(|x| x > date).unwrap_or(false);
104    if distro.is_none() || distro == Some("debian") {
105        let debian = distro_info::DebianDistroInfo::new().unwrap();
106        if name == "lts" {
107            let lts = debian
108                .all_at(date)
109                .into_iter()
110                .filter(|r| active(r.eol_lts()))
111                .min_by_key(|r| r.created());
112            return lts.map(|r| r.series().to_string());
113        }
114        if name == "elts" {
115            let elts = debian
116                .all_at(date)
117                .into_iter()
118                .filter(|r| active(r.eol_elts()))
119                .min_by_key(|r| r.created());
120            return elts.map(|r| r.series().to_string());
121        }
122        let mut all_released = debian
123            .all_at(date)
124            .into_iter()
125            .filter(|r| r.release().is_some())
126            .collect::<Vec<_>>();
127        all_released.sort_by_key(|r| r.created());
128        all_released.reverse();
129        if name == "stable" {
130            return Some(all_released[0].series().to_string());
131        }
132        if name == "oldstable" {
133            return Some(all_released[1].series().to_string());
134        }
135        if name == "oldoldstable" {
136            return Some(all_released[2].series().to_string());
137        }
138        if name == "unstable" {
139            name = "sid";
140        }
141        // experimental is a perpetual Debian suite and is no longer a row in
142        // distro-info-data's debian.csv (dropped after 2026.07).
143        if name == "experimental" {
144            return Some("experimental".to_string());
145        }
146        if name == "testing" {
147            let mut all_unreleased = debian
148                .all_at(date)
149                .into_iter()
150                .filter(|r| r.release().is_none())
151                .collect::<Vec<_>>();
152            all_unreleased.sort_by_key(|r| r.created());
153            return Some(all_unreleased.last().unwrap().series().to_string());
154        }
155
156        let all = debian.all_at(date);
157        if let Some(series) = all
158            .iter()
159            .find(|r| r.codename() == name || r.series() == name)
160        {
161            return Some(series.series().to_string());
162        }
163    }
164    if distro.is_none() || distro == Some("ubuntu") {
165        let ubuntu = distro_info::UbuntuDistroInfo::new().unwrap();
166        if name == "esm" {
167            return ubuntu
168                .all_at(date)
169                .into_iter()
170                .filter(|r| active(r.eol_esm()))
171                .min_by_key(|r| r.created())
172                .map(|r| r.series().to_string());
173        }
174        if name == "lts" {
175            return ubuntu
176                .all_at(date)
177                .into_iter()
178                .filter(|r| r.is_lts() && r.supported_at(date))
179                .min_by_key(|r| r.created())
180                .map(|r| r.series().to_string());
181        }
182        let all = ubuntu.all_at(date);
183        if let Some(series) = all
184            .iter()
185            .find(|r| r.codename() == name || r.series() == name)
186        {
187            return Some(series.series().to_string());
188        }
189    }
190    None
191}
192
193include!(concat!(env!("OUT_DIR"), "/key_package_versions.rs"));
194
195#[cfg(test)]
196mod tests {
197    use super::resolve_release_codename;
198
199    #[test]
200    fn test_debian() {
201        assert_eq!("sid", resolve_release_codename("debian/sid", None).unwrap());
202        assert_eq!("sid", resolve_release_codename("sid", None).unwrap());
203        assert_eq!("sid", resolve_release_codename("unstable", None).unwrap());
204        assert_eq!(
205            "experimental",
206            resolve_release_codename("experimental", None).unwrap()
207        );
208    }
209
210    #[test]
211    fn test_ubuntu() {
212        assert_eq!(
213            "trusty",
214            resolve_release_codename("ubuntu/trusty", None).unwrap()
215        );
216        assert_eq!("trusty", resolve_release_codename("trusty", None).unwrap());
217        assert!(resolve_release_codename("ubuntu/lts", None).is_some());
218    }
219
220    #[test]
221    fn test_resolve_debian() {
222        assert_eq!("sid", resolve_release_codename("sid", None).unwrap());
223        assert_eq!("buster", resolve_release_codename("buster", None).unwrap());
224        assert_eq!("sid", resolve_release_codename("unstable", None).unwrap());
225        assert_eq!(
226            "sid",
227            resolve_release_codename("debian/unstable", None).unwrap()
228        );
229        assert!(resolve_release_codename("oldstable", None).is_some());
230        assert!(resolve_release_codename("oldoldstable", None).is_some());
231    }
232
233    #[test]
234    fn test_resolve_unknown() {
235        assert!(resolve_release_codename("blah", None).is_none());
236    }
237
238    #[test]
239    fn test_resolve_ubuntu() {
240        assert_eq!("trusty", resolve_release_codename("trusty", None).unwrap());
241        assert_eq!(
242            "trusty",
243            resolve_release_codename("ubuntu/trusty", None).unwrap()
244        );
245        assert!(resolve_release_codename("ubuntu/lts", None).is_some())
246    }
247
248    #[test]
249    fn test_resolve_ubuntu_esm() {
250        assert!(resolve_release_codename("ubuntu/esm", None).is_some())
251    }
252
253    #[test]
254    fn test_debhelper_versions() {
255        assert!(super::debhelper_versions.get("sid").is_some());
256        assert!(super::debhelper_versions.get("trixie").is_some());
257    }
258}