use crate::models::{DatasourceId, PackageType};
use std::fs;
use std::path::Path;
use crate::parser_warn as warn;
use crate::models::PackageData;
use super::PackageParser;
const PACKAGE_TYPE: PackageType = PackageType::Rpm;
fn default_package_data() -> PackageData {
PackageData {
package_type: Some(PACKAGE_TYPE),
namespace: Some("mariner".to_string()),
datasource_id: Some(DatasourceId::RpmMarinerManifest),
..Default::default()
}
}
pub struct RpmMarinerManifestParser;
impl PackageParser for RpmMarinerManifestParser {
const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
fn is_match(path: &Path) -> bool {
path.to_str()
.is_some_and(|p| p.ends_with("/var/lib/rpmmanifest/container-manifest-2"))
}
fn extract_packages(path: &Path) -> Vec<PackageData> {
let content = match fs::read_to_string(path) {
Ok(c) => c,
Err(e) => {
warn!("Failed to read RPM Mariner manifest {:?}: {}", path, e);
return vec![default_package_data()];
}
};
parse_rpm_mariner_manifest(&content)
}
}
pub(crate) fn parse_rpm_mariner_manifest(content: &str) -> Vec<PackageData> {
let mut packages = Vec::new();
for line in content.lines() {
let line = line.trim_matches(|c: char| c.is_whitespace() && c != '\t');
if line.is_empty() {
continue;
}
let parts: Vec<&str> = line.split('\t').collect();
if parts.len() < 10 {
warn!(
"Invalid RPM Mariner manifest line (expected 10 fields): {}",
line
);
continue;
}
let name = parts[0];
let version = parts[1];
let arch = parts[7];
let filename = parts[9];
let qualifiers = if arch.is_empty() {
None
} else {
let mut quals = std::collections::HashMap::new();
quals.insert("arch".to_string(), arch.to_string());
Some(quals)
};
let extra_data = if filename.is_empty() {
None
} else {
let mut extra = std::collections::HashMap::new();
extra.insert(
"filename".to_string(),
serde_json::Value::String(filename.to_string()),
);
Some(extra)
};
packages.push(PackageData {
package_type: Some(PACKAGE_TYPE),
namespace: Some("mariner".to_string()),
name: if name.is_empty() {
None
} else {
Some(name.to_string())
},
version: if version.is_empty() {
None
} else {
Some(version.to_string())
},
qualifiers,
datasource_id: Some(DatasourceId::RpmMarinerManifest),
extra_data,
..Default::default()
});
}
if packages.is_empty() {
packages.push(default_package_data());
}
packages
}
crate::register_parser!(
"RPM Mariner distroless package manifest",
&["*var/lib/rpmmanifest/container-manifest-2"],
"rpm",
"",
Some("https://github.com/microsoft/marinara/"),
);