package_info: refactor into distro_info and package_info split, yaml data
Some checks failed
CI / build (push) Failing after 9m3s
Some checks failed
CI / build (push) Failing after 9m3s
This commit is contained in:
185
src/distro_info.rs
Normal file
185
src/distro_info.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
use chrono::NaiveDate;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Deserialize;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SeriesInfo {
|
||||
local: String,
|
||||
network: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct DistData {
|
||||
base_url: String,
|
||||
pockets: Vec<String>,
|
||||
series: SeriesInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Data {
|
||||
dist: std::collections::HashMap<String, DistData>,
|
||||
}
|
||||
|
||||
const DATA_YAML: &str = include_str!("../distro_info.yml");
|
||||
lazy_static! {
|
||||
static ref DATA: Data = serde_yaml::from_str(DATA_YAML).unwrap();
|
||||
}
|
||||
|
||||
fn parse_series_csv(content: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
let mut rdr = csv::ReaderBuilder::new()
|
||||
.flexible(true)
|
||||
.from_reader(content.as_bytes());
|
||||
|
||||
let headers = rdr.headers()?.clone();
|
||||
let series_idx = headers
|
||||
.iter()
|
||||
.position(|h| h == "series")
|
||||
.ok_or("Column 'series' not found")?;
|
||||
let created_idx = headers
|
||||
.iter()
|
||||
.position(|h| h == "created")
|
||||
.ok_or("Column 'created' not found")?;
|
||||
|
||||
let mut entries = Vec::new();
|
||||
for result in rdr.records() {
|
||||
let record = result?;
|
||||
if let (Some(s), Some(c)) = (record.get(series_idx), record.get(created_idx))
|
||||
&& let Ok(date) = NaiveDate::parse_from_str(c, "%Y-%m-%d")
|
||||
{
|
||||
entries.push((s.to_string(), date));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by date descending (newest first)
|
||||
entries.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
|
||||
Ok(entries.into_iter().map(|(s, _)| s).collect())
|
||||
}
|
||||
|
||||
/// Get time-ordered list of series for a distribution, development series first
|
||||
pub async fn get_ordered_series(dist: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
let series_info = &DATA.dist.get(dist).unwrap().series;
|
||||
let content = if Path::new(series_info.local.as_str()).exists() {
|
||||
std::fs::read_to_string(format!("/usr/share/distro-info/{dist}.csv"))?
|
||||
} else {
|
||||
reqwest::get(series_info.network.as_str())
|
||||
.await?
|
||||
.text()
|
||||
.await?
|
||||
};
|
||||
|
||||
let mut series = parse_series_csv(&content)?;
|
||||
|
||||
// For Debian, ensure 'sid' is first if it's not
|
||||
// We want to try 'sid' (unstable) first for Debian.
|
||||
if dist == "debian" {
|
||||
series.retain(|s| s != "sid");
|
||||
series.insert(0, "sid".to_string());
|
||||
}
|
||||
|
||||
Ok(series)
|
||||
}
|
||||
|
||||
/// 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>> {
|
||||
for dist in DATA.dist.keys() {
|
||||
if get_ordered_series(dist)
|
||||
.await?
|
||||
.contains(&series.to_string())
|
||||
{
|
||||
return Ok(dist.to_string());
|
||||
}
|
||||
}
|
||||
Err(format!("Unknown series: {}", series).into())
|
||||
}
|
||||
|
||||
/// Get the package pockets available for a given distribution
|
||||
///
|
||||
/// Example: get_dist_pockets(ubuntu) => ["proposed", "updates", ""]
|
||||
pub fn get_dist_pockets(dist: &str) -> Vec<String> {
|
||||
let mut pockets = DATA.dist.get(dist).unwrap().pockets.clone();
|
||||
|
||||
// Explicitely add 'main' pocket, which is just the empty string
|
||||
pockets.push("".to_string());
|
||||
|
||||
pockets
|
||||
}
|
||||
|
||||
/// Get the sources URL for a distribution, series, pocket, and component
|
||||
pub fn get_sources_url(base_url: &str, series: &str, pocket: &str, component: &str) -> String {
|
||||
let pocket_full = if pocket.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("-{}", pocket)
|
||||
};
|
||||
format!("{base_url}/dists/{series}{pocket_full}/{component}/source/Sources.gz")
|
||||
}
|
||||
|
||||
/// Get the archive base URL for a distribution
|
||||
///
|
||||
/// Example: ubuntu => http://archive.ubuntu.com/ubuntu
|
||||
pub fn get_base_url(dist: &str) -> String {
|
||||
DATA.dist.get(dist).unwrap().base_url.clone()
|
||||
}
|
||||
|
||||
/// Obtain the URL for the 'Release' file of a distribution series
|
||||
fn get_release_url(base_url: &str, series: &str, pocket: &str) -> String {
|
||||
let pocket_full = if pocket.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("-{}", pocket)
|
||||
};
|
||||
format!("{base_url}/dists/{series}{pocket_full}/Release")
|
||||
}
|
||||
|
||||
/// Obtain the components of a distribution series by parsing the 'Release' file
|
||||
pub async fn get_components(
|
||||
base_url: &str,
|
||||
series: &str,
|
||||
pocket: &str,
|
||||
) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
let url = get_release_url(base_url, series, pocket);
|
||||
log::debug!("Fetching Release file from: {}", url);
|
||||
|
||||
let content = reqwest::get(&url).await?.text().await?;
|
||||
|
||||
for line in content.lines() {
|
||||
if line.starts_with("Components:")
|
||||
&& let Some((_, components)) = line.split_once(':')
|
||||
{
|
||||
return Ok(components
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect());
|
||||
}
|
||||
}
|
||||
|
||||
Err("Components not found.".into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_debian_series() {
|
||||
let series = get_ordered_series("debian").await.unwrap();
|
||||
assert!(series.contains(&"sid".to_string()));
|
||||
assert!(series.contains(&"bookworm".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_ubuntu_series() {
|
||||
let series = get_ordered_series("ubuntu").await.unwrap();
|
||||
assert!(series.contains(&"noble".to_string()));
|
||||
assert!(series.contains(&"jammy".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_dist_from_series() {
|
||||
assert_eq!(get_dist_from_series("sid").await.unwrap(), "debian");
|
||||
assert_eq!(get_dist_from_series("noble").await.unwrap(), "ubuntu");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user