provenant/parsers/
rpm_mariner_manifest.rs1use crate::models::{DatasourceId, PackageType};
20use std::fs;
21use std::path::Path;
22
23use crate::parser_warn as warn;
24
25use crate::models::PackageData;
26
27use super::PackageParser;
28
29const PACKAGE_TYPE: PackageType = PackageType::Rpm;
30
31fn default_package_data() -> PackageData {
32 PackageData {
33 package_type: Some(PACKAGE_TYPE),
34 namespace: Some("mariner".to_string()),
35 datasource_id: Some(DatasourceId::RpmMarinerManifest),
36 ..Default::default()
37 }
38}
39
40pub struct RpmMarinerManifestParser;
42
43impl PackageParser for RpmMarinerManifestParser {
44 const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
45
46 fn is_match(path: &Path) -> bool {
47 path.to_str()
48 .is_some_and(|p| p.ends_with("/var/lib/rpmmanifest/container-manifest-2"))
49 }
50
51 fn extract_packages(path: &Path) -> Vec<PackageData> {
52 let content = match fs::read_to_string(path) {
53 Ok(c) => c,
54 Err(e) => {
55 warn!("Failed to read RPM Mariner manifest {:?}: {}", path, e);
56 return vec![default_package_data()];
57 }
58 };
59
60 parse_rpm_mariner_manifest(&content)
61 }
62}
63
64pub(crate) fn parse_rpm_mariner_manifest(content: &str) -> Vec<PackageData> {
65 let mut packages = Vec::new();
66
67 for line in content.lines() {
68 let line = line.trim_matches(|c: char| c.is_whitespace() && c != '\t');
70 if line.is_empty() {
71 continue;
72 }
73
74 let parts: Vec<&str> = line.split('\t').collect();
76
77 if parts.len() < 10 {
82 warn!(
83 "Invalid RPM Mariner manifest line (expected 10 fields): {}",
84 line
85 );
86 continue;
87 }
88
89 let name = parts[0];
90 let version = parts[1];
91 let arch = parts[7];
92 let filename = parts[9];
93
94 let qualifiers = if arch.is_empty() {
95 None
96 } else {
97 let mut quals = std::collections::HashMap::new();
98 quals.insert("arch".to_string(), arch.to_string());
99 Some(quals)
100 };
101
102 let extra_data = if filename.is_empty() {
103 None
104 } else {
105 let mut extra = std::collections::HashMap::new();
106 extra.insert(
107 "filename".to_string(),
108 serde_json::Value::String(filename.to_string()),
109 );
110 Some(extra)
111 };
112
113 packages.push(PackageData {
114 package_type: Some(PACKAGE_TYPE),
115 namespace: Some("mariner".to_string()),
116 name: if name.is_empty() {
117 None
118 } else {
119 Some(name.to_string())
120 },
121 version: if version.is_empty() {
122 None
123 } else {
124 Some(version.to_string())
125 },
126 qualifiers,
127 datasource_id: Some(DatasourceId::RpmMarinerManifest),
128 extra_data,
129 ..Default::default()
130 });
131 }
132
133 if packages.is_empty() {
134 packages.push(default_package_data());
135 }
136
137 packages
138}
139
140crate::register_parser!(
141 "RPM Mariner distroless package manifest",
142 &["*var/lib/rpmmanifest/container-manifest-2"],
143 "rpm",
144 "",
145 Some("https://github.com/microsoft/marinara/"),
146);