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_derive",
"regex",
"semver",
"serde",
"serde_json",
"sha2 0.9.9",
@ -1348,9 +1349,12 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.17"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
dependencies = [
"serde",
]
[[package]]
name = "serde"

View file

@ -28,6 +28,7 @@ pest_derive = "2.1.0"
strum = { version = "0.24.1", features = ["derive"] }
serde = { version = "1.0.207", features = ["derive"] }
serde_json = "1.0.124"
diff-struct = "0.5.3"
flate2 = "1.0.28"
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
use crate::digest::Digest;
use crate::fmri::Fmri;
use crate::payload::{Payload, PayloadError};
use pest::Parser;
use pest_derive::Parser;
@ -267,9 +268,9 @@ pub enum FileError {
#[derive(Debug, PartialEq)]
))]
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 predicate: String, //TODO make FMRI
pub predicate: Option<Fmri>, // FMRI for conditional dependencies
pub root_image: String, //TODO make boolean
pub optional: Vec<Property>,
pub facets: HashMap<String, Facet>,
@ -288,9 +289,25 @@ impl From<Action> for Dependency {
}
for prop in props {
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,
"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,
_ => {
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)]
pub mod actions;
pub mod digest;
pub mod fmri;
pub mod payload;
pub mod image;
pub mod repository;
@ -16,6 +17,7 @@ mod tests {
use crate::actions::Attr;
use crate::actions::{Dependency, Dir, Facet, File, Link, Manifest, Property};
use crate::digest::{Digest, DigestAlgorithm, DigestSource};
use crate::fmri::Fmri;
use crate::payload::Payload;
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![
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::default()
},
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::default()
},
Dependency {
fmri: "pkg:/system/data/hardware-registry@2020.2.22,5.11-2020.0.1.19951"
.to_string(),
fmri: Some(Fmri::parse("pkg:/system/data/hardware-registry@2020.2.22,5.11-2020.0.1.19951").unwrap()),
dependency_type: "incorporate".to_string(),
facets: hashmap! {
"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 {
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(),
facets: hashmap! {
"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 {
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(),
facets: hashmap! {
"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());
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!(
dependency.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::digest::Digest;
use crate::fmri::Fmri;
use crate::payload::{Payload, PayloadCompressionAlgorithm};
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() {
let fmri = &attr.values[0];
// Parse the FMRI to extract package name and version
// Format: pkg://publisher/package_name@version
if let Some(pkg_part) = fmri.strip_prefix("pkg://") {
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
};
// Parse the FMRI using our Fmri type
match Fmri::parse(fmri) {
Ok(parsed_fmri) => {
// Filter by pattern if specified
if let Some(pat) = pattern {
// Try to compile the pattern as a regex
match Regex::new(pat) {
Ok(regex) => {
// Use regex matching
if !regex.is_match(pkg_name) {
if !regex.is_match(&parsed_fmri.name) {
continue;
}
},
Err(err) => {
// Log the error but fall back to simple string contains
eprintln!("Error compiling regex pattern '{}': {}", pat, err);
if !pkg_name.contains(pat) {
if !parsed_fmri.name.contains(pat) {
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
packages.push(PackageInfo {
name: pkg_name.to_string(),
version: version.to_string(),
publisher: pub_name.clone(),
fmri: fmri_with_publisher,
});
} 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
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();
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)
let example_contents = vec![
(format!("{}@{}", pkg_info.name, pkg_info.version), "/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/bin/example".to_string(), "file".to_string()),
(pkg_id.clone(), "/usr/share/doc/example".to_string(), "dir".to_string()),
];
// Filter by action type if specified

View file

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

View file

@ -211,10 +211,17 @@ impl Repository for RestBackend {
for pkg_info in packages {
// 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)
let example_contents = vec![
(format!("{}@{}", pkg_info.name, pkg_info.version), "/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/bin/example".to_string(), "file".to_string()),
(pkg_id.clone(), "/usr/share/doc/example".to_string(), "dir".to_string()),
];
// Filter by action type if specified

View file

@ -378,7 +378,18 @@ fn main() -> Result<()> {
// Print 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(())