distro_info: fully parse distro info
This commit is contained in:
@@ -4,6 +4,23 @@ use serde::Deserialize;
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
/// Information about a specific distribution series
|
||||||
|
pub struct SeriesInformation {
|
||||||
|
/// Distribution series
|
||||||
|
pub series: String,
|
||||||
|
/// Codename, i.e. full name of series
|
||||||
|
pub codename: String,
|
||||||
|
/// Series version as numbers
|
||||||
|
pub version: Option<String>,
|
||||||
|
/// Series creation date
|
||||||
|
pub created: NaiveDate,
|
||||||
|
/// Series release date
|
||||||
|
pub release: Option<NaiveDate>,
|
||||||
|
/// Series end-of-life date
|
||||||
|
pub eol: Option<NaiveDate>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct SeriesInfo {
|
struct SeriesInfo {
|
||||||
local: String,
|
local: String,
|
||||||
@@ -28,7 +45,7 @@ lazy_static! {
|
|||||||
static ref DATA: Data = serde_yaml::from_str(DATA_YAML).unwrap();
|
static ref DATA: Data = serde_yaml::from_str(DATA_YAML).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_series_csv(content: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
fn parse_series_csv(content: &str) -> Result<Vec<SeriesInformation>, Box<dyn Error>> {
|
||||||
let mut rdr = csv::ReaderBuilder::new()
|
let mut rdr = csv::ReaderBuilder::new()
|
||||||
.flexible(true)
|
.flexible(true)
|
||||||
.from_reader(content.as_bytes());
|
.from_reader(content.as_bytes());
|
||||||
@@ -38,29 +55,63 @@ fn parse_series_csv(content: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
|||||||
.iter()
|
.iter()
|
||||||
.position(|h| h == "series")
|
.position(|h| h == "series")
|
||||||
.ok_or("Column 'series' not found")?;
|
.ok_or("Column 'series' not found")?;
|
||||||
|
let codename_idx = headers
|
||||||
|
.iter()
|
||||||
|
.position(|h| h == "codename")
|
||||||
|
.ok_or("Column 'codename' not found")?;
|
||||||
|
let version_idx = headers
|
||||||
|
.iter()
|
||||||
|
.position(|h| h == "version")
|
||||||
|
.ok_or("Column 'version' not found")?;
|
||||||
let created_idx = headers
|
let created_idx = headers
|
||||||
.iter()
|
.iter()
|
||||||
.position(|h| h == "created")
|
.position(|h| h == "created")
|
||||||
.ok_or("Column 'created' not found")?;
|
.ok_or("Column 'created' not found")?;
|
||||||
|
let release_idx = headers
|
||||||
|
.iter()
|
||||||
|
.position(|h| h == "release")
|
||||||
|
.ok_or("Column 'release' not found")?;
|
||||||
|
let eol_idx = headers
|
||||||
|
.iter()
|
||||||
|
.position(|h| h == "eol")
|
||||||
|
.ok_or("Column 'eol' not found")?;
|
||||||
|
|
||||||
|
let mut series_info_list = Vec::new();
|
||||||
|
|
||||||
let mut entries = Vec::new();
|
|
||||||
for result in rdr.records() {
|
for result in rdr.records() {
|
||||||
let record = result?;
|
let record = result?;
|
||||||
if let (Some(s), Some(c)) = (record.get(series_idx), record.get(created_idx))
|
let series = record.get(series_idx).unwrap().to_string();
|
||||||
&& let Ok(date) = NaiveDate::parse_from_str(c, "%Y-%m-%d")
|
let codename = record.get(codename_idx).unwrap().to_string();
|
||||||
{
|
let version = record.get(version_idx).map(|s| s.to_string());
|
||||||
entries.push((s.to_string(), date));
|
let created = record
|
||||||
}
|
.get(created_idx)
|
||||||
|
.map(|date_str| NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap())
|
||||||
|
.unwrap();
|
||||||
|
let release = record
|
||||||
|
.get(release_idx)
|
||||||
|
.map(|date_str| NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap());
|
||||||
|
let eol = record
|
||||||
|
.get(eol_idx)
|
||||||
|
.map(|date_str| NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap());
|
||||||
|
|
||||||
|
series_info_list.push(SeriesInformation {
|
||||||
|
series,
|
||||||
|
codename,
|
||||||
|
version,
|
||||||
|
created,
|
||||||
|
release,
|
||||||
|
eol,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by date descending (newest first)
|
// Revert to sort by most recent
|
||||||
entries.sort_by(|a, b| b.1.cmp(&a.1));
|
series_info_list.reverse();
|
||||||
|
|
||||||
Ok(entries.into_iter().map(|(s, _)| s).collect())
|
Ok(series_info_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get time-ordered list of series for a distribution, development series first
|
/// Get time-ordered list of series information for a distribution, development series first
|
||||||
pub async fn get_ordered_series(dist: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
pub async fn get_ordered_series(dist: &str) -> Result<Vec<SeriesInformation>, Box<dyn Error>> {
|
||||||
let series_info = &DATA.dist.get(dist).unwrap().series;
|
let series_info = &DATA.dist.get(dist).unwrap().series;
|
||||||
let content = if Path::new(series_info.local.as_str()).exists() {
|
let content = if Path::new(series_info.local.as_str()).exists() {
|
||||||
std::fs::read_to_string(format!("/usr/share/distro-info/{dist}.csv"))?
|
std::fs::read_to_string(format!("/usr/share/distro-info/{dist}.csv"))?
|
||||||
@@ -71,22 +122,48 @@ pub async fn get_ordered_series(dist: &str) -> Result<Vec<String>, Box<dyn Error
|
|||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut series = parse_series_csv(&content)?;
|
let series_info_list = parse_series_csv(&content)?;
|
||||||
|
Ok(series_info_list)
|
||||||
|
}
|
||||||
|
|
||||||
// For Debian, ensure 'sid' is first if it's not
|
/// Get time-ordered list of series names for a distribution, development series first
|
||||||
// We want to try 'sid' (unstable) first for Debian.
|
pub async fn get_ordered_series_name(dist: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
||||||
if dist == "debian" {
|
let series = get_ordered_series(dist).await?;
|
||||||
series.retain(|s| s != "sid");
|
Ok(series.iter().map(|info| info.series.clone()).collect())
|
||||||
series.insert(0, "sid".to_string());
|
}
|
||||||
|
|
||||||
|
/// Get the latest released series for a dist (excluding future releases and special cases like sid)
|
||||||
|
pub async fn get_latest_released_series(dist: &str) -> Result<String, Box<dyn Error>> {
|
||||||
|
let series_info_list = get_ordered_series(dist).await?;
|
||||||
|
|
||||||
|
let today = chrono::Local::now().date_naive();
|
||||||
|
let mut released_series = Vec::new();
|
||||||
|
|
||||||
|
for series_info in series_info_list {
|
||||||
|
// Skip 'sid' and series without release dates or with future release dates
|
||||||
|
if series_info.series != "sid"
|
||||||
|
&& series_info.series != "experimental"
|
||||||
|
&& series_info.release.is_some()
|
||||||
|
&& series_info.release.unwrap() <= today
|
||||||
|
{
|
||||||
|
released_series.push(series_info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(series)
|
// Sort by release date descending (newest first)
|
||||||
|
released_series.sort_by(|a, b| b.release.cmp(&a.release));
|
||||||
|
|
||||||
|
if let Some(latest) = released_series.first() {
|
||||||
|
Ok(latest.series.clone())
|
||||||
|
} else {
|
||||||
|
Err("No released series found".into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain the distribution (eg. debian, ubuntu) from a distribution series (eg. noble, bookworm)
|
/// Obtain the distribution (eg. debian, ubuntu) from a distribution series (eg. noble, bookworm)
|
||||||
pub async fn get_dist_from_series(series: &str) -> Result<String, Box<dyn Error>> {
|
pub async fn get_dist_from_series(series: &str) -> Result<String, Box<dyn Error>> {
|
||||||
for dist in DATA.dist.keys() {
|
for dist in DATA.dist.keys() {
|
||||||
if get_ordered_series(dist)
|
if get_ordered_series_name(dist)
|
||||||
.await?
|
.await?
|
||||||
.contains(&series.to_string())
|
.contains(&series.to_string())
|
||||||
{
|
{
|
||||||
@@ -135,13 +212,11 @@ pub async fn get_keyring_url(series: &str) -> Result<String, Box<dyn Error>> {
|
|||||||
|
|
||||||
// For Debian, we need the series number to form the keyring URL
|
// For Debian, we need the series number to form the keyring URL
|
||||||
if dist == "debian" {
|
if dist == "debian" {
|
||||||
// Special case for 'sid' - use the latest version
|
// Special case for 'sid' - use the latest released version
|
||||||
if series == "sid" {
|
if series == "sid" || series == "experimental" {
|
||||||
let latest_series = get_ordered_series("debian").await?;
|
let latest_released = get_latest_released_series("debian").await?;
|
||||||
// Get the first non-sid series (which should be the latest stable)
|
let series_num = get_debian_series_number(&latest_released).await?.unwrap();
|
||||||
let latest_stable = latest_series.iter().find(|&s| s != "sid").unwrap();
|
// Replace {series_num} placeholder with the latest released series number
|
||||||
let series_num = get_debian_series_number(latest_stable).await?.unwrap();
|
|
||||||
// Replace {series_num} placeholder with the latest stable series number
|
|
||||||
Ok(dist_data
|
Ok(dist_data
|
||||||
.archive_keyring
|
.archive_keyring
|
||||||
.replace("{series_num}", &series_num))
|
.replace("{series_num}", &series_num))
|
||||||
@@ -237,14 +312,14 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_debian_series() {
|
async fn test_get_debian_series() {
|
||||||
let series = get_ordered_series("debian").await.unwrap();
|
let series = get_ordered_series_name("debian").await.unwrap();
|
||||||
assert!(series.contains(&"sid".to_string()));
|
assert!(series.contains(&"sid".to_string()));
|
||||||
assert!(series.contains(&"bookworm".to_string()));
|
assert!(series.contains(&"bookworm".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_ubuntu_series() {
|
async fn test_get_ubuntu_series() {
|
||||||
let series = get_ordered_series("ubuntu").await.unwrap();
|
let series = get_ordered_series_name("ubuntu").await.unwrap();
|
||||||
assert!(series.contains(&"noble".to_string()));
|
assert!(series.contains(&"noble".to_string()));
|
||||||
assert!(series.contains(&"jammy".to_string()));
|
assert!(series.contains(&"jammy".to_string()));
|
||||||
}
|
}
|
||||||
@@ -273,13 +348,26 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_keyring_url_sid() {
|
async fn test_get_keyring_url_sid() {
|
||||||
// Test that 'sid' uses the latest stable version for keyring URL
|
// Test that 'sid' uses the latest released version for keyring URL
|
||||||
let sid_keyring = get_keyring_url("sid").await.unwrap();
|
let sid_keyring = get_keyring_url("sid").await.unwrap();
|
||||||
let latest_series = get_ordered_series("debian").await.unwrap();
|
let latest_released = get_latest_released_series("debian").await.unwrap();
|
||||||
let latest_stable = latest_series.iter().find(|&s| s != "sid").unwrap();
|
let latest_keyring = get_keyring_url(&latest_released).await.unwrap();
|
||||||
let latest_keyring = get_keyring_url(latest_stable).await.unwrap();
|
|
||||||
|
|
||||||
// The keyring URL for 'sid' should be the same as the latest stable version
|
// The keyring URL for 'sid' should be the same as the latest released version
|
||||||
assert_eq!(sid_keyring, latest_keyring);
|
assert_eq!(sid_keyring, latest_keyring);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_latest_released_debian_series() {
|
||||||
|
// Test that we get a valid released series
|
||||||
|
let latest_released = get_latest_released_series("debian").await.unwrap();
|
||||||
|
|
||||||
|
// Should not be 'sid' or 'experimental'
|
||||||
|
assert_ne!(latest_released, "sid");
|
||||||
|
assert_ne!(latest_released, "experimental");
|
||||||
|
|
||||||
|
// Should have a version number
|
||||||
|
let version = get_debian_series_number(&latest_released).await.unwrap();
|
||||||
|
assert!(version.is_some());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ async fn find_package(
|
|||||||
version: Option<&str>,
|
version: Option<&str>,
|
||||||
progress: ProgressCallback<'_>,
|
progress: ProgressCallback<'_>,
|
||||||
) -> Result<PackageInfo, Box<dyn Error>> {
|
) -> Result<PackageInfo, Box<dyn Error>> {
|
||||||
let series_list = crate::distro_info::get_ordered_series(dist).await?;
|
let series_list = crate::distro_info::get_ordered_series_name(dist).await?;
|
||||||
|
|
||||||
for (i, series) in series_list.iter().enumerate() {
|
for (i, series) in series_list.iter().enumerate() {
|
||||||
if let Some(cb) = progress {
|
if let Some(cb) = progress {
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ pub async fn pull(
|
|||||||
|
|
||||||
// Depending on target series, we pick target branch; if latest series is specified,
|
// Depending on target series, we pick target branch; if latest series is specified,
|
||||||
// we target the development branch, i.e. the default branch
|
// we target the development branch, i.e. the default branch
|
||||||
let branch_name = if crate::distro_info::get_ordered_series(package_info.dist.as_str())
|
let branch_name = if crate::distro_info::get_ordered_series_name(package_info.dist.as_str())
|
||||||
.await?[0]
|
.await?[0]
|
||||||
!= *series
|
!= *series
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user