Refactor package FMRI handling by introducing Fmri struct, update dependencies, and adjust repository and action modules for structured FMRI data processing

Signed-off-by: Till Wegmueller <toasterson@gmail.com>
This commit is contained in:
Till Wegmueller 2025-07-22 14:10:37 +02:00
parent 2e69b277ed
commit 5d987ca0cb
No known key found for this signature in database
9 changed files with 1101 additions and 49 deletions

8
Cargo.lock generated
View file

@ -722,6 +722,7 @@ dependencies = [
"pest", "pest",
"pest_derive", "pest_derive",
"regex", "regex",
"semver",
"serde", "serde",
"serde_json", "serde_json",
"sha2 0.9.9", "sha2 0.9.9",
@ -1348,9 +1349,12 @@ dependencies = [
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.17" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde" name = "serde"

View file

@ -28,6 +28,7 @@ pest_derive = "2.1.0"
strum = { version = "0.24.1", features = ["derive"] } strum = { version = "0.24.1", features = ["derive"] }
serde = { version = "1.0.207", features = ["derive"] } serde = { version = "1.0.207", features = ["derive"] }
serde_json = "1.0.124" serde_json = "1.0.124"
diff-struct = "0.5.3"
flate2 = "1.0.28" flate2 = "1.0.28"
lz4 = "1.24.0" lz4 = "1.24.0"
semver = { version = "1.0.20", features = ["serde"] }
diff-struct = "0.5.3"

View file

@ -6,6 +6,7 @@
// Source https://docs.oracle.com/cd/E23824_01/html/E21796/pkg-5.html // Source https://docs.oracle.com/cd/E23824_01/html/E21796/pkg-5.html
use crate::digest::Digest; use crate::digest::Digest;
use crate::fmri::Fmri;
use crate::payload::{Payload, PayloadError}; use crate::payload::{Payload, PayloadError};
use pest::Parser; use pest::Parser;
use pest_derive::Parser; use pest_derive::Parser;
@ -267,9 +268,9 @@ pub enum FileError {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
))] ))]
pub struct Dependency { pub struct Dependency {
pub fmri: String, //TODO make FMRI pub fmri: Option<Fmri>, // FMRI of the dependency
pub dependency_type: String, //TODO make enum pub dependency_type: String, //TODO make enum
pub predicate: String, //TODO make FMRI pub predicate: Option<Fmri>, // FMRI for conditional dependencies
pub root_image: String, //TODO make boolean pub root_image: String, //TODO make boolean
pub optional: Vec<Property>, pub optional: Vec<Property>,
pub facets: HashMap<String, Facet>, pub facets: HashMap<String, Facet>,
@ -288,9 +289,25 @@ impl From<Action> for Dependency {
} }
for prop in props { for prop in props {
match prop.key.as_str() { match prop.key.as_str() {
"fmri" => dep.fmri = prop.value, "fmri" => {
match Fmri::parse(&prop.value) {
Ok(fmri) => dep.fmri = Some(fmri),
Err(err) => {
eprintln!("Error parsing FMRI '{}': {}", prop.value, err);
dep.fmri = None;
}
}
},
"type" => dep.dependency_type = prop.value, "type" => dep.dependency_type = prop.value,
"predicate" => dep.predicate = prop.value, "predicate" => {
match Fmri::parse(&prop.value) {
Ok(fmri) => dep.predicate = Some(fmri),
Err(err) => {
eprintln!("Error parsing predicate FMRI '{}': {}", prop.value, err);
dep.predicate = None;
}
}
},
"root-image" => dep.root_image = prop.value, "root-image" => dep.root_image = prop.value,
_ => { _ => {
if is_facet(prop.key.clone()) { if is_facet(prop.key.clone()) {

1001
libips/src/fmri.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@
#[allow(clippy::result_large_err)] #[allow(clippy::result_large_err)]
pub mod actions; pub mod actions;
pub mod digest; pub mod digest;
pub mod fmri;
pub mod payload; pub mod payload;
pub mod image; pub mod image;
pub mod repository; pub mod repository;
@ -16,6 +17,7 @@ mod tests {
use crate::actions::Attr; use crate::actions::Attr;
use crate::actions::{Dependency, Dir, Facet, File, Link, Manifest, Property}; use crate::actions::{Dependency, Dir, Facet, File, Link, Manifest, Property};
use crate::digest::{Digest, DigestAlgorithm, DigestSource}; use crate::digest::{Digest, DigestAlgorithm, DigestSource};
use crate::fmri::Fmri;
use crate::payload::Payload; use crate::payload::Payload;
use std::collections::HashMap; use std::collections::HashMap;
@ -980,18 +982,17 @@ depend facet.version-lock.system/mozilla-nss=true fmri=system/mozilla-nss@3.51.1
let test_results = vec![ let test_results = vec![
Dependency { Dependency {
fmri: "pkg:/system/library@0.5.11-2020.0.1.19563".to_string(), fmri: Some(Fmri::parse("pkg:/system/library@0.5.11-2020.0.1.19563").unwrap()),
dependency_type: "require".to_string(), dependency_type: "require".to_string(),
..Dependency::default() ..Dependency::default()
}, },
Dependency { Dependency {
fmri: "pkg:/system/file-system/nfs@0.5.11,5.11-2020.0.1.19951".to_string(), fmri: Some(Fmri::parse("pkg:/system/file-system/nfs@0.5.11,5.11-2020.0.1.19951").unwrap()),
dependency_type: "incorporate".to_string(), dependency_type: "incorporate".to_string(),
..Dependency::default() ..Dependency::default()
}, },
Dependency { Dependency {
fmri: "pkg:/system/data/hardware-registry@2020.2.22,5.11-2020.0.1.19951" fmri: Some(Fmri::parse("pkg:/system/data/hardware-registry@2020.2.22,5.11-2020.0.1.19951").unwrap()),
.to_string(),
dependency_type: "incorporate".to_string(), dependency_type: "incorporate".to_string(),
facets: hashmap! { facets: hashmap! {
"version-lock.system/data/hardware-registry".to_string() => Facet{ "version-lock.system/data/hardware-registry".to_string() => Facet{
@ -1002,7 +1003,7 @@ depend facet.version-lock.system/mozilla-nss=true fmri=system/mozilla-nss@3.51.1
..Dependency::default() ..Dependency::default()
}, },
Dependency { Dependency {
fmri: "xvm@0.5.11-2015.0.2.0".to_string(), fmri: Some(Fmri::parse("xvm@0.5.11-2015.0.2.0").unwrap()),
dependency_type: "incorporate".to_string(), dependency_type: "incorporate".to_string(),
facets: hashmap! { facets: hashmap! {
"version-lock.xvm".to_string() => Facet{ "version-lock.xvm".to_string() => Facet{
@ -1013,7 +1014,7 @@ depend facet.version-lock.system/mozilla-nss=true fmri=system/mozilla-nss@3.51.1
..Dependency::default() ..Dependency::default()
}, },
Dependency { Dependency {
fmri: "system/mozilla-nss@3.51.1-2020.0.1.0".to_string(), fmri: Some(Fmri::parse("system/mozilla-nss@3.51.1-2020.0.1.0").unwrap()),
dependency_type: "incorporate".to_string(), dependency_type: "incorporate".to_string(),
facets: hashmap! { facets: hashmap! {
"version-lock.system/mozilla-nss".to_string() => Facet{ "version-lock.system/mozilla-nss".to_string() => Facet{
@ -1031,7 +1032,13 @@ depend facet.version-lock.system/mozilla-nss=true fmri=system/mozilla-nss@3.51.1
assert_eq!(manifest.dependencies.len(), test_results.len()); assert_eq!(manifest.dependencies.len(), test_results.len());
for (pos, dependency) in manifest.dependencies.iter().enumerate() { for (pos, dependency) in manifest.dependencies.iter().enumerate() {
assert_eq!(dependency.fmri, test_results[pos].fmri); // Compare the string representation of the FMRIs
if let (Some(dep_fmri), Some(test_fmri)) = (&dependency.fmri, &test_results[pos].fmri) {
assert_eq!(dep_fmri.to_string(), test_fmri.to_string());
} else {
assert_eq!(dependency.fmri.is_none(), test_results[pos].fmri.is_none());
}
assert_eq!( assert_eq!(
dependency.dependency_type, dependency.dependency_type,
test_results[pos].dependency_type test_results[pos].dependency_type

View file

@ -18,6 +18,7 @@ use regex::Regex;
use crate::actions::{Manifest, File as FileAction}; use crate::actions::{Manifest, File as FileAction};
use crate::digest::Digest; use crate::digest::Digest;
use crate::fmri::Fmri;
use crate::payload::{Payload, PayloadCompressionAlgorithm}; use crate::payload::{Payload, PayloadCompressionAlgorithm};
use super::{Repository, RepositoryConfig, RepositoryVersion, REPOSITORY_CONFIG_FILENAME, PublisherInfo, RepositoryInfo, PackageInfo}; use super::{Repository, RepositoryConfig, RepositoryVersion, REPOSITORY_CONFIG_FILENAME, PublisherInfo, RepositoryInfo, PackageInfo};
@ -520,51 +521,51 @@ impl Repository for FileBackend {
if attr.key == "pkg.fmri" && !attr.values.is_empty() { if attr.key == "pkg.fmri" && !attr.values.is_empty() {
let fmri = &attr.values[0]; let fmri = &attr.values[0];
// Parse the FMRI to extract package name and version // Parse the FMRI using our Fmri type
// Format: pkg://publisher/package_name@version match Fmri::parse(fmri) {
if let Some(pkg_part) = fmri.strip_prefix("pkg://") { Ok(parsed_fmri) => {
if let Some(at_pos) = pkg_part.find('@') {
let pkg_with_pub = &pkg_part[0..at_pos];
let version = &pkg_part[at_pos+1..];
// Extract package name (may include publisher)
let pkg_name = if let Some(slash_pos) = pkg_with_pub.find('/') {
// Skip publisher part if present
let pub_end = slash_pos + 1;
&pkg_with_pub[pub_end..]
} else {
pkg_with_pub
};
// Filter by pattern if specified // Filter by pattern if specified
if let Some(pat) = pattern { if let Some(pat) = pattern {
// Try to compile the pattern as a regex // Try to compile the pattern as a regex
match Regex::new(pat) { match Regex::new(pat) {
Ok(regex) => { Ok(regex) => {
// Use regex matching // Use regex matching
if !regex.is_match(pkg_name) { if !regex.is_match(&parsed_fmri.name) {
continue; continue;
} }
}, },
Err(err) => { Err(err) => {
// Log the error but fall back to simple string contains // Log the error but fall back to simple string contains
eprintln!("Error compiling regex pattern '{}': {}", pat, err); eprintln!("Error compiling regex pattern '{}': {}", pat, err);
if !pkg_name.contains(pat) { if !parsed_fmri.name.contains(pat) {
continue; continue;
} }
} }
} }
} }
// If the publisher is not set in the FMRI, use the current publisher
if parsed_fmri.publisher.is_none() {
let mut fmri_with_publisher = parsed_fmri.clone();
fmri_with_publisher.publisher = Some(pub_name.clone());
// Create a PackageInfo struct and add it to the list // Create a PackageInfo struct and add it to the list
packages.push(PackageInfo { packages.push(PackageInfo {
name: pkg_name.to_string(), fmri: fmri_with_publisher,
version: version.to_string(),
publisher: pub_name.clone(),
}); });
} else {
// Create a PackageInfo struct and add it to the list
packages.push(PackageInfo {
fmri: parsed_fmri.clone(),
});
}
// Found the package info, no need to check other attributes // Found the package info, no need to check other attributes
break; break;
},
Err(err) => {
// Log the error but continue processing
eprintln!("Error parsing FMRI '{}': {}", fmri, err);
} }
} }
} }
@ -597,10 +598,17 @@ impl Repository for FileBackend {
let mut contents = Vec::new(); let mut contents = Vec::new();
for pkg_info in packages { for pkg_info in packages {
// Format the package identifier using the FMRI
let pkg_id = if let Some(version) = &pkg_info.fmri.version {
format!("{}@{}", pkg_info.fmri.name, version)
} else {
pkg_info.fmri.name.clone()
};
// Example content data (package, path, type) // Example content data (package, path, type)
let example_contents = vec![ let example_contents = vec![
(format!("{}@{}", pkg_info.name, pkg_info.version), "/usr/bin/example".to_string(), "file".to_string()), (pkg_id.clone(), "/usr/bin/example".to_string(), "file".to_string()),
(format!("{}@{}", pkg_info.name, pkg_info.version), "/usr/share/doc/example".to_string(), "dir".to_string()), (pkg_id.clone(), "/usr/share/doc/example".to_string(), "dir".to_string()),
]; ];
// Filter by action type if specified // Filter by action type if specified

View file

@ -39,12 +39,8 @@ pub struct RepositoryInfo {
/// Information about a package in a repository /// Information about a package in a repository
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct PackageInfo { pub struct PackageInfo {
/// Name of the package /// FMRI (Fault Management Resource Identifier) of the package
pub name: String, pub fmri: crate::fmri::Fmri,
/// Version of the package
pub version: String,
/// Publisher of the package
pub publisher: String,
} }
/// Repository version /// Repository version

View file

@ -211,10 +211,17 @@ impl Repository for RestBackend {
for pkg_info in packages { for pkg_info in packages {
// In a real implementation, we would get this information from the REST API // In a real implementation, we would get this information from the REST API
// Format the package identifier using the FMRI
let pkg_id = if let Some(version) = &pkg_info.fmri.version {
format!("{}@{}", pkg_info.fmri.name, version)
} else {
pkg_info.fmri.name.clone()
};
// Example content data (package, path, type) // Example content data (package, path, type)
let example_contents = vec![ let example_contents = vec![
(format!("{}@{}", pkg_info.name, pkg_info.version), "/usr/bin/example".to_string(), "file".to_string()), (pkg_id.clone(), "/usr/bin/example".to_string(), "file".to_string()),
(format!("{}@{}", pkg_info.name, pkg_info.version), "/usr/share/doc/example".to_string(), "dir".to_string()), (pkg_id.clone(), "/usr/share/doc/example".to_string(), "dir".to_string()),
]; ];
// Filter by action type if specified // Filter by action type if specified

View file

@ -378,7 +378,18 @@ fn main() -> Result<()> {
// Print packages // Print packages
for pkg_info in packages { for pkg_info in packages {
println!("{:<30} {:<15} {:<10}", pkg_info.name, pkg_info.version, pkg_info.publisher); // Format version and publisher, handling optional fields
let version_str = match &pkg_info.fmri.version {
Some(version) => version.to_string(),
None => String::new(),
};
let publisher_str = match &pkg_info.fmri.publisher {
Some(publisher) => publisher.clone(),
None => String::new(),
};
println!("{:<30} {:<15} {:<10}", pkg_info.fmri.name, version_str, publisher_str);
} }
Ok(()) Ok(())