use std::collections::{BTreeMap, BTreeSet};
use crate::model::{Inventory, Package};
pub fn export(inventory: &Inventory, workload_key: Option<&str>) -> String {
let packages = inventory.packages_for_workload(workload_key);
let (all_rpms, arch_rpms) = collect_rpms(&packages);
let meta = workload_key.and_then(|k| inventory.inventory.workloads.get(k));
let name = meta
.and_then(|m| m.name.as_deref())
.map(|n| n.to_string())
.unwrap_or_else(|| match workload_key {
Some(k) => format!("{}-{k}", inventory.inventory.name),
None => inventory.inventory.name.clone(),
});
let description = meta
.and_then(|m| m.description.as_deref())
.unwrap_or(&inventory.inventory.description);
let maintainer = meta
.and_then(|m| m.maintainer.as_deref())
.unwrap_or(&inventory.inventory.maintainer);
let labels = meta
.and_then(|m| m.labels.as_ref())
.unwrap_or(&inventory.inventory.labels);
let mut out = String::new();
out.push_str("document: feedback-pipeline-workload\n");
out.push_str("version: 1\n");
out.push_str("data:\n");
out.push_str(&format!(" name: {name}\n"));
out.push_str(&format!(" description: {description}\n"));
out.push_str(&format!(" maintainer: {maintainer}\n"));
if !all_rpms.is_empty() {
out.push_str(" packages:\n");
for rpm in &all_rpms {
out.push_str(&format!(" - {rpm}\n"));
}
}
if !arch_rpms.is_empty() {
out.push_str(" arch_packages:\n");
for (arch, rpms) in &arch_rpms {
out.push_str(&format!(" {arch}:\n"));
for rpm in rpms {
out.push_str(&format!(" - {rpm}\n"));
}
}
}
if !labels.is_empty() {
out.push_str(" labels:\n");
for label in labels {
out.push_str(&format!(" - {label}\n"));
}
}
out
}
fn collect_rpms(packages: &[&Package]) -> (BTreeSet<String>, BTreeMap<String, BTreeSet<String>>) {
let mut all_rpms = BTreeSet::new();
let mut arch_rpms: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
for pkg in packages {
if let Some(ref rpms) = pkg.rpms {
for rpm in rpms {
all_rpms.insert(rpm.clone());
}
} else {
all_rpms.insert(pkg.name.clone());
}
if let Some(ref arch) = pkg.arch_rpms {
for (a, rpms) in arch {
for rpm in rpms {
arch_rpms.entry(a.clone()).or_default().insert(rpm.clone());
}
}
}
}
for rpms in arch_rpms.values() {
for rpm in rpms {
all_rpms.remove(rpm);
}
}
(all_rpms, arch_rpms)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{InventoryMeta, Package, WorkloadMeta};
fn make_inventory() -> Inventory {
Inventory {
inventory: InventoryMeta {
name: "test-packages".to_string(),
description: "Test packages".to_string(),
maintainer: "test-sig".to_string(),
labels: vec!["eln-extras".to_string()],
workloads: BTreeMap::from([
(
"hyperscale".to_string(),
WorkloadMeta {
packages: vec!["fish".to_string(), "systemd".to_string()],
..Default::default()
},
),
(
"epel".to_string(),
WorkloadMeta {
packages: vec!["neovim".to_string()],
..Default::default()
},
),
]),
private_fields: vec!["poc".to_string()],
},
package: vec![
Package {
name: "fish".to_string(),
poc: Some("Team <t@e.com>".to_string()),
reason: None,
team: None,
task: None,
rpms: Some(vec!["fish".to_string()]),
arch_rpms: None,
track: None,
repology_name: None,
distros: None,
file_issue: None,
priority: None,
retired_on: None,
unshipped: None,
archived_builds: None,
},
Package {
name: "systemd".to_string(),
poc: Some("Infra <i@e.com>".to_string()),
reason: None,
team: None,
task: None,
rpms: Some(vec!["systemd-networkd".to_string()]),
arch_rpms: Some(BTreeMap::from([
(
"x86_64".to_string(),
vec!["systemd-boot-unsigned".to_string()],
),
(
"aarch64".to_string(),
vec!["systemd-boot-unsigned".to_string()],
),
])),
track: None,
repology_name: None,
distros: None,
file_issue: None,
priority: None,
retired_on: None,
unshipped: None,
archived_builds: None,
},
Package {
name: "neovim".to_string(),
poc: None,
reason: None,
team: None,
task: None,
rpms: None,
arch_rpms: None,
track: None,
repology_name: None,
distros: None,
file_issue: None,
priority: None,
retired_on: None,
unshipped: None,
archived_builds: None,
},
],
}
}
#[test]
fn export_all() {
let inv = make_inventory();
let yaml = export(&inv, None);
assert!(yaml.contains("document: feedback-pipeline-workload"));
assert!(yaml.contains("name: test-packages"));
assert!(yaml.contains(" - fish"));
assert!(yaml.contains(" - systemd-networkd"));
assert!(yaml.contains(" - neovim"));
assert!(yaml.contains(" x86_64:"));
assert!(yaml.contains(" - systemd-boot-unsigned"));
assert!(yaml.contains(" - eln-extras"));
assert!(!yaml.contains("Team <t@e.com>"));
}
#[test]
fn export_filtered_by_workload() {
let inv = make_inventory();
let yaml = export(&inv, Some("hyperscale"));
assert!(yaml.contains(" - fish"));
assert!(yaml.contains(" - systemd-networkd"));
assert!(!yaml.contains("neovim"));
assert!(yaml.contains("name: test-packages-hyperscale"));
}
#[test]
fn export_with_workload_meta() {
let mut inv = make_inventory();
let wl = inv.inventory.workloads.get_mut("hyperscale").unwrap();
wl.name = Some("hs-packages".to_string());
wl.description = Some("Hyperscale SIG".to_string());
wl.labels = Some(vec!["hs-label".to_string()]);
let yaml = export(&inv, Some("hyperscale"));
assert!(yaml.contains("name: hs-packages"));
assert!(yaml.contains("description: Hyperscale SIG"));
assert!(yaml.contains("maintainer: test-sig"));
assert!(yaml.contains(" - hs-label"));
assert!(!yaml.contains("eln-extras"));
}
#[test]
fn export_default_rpm_name() {
let inv = make_inventory();
let yaml = export(&inv, Some("epel"));
assert!(yaml.contains(" - neovim"));
}
#[test]
fn arch_rpms_not_in_general_list() {
let inv = make_inventory();
let yaml = export(&inv, Some("hyperscale"));
let packages_section = yaml
.split(" packages:\n")
.nth(1)
.unwrap()
.split(" arch_packages:")
.next()
.unwrap();
assert!(!packages_section.contains("systemd-boot-unsigned"));
}
}