diff --git a/libips/src/actions/mod.rs b/libips/src/actions/mod.rs index 3ff018c..0981879 100644 --- a/libips/src/actions/mod.rs +++ b/libips/src/actions/mod.rs @@ -511,8 +511,10 @@ impl From for User { // Parse ftpuser property into services match string_to_bool(&prop.value) { // If it's a boolean value (backward compatibility) - Ok(true) => { user.services.insert("ftp".to_string()); }, - Ok(false) => {}, // No services if false + Ok(true) => { + user.services.insert("ftp".to_string()); + } + Ok(false) => {} // No services if false // If the value not a boolean, treat as a comma-separated list of services _ => { for service in prop.value.split(',') { @@ -858,7 +860,7 @@ impl Manifest { pub fn parse_file>(f: P) -> Result { let content = read_to_string(f)?; - + // Try to parse as JSON first match serde_json::from_str::(&content) { Ok(manifest) => Ok(manifest), @@ -966,7 +968,7 @@ pub enum ManifestError { help("Check the action name and make sure it's one of the supported action types.") )] UnknownAction { line: usize, action: String }, - + #[error("action string \"{action:?}\" at line {line:?} is invalid: {message:?}")] #[diagnostic( code(ips::action_error::manifest::invalid_action), diff --git a/libips/src/digest/mod.rs b/libips/src/digest/mod.rs index 99a7083..4154058 100644 --- a/libips/src/digest/mod.rs +++ b/libips/src/digest/mod.rs @@ -155,7 +155,7 @@ pub enum DigestError { help("Use one of the supported algorithms: sha1, sha256t, sha512t, sha512t_256, sha3256t, sha3512t_256, sha3512t") )] UnknownAlgorithm { algorithm: String }, - + #[error("digest {digest:?} is not formatted properly: {details:?}")] #[diagnostic( code(ips::digest_error::invalid_format), diff --git a/libips/src/fmri.rs b/libips/src/fmri.rs index aece6ff..6adefcc 100644 --- a/libips/src/fmri.rs +++ b/libips/src/fmri.rs @@ -73,35 +73,35 @@ pub enum FmriError { help("FMRI should be in the format: [scheme://][publisher/]name[@version]") )] InvalidFormat, - + #[error("invalid version format")] #[diagnostic( code(ips::fmri_error::invalid_version_format), help("Version should be in the format: release[,branch][-build][:timestamp]") )] InvalidVersionFormat, - + #[error("invalid release format")] #[diagnostic( code(ips::fmri_error::invalid_release_format), help("Release should be a dot-separated vector of digits (e.g., 5.11)") )] InvalidReleaseFormat, - + #[error("invalid branch format")] #[diagnostic( code(ips::fmri_error::invalid_branch_format), help("Branch should be a dot-separated vector of digits (e.g., 1)") )] InvalidBranchFormat, - + #[error("invalid build format")] #[diagnostic( code(ips::fmri_error::invalid_build_format), help("Build should be a dot-separated vector of digits (e.g., 2020.0.1.0)") )] InvalidBuildFormat, - + #[error("invalid timestamp format")] #[diagnostic( code(ips::fmri_error::invalid_timestamp_format), diff --git a/libips/src/image/mod.rs b/libips/src/image/mod.rs index 3337b46..623243c 100644 --- a/libips/src/image/mod.rs +++ b/libips/src/image/mod.rs @@ -1,7 +1,7 @@ mod properties; -use properties::*; use miette::Diagnostic; +use properties::*; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs::File; @@ -16,7 +16,7 @@ pub enum ImageError { help("Check system resources and permissions") )] IO(#[from] std::io::Error), - + #[error("JSON error: {0}")] #[diagnostic( code(ips::image_error::json), diff --git a/libips/src/payload/mod.rs b/libips/src/payload/mod.rs index 39a97b7..0e4b0d3 100644 --- a/libips/src/payload/mod.rs +++ b/libips/src/payload/mod.rs @@ -23,7 +23,7 @@ pub enum PayloadError { help("Check system resources and permissions") )] IOError(#[from] IOError), - + #[error(transparent)] #[diagnostic(transparent)] DigestError(#[from] DigestError), diff --git a/libips/src/repository/catalog.rs b/libips/src/repository/catalog.rs index ff9a714..89d2176 100644 --- a/libips/src/repository/catalog.rs +++ b/libips/src/repository/catalog.rs @@ -22,36 +22,28 @@ pub enum CatalogError { code(ips::repository_error::catalog::part_not_found), help("Check that the catalog part exists and is accessible") )] - CatalogPartNotFound { - name: String, - }, + CatalogPartNotFound { name: String }, #[error("catalog part not loaded: {name}")] #[diagnostic( code(ips::repository_error::catalog::part_not_loaded), help("Load the catalog part before attempting to save it") )] - CatalogPartNotLoaded { - name: String, - }, + CatalogPartNotLoaded { name: String }, #[error("update log not loaded: {name}")] #[diagnostic( code(ips::repository_error::catalog::update_log_not_loaded), help("Load the update log before attempting to save it") )] - UpdateLogNotLoaded { - name: String, - }, + UpdateLogNotLoaded { name: String }, #[error("update log does not exist: {name}")] #[diagnostic( code(ips::repository_error::catalog::update_log_not_found), help("Check that the update log exists and is accessible") )] - UpdateLogNotFound { - name: String, - }, + UpdateLogNotFound { name: String }, #[error("failed to serialize JSON: {0}")] #[diagnostic( diff --git a/libips/src/repository/file_backend.rs b/libips/src/repository/file_backend.rs index ad33c68..3c66e3a 100644 --- a/libips/src/repository/file_backend.rs +++ b/libips/src/repository/file_backend.rs @@ -3,7 +3,7 @@ // MPL was not distributed with this file, You can // obtain one at https://mozilla.org/MPL/2.0/. -use super::{Result, RepositoryError}; +use super::{RepositoryError, Result}; use flate2::write::GzEncoder; use flate2::Compression as GzipCompression; use lz4::EncoderBuilder; @@ -316,13 +316,22 @@ impl Transaction { // Check if the temp file already exists if temp_file_path.exists() { // If it exists, remove it to avoid any issues with existing content - fs::remove_file(&temp_file_path) - .map_err(|e| RepositoryError::FileWriteError(format!("Failed to remove existing temp file: {}", e)))?; + fs::remove_file(&temp_file_path).map_err(|e| { + RepositoryError::FileWriteError(format!( + "Failed to remove existing temp file: {}", + e + )) + })?; } // Read the file content - let file_content = fs::read(file_path) - .map_err(|e| RepositoryError::FileReadError(format!("Failed to read file {}: {}", file_path.display(), e)))?; + let file_content = fs::read(file_path).map_err(|e| { + RepositoryError::FileReadError(format!( + "Failed to read file {}: {}", + file_path.display(), + e + )) + })?; // Create a payload with the hash information if it doesn't exist let mut updated_file_action = file_action; @@ -338,18 +347,22 @@ impl Transaction { let mut encoder = GzEncoder::new(Vec::new(), GzipCompression::default()); // Write the file content to the encoder - encoder - .write_all(&file_content) - .map_err(|e| RepositoryError::Other(format!("Failed to write data to Gzip encoder: {}", e)))?; + encoder.write_all(&file_content).map_err(|e| { + RepositoryError::Other(format!("Failed to write data to Gzip encoder: {}", e)) + })?; // Finish the compression and get the compressed data - let compressed_data = encoder - .finish() - .map_err(|e| RepositoryError::Other(format!("Failed to finish Gzip compression: {}", e)))?; + let compressed_data = encoder.finish().map_err(|e| { + RepositoryError::Other(format!("Failed to finish Gzip compression: {}", e)) + })?; // Write the compressed data to the temp file - fs::write(&temp_file_path, &compressed_data) - .map_err(|e| RepositoryError::FileWriteError(format!("Failed to write compressed data to temp file: {}", e)))?; + fs::write(&temp_file_path, &compressed_data).map_err(|e| { + RepositoryError::FileWriteError(format!( + "Failed to write compressed data to temp file: {}", + e + )) + })?; // Calculate hash of the compressed data let mut hasher = Sha256::new(); @@ -358,21 +371,24 @@ impl Transaction { } PayloadCompressionAlgorithm::LZ4 => { // Create an LZ4 encoder with the default compression level - let mut encoder = EncoderBuilder::new() - .build(Vec::new()) - .map_err(|e| RepositoryError::Other(format!("Failed to create LZ4 encoder: {}", e)))?; + let mut encoder = EncoderBuilder::new().build(Vec::new()).map_err(|e| { + RepositoryError::Other(format!("Failed to create LZ4 encoder: {}", e)) + })?; // Write the file content to the encoder - encoder - .write_all(&file_content) - .map_err(|e| RepositoryError::Other(format!("Failed to write data to LZ4 encoder: {}", e)))?; + encoder.write_all(&file_content).map_err(|e| { + RepositoryError::Other(format!("Failed to write data to LZ4 encoder: {}", e)) + })?; // Finish the compression and get the compressed data let (compressed_data, _) = encoder.finish(); // Write the compressed data to the temp file fs::write(&temp_file_path, &compressed_data).map_err(|e| { - RepositoryError::FileWriteError(format!("Failed to write LZ4 compressed data to temp file: {}", e)) + RepositoryError::FileWriteError(format!( + "Failed to write LZ4 compressed data to temp file: {}", + e + )) })?; // Calculate hash of the compressed data @@ -424,9 +440,13 @@ impl Transaction { // Extract the first two and next two characters from the hash let first_two = &hash[0..2]; let next_two = &hash[2..4]; - + // Create the path: $REPO/file/XX/YY/XXYY... - self.repo.join("file").join(first_two).join(next_two).join(&hash) + self.repo + .join("file") + .join(first_two) + .join(next_two) + .join(&hash) }; // Create parent directories if they don't exist @@ -460,7 +480,7 @@ impl Transaction { Some(pub_name) => { debug!("Using specified publisher: {}", pub_name); pub_name.clone() - }, + } None => { debug!("No publisher specified, trying to use default publisher"); // If no publisher is specified, use the default publisher from the repository config @@ -472,18 +492,19 @@ impl Transaction { Some(default_pub) => { debug!("Using default publisher: {}", default_pub); default_pub - }, + } None => { debug!("No default publisher set in repository"); return Err(RepositoryError::Other( - "No publisher specified and no default publisher set in repository".to_string() - )) + "No publisher specified and no default publisher set in repository" + .to_string(), + )); } } } else { debug!("Repository configuration not found"); return Err(RepositoryError::Other( - "No publisher specified and repository configuration not found".to_string() + "No publisher specified and repository configuration not found".to_string(), )); } } @@ -498,7 +519,12 @@ impl Transaction { } // Construct the manifest path using the helper method - let pkg_manifest_path = FileBackend::construct_manifest_path(&self.repo, &publisher, &package_stem, &package_version); + let pkg_manifest_path = FileBackend::construct_manifest_path( + &self.repo, + &publisher, + &package_stem, + &package_version, + ); debug!("Manifest path: {}", pkg_manifest_path.display()); // Create parent directories if they don't exist @@ -508,7 +534,11 @@ impl Transaction { } // Copy to pkg directory - debug!("Copying manifest from {} to {}", manifest_path.display(), pkg_manifest_path.display()); + debug!( + "Copying manifest from {} to {}", + manifest_path.display(), + pkg_manifest_path.display() + ); fs::copy(&manifest_path, &pkg_manifest_path)?; // Clean up the transaction directory @@ -653,13 +683,19 @@ impl ReadableRepository for FileBackend { if publisher_pkg_dir.exists() { // Verify that the publisher is in the config if !self.config.publishers.contains(&pub_name) { - return Err(RepositoryError::Other( - format!("Publisher directory exists but is not in the repository configuration: {}", pub_name) - )); + return Err(RepositoryError::Other(format!( + "Publisher directory exists but is not in the repository configuration: {}", + pub_name + ))); } // Recursively walk through the directory and collect package manifests - self.find_manifests_recursive(&publisher_pkg_dir, &pub_name, pattern, &mut packages)?; + self.find_manifests_recursive( + &publisher_pkg_dir, + &pub_name, + pattern, + &mut packages, + )?; } } @@ -964,16 +1000,16 @@ impl ReadableRepository for FileBackend { // For each publisher, search the index for pub_name in publishers { debug!("Searching publisher: {}", pub_name); - + // Check if the index exists let index_path = self.path.join("index").join(&pub_name).join("search.json"); debug!("Index path: {}", index_path.display()); debug!("Index exists: {}", index_path.exists()); - + if let Ok(Some(index)) = self.get_search_index(&pub_name) { debug!("Got search index for publisher: {}", pub_name); debug!("Index terms: {:?}", index.terms.keys().collect::>()); - + // Search the index let fmris = index.search(query, limit); debug!("Search results (FMRIs): {:?}", fmris); @@ -990,7 +1026,7 @@ impl ReadableRepository for FileBackend { } else { debug!("No search index found for publisher: {}", pub_name); debug!("Falling back to simple search"); - + // If the index doesn't exist, fall back to the simple search let all_packages = self.list_packages(Some(&pub_name), None)?; debug!("All packages: {:?}", all_packages); @@ -1093,14 +1129,16 @@ impl WritableRepository for FileBackend { // Remove the catalog directory if it exists if catalog_dir.exists() { - fs::remove_dir_all(&catalog_dir) - .map_err(|e| RepositoryError::Other(format!("Failed to remove catalog directory: {}", e)))?; + fs::remove_dir_all(&catalog_dir).map_err(|e| { + RepositoryError::Other(format!("Failed to remove catalog directory: {}", e)) + })?; } // Remove the package directory if it exists if pkg_dir.exists() { - fs::remove_dir_all(&pkg_dir) - .map_err(|e| RepositoryError::Other(format!("Failed to remove package directory: {}", e)))?; + fs::remove_dir_all(&pkg_dir).map_err(|e| { + RepositoryError::Other(format!("Failed to remove package directory: {}", e)) + })?; } // Save the updated configuration @@ -1146,8 +1184,11 @@ impl WritableRepository for FileBackend { /// Rebuild repository metadata fn rebuild(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()> { - debug!("rebuild called with publisher: {:?}, no_catalog: {}, no_index: {}", publisher, no_catalog, no_index); - + debug!( + "rebuild called with publisher: {:?}, no_catalog: {}, no_index: {}", + publisher, no_catalog, no_index + ); + // Filter publishers if specified let publishers = if let Some(pub_name) = publisher { if !self.config.publishers.contains(&pub_name.to_string()) { @@ -1156,7 +1197,10 @@ impl WritableRepository for FileBackend { debug!("rebuild: using specified publisher: {}", pub_name); vec![pub_name.to_string()] } else { - debug!("rebuild: using all publishers: {:?}", self.config.publishers); + debug!( + "rebuild: using all publishers: {:?}", + self.config.publishers + ); self.config.publishers.clone() }; @@ -1227,7 +1271,12 @@ impl WritableRepository for FileBackend { impl FileBackend { /// Helper method to construct a manifest path consistently - fn construct_manifest_path(base_path: &Path, publisher: &str, stem: &str, version: &str) -> PathBuf { + fn construct_manifest_path( + base_path: &Path, + publisher: &str, + stem: &str, + version: &str, + ) -> PathBuf { let pkg_dir = base_path.join("pkg").join(publisher).join(stem); let encoded_version = Self::url_encode(version); pkg_dir.join(encoded_version) @@ -1253,26 +1302,36 @@ impl FileBackend { let mut file = match fs::File::open(&path) { Ok(file) => file, Err(err) => { - error!("FileBackend::find_manifests_recursive: Error opening file {}: {}", path.display(), err); + error!( + "FileBackend::find_manifests_recursive: Error opening file {}: {}", + path.display(), + err + ); continue; } }; - + let mut buffer = [0; 1024]; let bytes_read = match file.read(&mut buffer) { Ok(bytes) => bytes, Err(err) => { - error!("FileBackend::find_manifests_recursive: Error reading file {}: {}", path.display(), err); + error!( + "FileBackend::find_manifests_recursive: Error reading file {}: {}", + path.display(), + err + ); continue; } }; - + // Check if the file starts with a valid manifest marker // For example, if it's a JSON file, it should start with '{' - if bytes_read == 0 || (buffer[0] != b'{' && buffer[0] != b'<' && buffer[0] != b's') { + if bytes_read == 0 + || (buffer[0] != b'{' && buffer[0] != b'<' && buffer[0] != b's') + { continue; } - + // Process manifest files match Manifest::parse_file(&path) { Ok(manifest) => { @@ -1307,7 +1366,8 @@ impl FileBackend { // 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(publisher.to_string()); + fmri_with_publisher.publisher = + Some(publisher.to_string()); // Create a PackageInfo struct and add it to the list packages.push(PackageInfo { @@ -1360,76 +1420,83 @@ impl FileBackend { Ok(()) } - + /// Rebuild catalog for a publisher /// /// This method generates catalog files for a publisher and stores them in the publisher's /// subdirectory within the catalog directory. pub fn rebuild_catalog(&self, publisher: &str, create_update_log: bool) -> Result<()> { info!("Rebuilding catalog for publisher: {}", publisher); - debug!("Catalog directory path: {}", self.path.join("catalog").display()); - + debug!( + "Catalog directory path: {}", + self.path.join("catalog").display() + ); + // Create the catalog directory for the publisher if it doesn't exist let catalog_dir = self.path.join("catalog").join(publisher); debug!("Publisher catalog directory: {}", catalog_dir.display()); fs::create_dir_all(&catalog_dir)?; debug!("Created publisher catalog directory"); - + // Collect package data let packages = self.list_packages(Some(publisher), None)?; - + // Prepare data structures for catalog parts let mut base_entries = Vec::new(); let mut dependency_entries = Vec::new(); let mut summary_entries = Vec::new(); let mut update_entries = Vec::new(); - + // Track package counts let mut package_count = 0; let mut package_version_count = 0; - + // Process each package for package in packages { let fmri = &package.fmri; let stem = fmri.stem(); - + // Skip if no version if fmri.version().is_empty() { continue; } - + // Get the package version let version = fmri.version(); - + // Construct the manifest path using the helper method - let manifest_path = Self::construct_manifest_path(&self.path, publisher, stem, &version); - + let manifest_path = + Self::construct_manifest_path(&self.path, publisher, stem, &version); + // Check if the package directory exists if let Some(pkg_dir) = manifest_path.parent() { if !pkg_dir.exists() { - error!("Package directory {} does not exist skipping", pkg_dir.display()); + error!( + "Package directory {} does not exist skipping", + pkg_dir.display() + ); continue; } } - + if !manifest_path.exists() { continue; } - + // Read the manifest content for hash calculation let manifest_content = fs::read_to_string(&manifest_path)?; - + // Parse the manifest using parse_file which handles JSON correctly let manifest = Manifest::parse_file(&manifest_path)?; - + // Calculate SHA-256 hash of the manifest (as a substitute for SHA-1) let mut hasher = sha2::Sha256::new(); hasher.update(manifest_content.as_bytes()); let signature = format!("{:x}", hasher.finalize()); - + // Add to base entries base_entries.push((fmri.clone(), None, signature.clone())); - + // Extract dependency actions let mut dependency_actions = Vec::new(); for dep in &manifest.dependencies { @@ -1440,7 +1507,7 @@ impl FileBackend { )); } } - + // Extract variant and facet actions for attr in &manifest.attributes { if attr.key.starts_with("variant.") || attr.key.starts_with("facet.") { @@ -1448,7 +1515,7 @@ impl FileBackend { dependency_actions.push(format!("set name={} value={}", attr.key, values_str)); } } - + // Add to dependency entries if there are dependency actions if !dependency_actions.is_empty() { dependency_entries.push(( @@ -1457,7 +1524,7 @@ impl FileBackend { signature.clone(), )); } - + // Extract summary actions (set actions excluding variants and facets) let mut summary_actions = Vec::new(); for attr in &manifest.attributes { @@ -1466,7 +1533,7 @@ impl FileBackend { summary_actions.push(format!("set name={} value={}", attr.key, values_str)); } } - + // Add to summary entries if there are summary actions if !summary_actions.is_empty() { summary_entries.push(( @@ -1475,40 +1542,40 @@ impl FileBackend { signature.clone(), )); } - + // Prepare update entry if needed if create_update_log { let mut catalog_parts = HashMap::new(); - + // Add dependency actions to update entry if !dependency_actions.is_empty() { let mut actions = HashMap::new(); actions.insert("actions".to_string(), dependency_actions); catalog_parts.insert("catalog.dependency.C".to_string(), actions); } - + // Add summary actions to update entry if !summary_actions.is_empty() { let mut actions = HashMap::new(); actions.insert("actions".to_string(), summary_actions); catalog_parts.insert("catalog.summary.C".to_string(), actions); } - + // Add to update entries update_entries.push((fmri.clone(), catalog_parts, signature)); } - + // Update counts package_count += 1; package_version_count += 1; } - + // Create and save catalog parts - + // Create a catalog.attrs file let now = SystemTime::now(); let timestamp = format_iso8601_timestamp(&now); - + // Get the CatalogAttrs struct definition to see what fields it has let mut attrs = crate::repository::catalog::CatalogAttrs { created: timestamp.clone(), @@ -1520,7 +1587,7 @@ impl FileBackend { signature: None, updates: HashMap::new(), }; - + // Add part information let base_part_name = "catalog.base.C"; attrs.parts.insert( @@ -1530,7 +1597,7 @@ impl FileBackend { signature_sha1: None, }, ); - + let dependency_part_name = "catalog.dependency.C"; attrs.parts.insert( dependency_part_name.to_string(), @@ -1539,7 +1606,7 @@ impl FileBackend { signature_sha1: None, }, ); - + let summary_part_name = "catalog.summary.C"; attrs.parts.insert( summary_part_name.to_string(), @@ -1548,16 +1615,16 @@ impl FileBackend { signature_sha1: None, }, ); - + // Save the catalog.attrs file let attrs_path = catalog_dir.join("catalog.attrs"); debug!("Writing catalog.attrs to: {}", attrs_path.display()); let attrs_json = serde_json::to_string_pretty(&attrs)?; fs::write(&attrs_path, attrs_json)?; debug!("Wrote catalog.attrs file"); - + // Create and save catalog parts - + // Base part let base_part_path = catalog_dir.join(base_part_name); debug!("Writing base part to: {}", base_part_path.display()); @@ -1568,10 +1635,13 @@ impl FileBackend { let base_part_json = serde_json::to_string_pretty(&base_part)?; fs::write(&base_part_path, base_part_json)?; debug!("Wrote base part file"); - + // Dependency part let dependency_part_path = catalog_dir.join(dependency_part_name); - debug!("Writing dependency part to: {}", dependency_part_path.display()); + debug!( + "Writing dependency part to: {}", + dependency_part_path.display() + ); let mut dependency_part = crate::repository::catalog::CatalogPart::new(); for (fmri, actions, signature) in dependency_entries { dependency_part.add_package(publisher, &fmri, actions, Some(signature)); @@ -1579,7 +1649,7 @@ impl FileBackend { let dependency_part_json = serde_json::to_string_pretty(&dependency_part)?; fs::write(&dependency_part_path, dependency_part_json)?; debug!("Wrote dependency part file"); - + // Summary part let summary_part_path = catalog_dir.join(summary_part_name); debug!("Writing summary part to: {}", summary_part_path.display()); @@ -1590,14 +1660,14 @@ impl FileBackend { let summary_part_json = serde_json::to_string_pretty(&summary_part)?; fs::write(&summary_part_path, summary_part_json)?; debug!("Wrote summary part file"); - + // Create and save the update log if needed if create_update_log { debug!("Creating update log"); let update_log_name = format!("update.{}Z.C", timestamp.split('.').next().unwrap()); let update_log_path = catalog_dir.join(&update_log_name); debug!("Update log path: {}", update_log_path.display()); - + let mut update_log = crate::repository::catalog::UpdateLog::new(); debug!("Adding {} updates to the log", update_entries.len()); for (fmri, catalog_parts, signature) in update_entries { @@ -1609,11 +1679,11 @@ impl FileBackend { Some(signature), ); } - + let update_log_json = serde_json::to_string_pretty(&update_log)?; fs::write(&update_log_path, update_log_json)?; debug!("Wrote update log file"); - + // Add an update log to catalog.attrs debug!("Adding update log to catalog.attrs"); attrs.updates.insert( @@ -1623,18 +1693,18 @@ impl FileBackend { signature_sha1: None, }, ); - + // Update the catalog.attrs file with the new update log debug!("Updating catalog.attrs file with new update log"); let attrs_json = serde_json::to_string_pretty(&attrs)?; fs::write(catalog_dir.join("catalog.attrs"), attrs_json)?; debug!("Updated catalog.attrs file"); } - + info!("Catalog rebuilt for publisher: {}", publisher); Ok(()) } - + /// Generate the file path for a given hash using the new directory structure /// The path will be $REPO/file/XX/YY/XXYY... where XX and YY are the first four letters of the hash fn generate_file_path(&self, hash: &str) -> PathBuf { @@ -1642,17 +1712,21 @@ impl FileBackend { // Fallback for very short hashes (shouldn't happen with SHA256) return self.path.join("file").join(hash); } - + // Extract the first two and next two characters from the hash let first_two = &hash[0..2]; let next_two = &hash[2..4]; - + // Create the path: $REPO/file/XX/YY/XXYY... - self.path.join("file").join(first_two).join(next_two).join(hash) + self.path + .join("file") + .join(first_two) + .join(next_two) + .join(hash) } /// Get or initialize the catalog manager - /// + /// /// This method returns a mutable reference to the catalog manager. /// It uses interior mutability with RefCell to allow mutation through an immutable reference. pub fn get_catalog_manager( @@ -1888,7 +1962,7 @@ impl FileBackend { // Begin a transaction let mut transaction = self.begin_transaction()?; - + // Set the publisher for the transaction transaction.set_publisher(publisher); @@ -1913,11 +1987,10 @@ impl FileBackend { let actual_path = &transaction.manifest.files[0].path; if actual_path != expected_path { - return Err(RepositoryError::Other( - format!("Path in FileAction is incorrect. Expected: {}, Actual: {}", - expected_path, - actual_path) - )); + return Err(RepositoryError::Other(format!( + "Path in FileAction is incorrect. Expected: {}, Actual: {}", + expected_path, actual_path + ))); } // Commit the transaction @@ -1928,12 +2001,18 @@ impl FileBackend { let stored_file_path = self.generate_file_path(&hash); if !stored_file_path.exists() { - return Err(RepositoryError::Other("File was not stored correctly".to_string())); + return Err(RepositoryError::Other( + "File was not stored correctly".to_string(), + )); } // Verify the manifest was updated in the publisher-specific directory // The manifest should be named "unknown.manifest" since we didn't set a package name - let manifest_path = self.path.join("pkg").join(publisher).join("unknown.manifest"); + let manifest_path = self + .path + .join("pkg") + .join(publisher) + .join("unknown.manifest"); if !manifest_path.exists() { return Err(RepositoryError::Other(format!( @@ -1974,7 +2053,7 @@ impl FileBackend { // Begin a transaction let mut transaction = self.begin_transaction()?; - + // Set the publisher for the transaction transaction.set_publisher(publisher); @@ -1983,7 +2062,7 @@ impl FileBackend { // Commit the transaction transaction.commit()?; - + // Regenerate catalog and search index self.rebuild(Some(publisher), false, false)?; diff --git a/libips/src/repository/mod.rs b/libips/src/repository/mod.rs index 6714546..1b0e79d 100644 --- a/libips/src/repository/mod.rs +++ b/libips/src/repository/mod.rs @@ -3,10 +3,10 @@ // MPL was not distributed with this file, You can // obtain one at https://mozilla.org/MPL/2.0/. +use miette::Diagnostic; use std::collections::HashMap; use std::io; use std::path::{Path, StripPrefixError}; -use miette::Diagnostic; use thiserror::Error; /// Result type for repository operations @@ -149,25 +149,25 @@ impl From for RepositoryError { } } - impl From for RepositoryError { fn from(err: StripPrefixError) -> Self { RepositoryError::PathPrefixError(err.to_string()) } } - mod catalog; mod file_backend; mod rest_backend; #[cfg(test)] mod tests; -pub use catalog::{CatalogAttrs, CatalogError, CatalogManager, CatalogOperationType, CatalogPart, UpdateLog}; -pub use file_backend::FileBackend; -pub use rest_backend::RestBackend; use crate::actions::ActionError; use crate::digest::DigestError; +pub use catalog::{ + CatalogAttrs, CatalogError, CatalogManager, CatalogOperationType, CatalogPart, UpdateLog, +}; +pub use file_backend::FileBackend; +pub use rest_backend::RestBackend; /// Repository configuration filename pub const REPOSITORY_CONFIG_FILENAME: &str = "pkg6.repository"; diff --git a/libips/src/repository/tests.rs b/libips/src/repository/tests.rs index 7bda2ec..8ca865b 100644 --- a/libips/src/repository/tests.rs +++ b/libips/src/repository/tests.rs @@ -8,8 +8,8 @@ mod tests { use crate::actions::Manifest; use crate::fmri::Fmri; use crate::repository::{ - CatalogManager, FileBackend, ReadableRepository, RepositoryError, RepositoryVersion, Result, - WritableRepository, REPOSITORY_CONFIG_FILENAME, + CatalogManager, FileBackend, ReadableRepository, RepositoryError, RepositoryVersion, + Result, WritableRepository, REPOSITORY_CONFIG_FILENAME, }; use std::fs; use std::path::PathBuf; @@ -83,17 +83,19 @@ mod tests { // Check if the manifest file exists if !manifest_path.exists() { println!("Error: Manifest file does not exist"); - return Err(RepositoryError::FileReadError( - format!("Manifest file does not exist: {}", manifest_path.display()) - )); + return Err(RepositoryError::FileReadError(format!( + "Manifest file does not exist: {}", + manifest_path.display() + ))); } // Check if the prototype directory exists if !prototype_dir.exists() { println!("Error: Prototype directory does not exist"); - return Err(RepositoryError::NotFound( - format!("Prototype directory does not exist: {}", prototype_dir.display()) - )); + return Err(RepositoryError::NotFound(format!( + "Prototype directory does not exist: {}", + prototype_dir.display() + ))); } // Parse the manifest file @@ -393,38 +395,50 @@ mod tests { // Clean up cleanup_test_dir(&test_dir); } - + #[test] fn test_file_structure() { // Create a test directory let test_dir = create_test_dir("file_structure"); let repo_path = test_dir.join("repo"); - + // Create a repository let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap(); - + // Add a publisher repo.add_publisher("test").unwrap(); - + // Create a test file let test_file_path = test_dir.join("test_file.txt"); fs::write(&test_file_path, "This is a test file").unwrap(); - + // Store the file in the repository let hash = repo.store_file(&test_file_path).unwrap(); - + // Check if the file was stored in the correct directory structure let first_two = &hash[0..2]; let next_two = &hash[2..4]; - let expected_path = repo_path.join("file").join(first_two).join(next_two).join(&hash); - + let expected_path = repo_path + .join("file") + .join(first_two) + .join(next_two) + .join(&hash); + // Verify that the file exists at the expected path - assert!(expected_path.exists(), "File was not stored at the expected path: {}", expected_path.display()); - + assert!( + expected_path.exists(), + "File was not stored at the expected path: {}", + expected_path.display() + ); + // Verify that the file does NOT exist at the old path let old_path = repo_path.join("file").join(&hash); - assert!(!old_path.exists(), "File was stored at the old path: {}", old_path.display()); - + assert!( + !old_path.exists(), + "File was stored at the old path: {}", + old_path.display() + ); + // Clean up cleanup_test_dir(&test_dir); } diff --git a/libips/src/test_json_manifest.rs b/libips/src/test_json_manifest.rs index 41cc2f0..25419d5 100644 --- a/libips/src/test_json_manifest.rs +++ b/libips/src/test_json_manifest.rs @@ -13,7 +13,7 @@ mod tests { // Create a simple manifest let mut manifest = Manifest::new(); - + // Add some attributes let mut attr = crate::actions::Attr::default(); attr.key = "pkg.fmri".to_string(); @@ -33,7 +33,10 @@ mod tests { // Verify that the parsed manifest matches the original assert_eq!(parsed_manifest.attributes.len(), 1); assert_eq!(parsed_manifest.attributes[0].key, "pkg.fmri"); - assert_eq!(parsed_manifest.attributes[0].values[0], "pkg://test/example@1.0.0"); + assert_eq!( + parsed_manifest.attributes[0].values[0], + "pkg://test/example@1.0.0" + ); } #[test] @@ -55,6 +58,9 @@ mod tests { // Verify that the parsed manifest has the expected attributes assert_eq!(parsed_manifest.attributes.len(), 1); assert_eq!(parsed_manifest.attributes[0].key, "pkg.fmri"); - assert_eq!(parsed_manifest.attributes[0].values[0], "pkg://test/example@1.0.0"); + assert_eq!( + parsed_manifest.attributes[0].values[0], + "pkg://test/example@1.0.0" + ); } -} \ No newline at end of file +} diff --git a/libips/tests/test_manifest_parsing.rs b/libips/tests/test_manifest_parsing.rs index ecf18c4..9c45507 100644 --- a/libips/tests/test_manifest_parsing.rs +++ b/libips/tests/test_manifest_parsing.rs @@ -7,33 +7,54 @@ use std::path::Path; fn test_parse_postgre_common_manifest() { let manifest_path = Path::new("/home/toasty/ws/illumos/ips/pkg6repo/postgre-common.manifest"); let manifest = Manifest::parse_file(manifest_path).expect("Failed to parse manifest"); - + // Check that the manifest contains the expected actions assert_eq!(manifest.attributes.len(), 11, "Expected 11 attributes"); assert_eq!(manifest.directories.len(), 1, "Expected 1 directory"); assert_eq!(manifest.groups.len(), 1, "Expected 1 group"); assert_eq!(manifest.users.len(), 1, "Expected 1 user"); assert_eq!(manifest.licenses.len(), 1, "Expected 1 license"); - + // Check the group action let group = &manifest.groups[0]; - assert_eq!(group.groupname, "postgres", "Expected groupname to be 'postgres'"); + assert_eq!( + group.groupname, "postgres", + "Expected groupname to be 'postgres'" + ); assert_eq!(group.gid, "90", "Expected gid to be '90'"); - + // Check the user action let user = &manifest.users[0]; - assert_eq!(user.username, "postgres", "Expected username to be 'postgres'"); + assert_eq!( + user.username, "postgres", + "Expected username to be 'postgres'" + ); assert_eq!(user.uid, "90", "Expected uid to be '90'"); assert_eq!(user.group, "postgres", "Expected group to be 'postgres'"); - assert_eq!(user.home_dir, "/var/postgres", "Expected home_dir to be '/var/postgres'"); - assert_eq!(user.login_shell, "/usr/bin/pfksh", "Expected login_shell to be '/usr/bin/pfksh'"); + assert_eq!( + user.home_dir, "/var/postgres", + "Expected home_dir to be '/var/postgres'" + ); + assert_eq!( + user.login_shell, "/usr/bin/pfksh", + "Expected login_shell to be '/usr/bin/pfksh'" + ); assert_eq!(user.password, "NP", "Expected password to be 'NP'"); - assert!(user.services.is_empty(), "Expected no services for ftpuser=false"); - assert_eq!(user.gcos_field, "PostgreSQL Reserved UID", "Expected gcos_field to be 'PostgreSQL Reserved UID'"); - + assert!( + user.services.is_empty(), + "Expected no services for ftpuser=false" + ); + assert_eq!( + user.gcos_field, "PostgreSQL Reserved UID", + "Expected gcos_field to be 'PostgreSQL Reserved UID'" + ); + // Check the directory action let dir = &manifest.directories[0]; - assert_eq!(dir.path, "var/postgres", "Expected path to be 'var/postgres'"); + assert_eq!( + dir.path, "var/postgres", + "Expected path to be 'var/postgres'" + ); assert_eq!(dir.group, "postgres", "Expected group to be 'postgres'"); assert_eq!(dir.owner, "postgres", "Expected owner to be 'postgres'"); assert_eq!(dir.mode, "0755", "Expected mode to be '0755'"); @@ -43,17 +64,29 @@ fn test_parse_postgre_common_manifest() { fn test_parse_pgadmin_manifest() { let manifest_path = Path::new("/home/toasty/ws/illumos/ips/pkg6repo/pgadmin.manifest"); let manifest = Manifest::parse_file(manifest_path).expect("Failed to parse manifest"); - + // Check that the manifest contains the expected actions assert!(manifest.attributes.len() > 0, "Expected attributes"); assert!(manifest.files.len() > 0, "Expected files"); assert_eq!(manifest.legacies.len(), 1, "Expected 1 legacy action"); - + // Check the legacy action let legacy = &manifest.legacies[0]; assert_eq!(legacy.arch, "i386", "Expected arch to be 'i386'"); - assert_eq!(legacy.category, "system", "Expected category to be 'system'"); - assert_eq!(legacy.pkg, "SUNWpgadmin3", "Expected pkg to be 'SUNWpgadmin3'"); - assert_eq!(legacy.vendor, "Project OpenIndiana", "Expected vendor to be 'Project OpenIndiana'"); - assert_eq!(legacy.version, "11.11.0,REV=2010.05.25.01.00", "Expected version to be '11.11.0,REV=2010.05.25.01.00'"); -} \ No newline at end of file + assert_eq!( + legacy.category, "system", + "Expected category to be 'system'" + ); + assert_eq!( + legacy.pkg, "SUNWpgadmin3", + "Expected pkg to be 'SUNWpgadmin3'" + ); + assert_eq!( + legacy.vendor, "Project OpenIndiana", + "Expected vendor to be 'Project OpenIndiana'" + ); + assert_eq!( + legacy.version, "11.11.0,REV=2010.05.25.01.00", + "Expected version to be '11.11.0,REV=2010.05.25.01.00'" + ); +} diff --git a/libips/tests/test_user_services.rs b/libips/tests/test_user_services.rs index 6c6964c..c4143f1 100644 --- a/libips/tests/test_user_services.rs +++ b/libips/tests/test_user_services.rs @@ -6,15 +6,18 @@ use libips::actions::Manifest; fn test_ftpuser_boolean_true() { // Create a manifest string with ftpuser=true let manifest_string = "user ftpuser=true".to_string(); - + // Parse the manifest let manifest = Manifest::parse_string(manifest_string).expect("Failed to parse manifest"); - + // Get the user let user = &manifest.users[0]; - + // Check that "ftp" service is added - assert!(user.services.contains("ftp"), "Expected 'ftp' service to be added when ftpuser=true"); + assert!( + user.services.contains("ftp"), + "Expected 'ftp' service to be added when ftpuser=true" + ); assert_eq!(user.services.len(), 1, "Expected exactly one service"); } @@ -22,32 +25,44 @@ fn test_ftpuser_boolean_true() { fn test_ftpuser_boolean_false() { // Create a manifest string with ftpuser=false let manifest_string = "user ftpuser=false".to_string(); - + // Parse the manifest let manifest = Manifest::parse_string(manifest_string).expect("Failed to parse manifest"); - + // Get the user let user = &manifest.users[0]; - + // Check that no services are added - assert!(user.services.is_empty(), "Expected no services when ftpuser=false"); + assert!( + user.services.is_empty(), + "Expected no services when ftpuser=false" + ); } #[test] fn test_ftpuser_services_list() { // Create a manifest string with ftpuser=ssh,ftp,http let manifest_string = "user ftpuser=ssh,ftp,http".to_string(); - + // Parse the manifest let manifest = Manifest::parse_string(manifest_string).expect("Failed to parse manifest"); - + // Get the user let user = &manifest.users[0]; - + // Check that all services are added - assert!(user.services.contains("ssh"), "Expected 'ssh' service to be added"); - assert!(user.services.contains("ftp"), "Expected 'ftp' service to be added"); - assert!(user.services.contains("http"), "Expected 'http' service to be added"); + assert!( + user.services.contains("ssh"), + "Expected 'ssh' service to be added" + ); + assert!( + user.services.contains("ftp"), + "Expected 'ftp' service to be added" + ); + assert!( + user.services.contains("http"), + "Expected 'http' service to be added" + ); assert_eq!(user.services.len(), 3, "Expected exactly three services"); } @@ -55,17 +70,26 @@ fn test_ftpuser_services_list() { fn test_ftpuser_services_with_whitespace() { // Create a manifest string with ftpuser=ssh, ftp, http let manifest_string = "user ftpuser=\"ssh, ftp, http\"".to_string(); - + // Parse the manifest let manifest = Manifest::parse_string(manifest_string).expect("Failed to parse manifest"); - + // Get the user let user = &manifest.users[0]; - + // Check that all services are added with whitespace trimmed - assert!(user.services.contains("ssh"), "Expected 'ssh' service to be added"); - assert!(user.services.contains("ftp"), "Expected 'ftp' service to be added"); - assert!(user.services.contains("http"), "Expected 'http' service to be added"); + assert!( + user.services.contains("ssh"), + "Expected 'ssh' service to be added" + ); + assert!( + user.services.contains("ftp"), + "Expected 'ftp' service to be added" + ); + assert!( + user.services.contains("http"), + "Expected 'http' service to be added" + ); assert_eq!(user.services.len(), 3, "Expected exactly three services"); } @@ -73,31 +97,40 @@ fn test_ftpuser_services_with_whitespace() { fn test_ftpuser_empty_string() { // Create a manifest string with ftpuser= let manifest_string = "user ftpuser=".to_string(); - + // Parse the manifest let manifest = Manifest::parse_string(manifest_string).expect("Failed to parse manifest"); - + // Get the user let user = &manifest.users[0]; - + // Check that no services are added - assert!(user.services.is_empty(), "Expected no services for empty string"); + assert!( + user.services.is_empty(), + "Expected no services for empty string" + ); } #[test] fn test_ftpuser_with_empty_elements() { // Create a manifest string with ftpuser=ssh,,http let manifest_string = "user ftpuser=ssh,,http".to_string(); - + // Parse the manifest let manifest = Manifest::parse_string(manifest_string).expect("Failed to parse manifest"); - + // Get the user let user = &manifest.users[0]; - + // Check that only non-empty services are added - assert!(user.services.contains("ssh"), "Expected 'ssh' service to be added"); - assert!(user.services.contains("http"), "Expected 'http' service to be added"); + assert!( + user.services.contains("ssh"), + "Expected 'ssh' service to be added" + ); + assert!( + user.services.contains("http"), + "Expected 'http' service to be added" + ); assert_eq!(user.services.len(), 2, "Expected exactly two services"); } @@ -105,13 +138,13 @@ fn test_ftpuser_with_empty_elements() { fn test_real_world_example() { // Create a manifest string similar to the one in postgre-common.manifest let manifest_string = "user username=postgres uid=90 group=postgres home-dir=/var/postgres login-shell=/usr/bin/pfksh password=NP gcos-field=\"PostgreSQL Reserved UID\" ftpuser=false".to_string(); - + // Parse the manifest let manifest = Manifest::parse_string(manifest_string).expect("Failed to parse manifest"); - + // Get the user let user = &manifest.users[0]; - + // Check user properties assert_eq!(user.username, "postgres"); assert_eq!(user.uid, "90"); @@ -120,5 +153,8 @@ fn test_real_world_example() { assert_eq!(user.login_shell, "/usr/bin/pfksh"); assert_eq!(user.password, "NP"); assert_eq!(user.gcos_field, "PostgreSQL Reserved UID"); - assert!(user.services.is_empty(), "Expected no services for ftpuser=false"); -} \ No newline at end of file + assert!( + user.services.is_empty(), + "Expected no services for ftpuser=false" + ); +} diff --git a/pkg6dev/src/error.rs b/pkg6dev/src/error.rs index 2a64b86..005ef3c 100644 --- a/pkg6dev/src/error.rs +++ b/pkg6dev/src/error.rs @@ -41,27 +41,21 @@ pub enum Pkg6DevError { code(ips::pkg6dev::component_path_error), help("Ensure the component path exists and is a directory") )] - ComponentPathError { - path: PathBuf, - }, + ComponentPathError { path: PathBuf }, #[error("manifest not found: {path}")] #[diagnostic( code(ips::pkg6dev::manifest_not_found), help("Ensure the manifest file exists at the specified path") )] - ManifestNotFoundError { - path: PathBuf, - }, + ManifestNotFoundError { path: PathBuf }, #[error("replacement format error: {value} is not in the format 'key:value'")] #[diagnostic( code(ips::pkg6dev::replacement_format_error), help("Replacements must be in the format 'key:value'") )] - ReplacementFormatError { - value: String, - }, + ReplacementFormatError { value: String }, // Makefile-related errors #[error("makefile parse error: {message}")] @@ -69,18 +63,14 @@ pub enum Pkg6DevError { code(ips::pkg6dev::makefile_parse_error), help("Check the Makefile syntax and try again") )] - MakefileParseError { - message: String, - }, + MakefileParseError { message: String }, #[error("component info error: {message}")] #[diagnostic( code(ips::pkg6dev::component_info_error), help("Check the component information and try again") )] - ComponentInfoError { - message: String, - }, + ComponentInfoError { message: String }, // Package publishing errors #[error("manifest file not found: {path}")] @@ -88,27 +78,21 @@ pub enum Pkg6DevError { code(ips::pkg6dev::manifest_file_not_found), help("Ensure the manifest file exists at the specified path") )] - ManifestFileNotFoundError { - path: PathBuf, - }, + ManifestFileNotFoundError { path: PathBuf }, #[error("prototype directory not found: {path}")] #[diagnostic( code(ips::pkg6dev::prototype_dir_not_found), help("Ensure the prototype directory exists at the specified path") )] - PrototypeDirNotFoundError { - path: PathBuf, - }, + PrototypeDirNotFoundError { path: PathBuf }, #[error("publisher not found: {publisher}")] #[diagnostic( code(ips::pkg6dev::publisher_not_found), help("Add the publisher to the repository using pkg6repo add-publisher") )] - PublisherNotFoundError { - publisher: String, - }, + PublisherNotFoundError { publisher: String }, #[error("no default publisher set")] #[diagnostic( @@ -122,9 +106,7 @@ pub enum Pkg6DevError { code(ips::pkg6dev::file_not_found_in_prototype), help("Ensure the file exists in the prototype directory") )] - FileNotFoundInPrototypeError { - path: PathBuf, - }, + FileNotFoundInPrototypeError { path: PathBuf }, // Logging environment error #[error("logging environment setup error: {0}")] @@ -162,4 +144,4 @@ impl From for Pkg6DevError { fn from(e: anyhow::Error) -> Self { Pkg6DevError::UserlandError(e.to_string()) } -} \ No newline at end of file +} diff --git a/pkg6dev/src/main.rs b/pkg6dev/src/main.rs index 5910d97..dfe38d2 100644 --- a/pkg6dev/src/main.rs +++ b/pkg6dev/src/main.rs @@ -6,12 +6,12 @@ use libips::repository::{FileBackend, ReadableRepository, WritableRepository}; use error::{Pkg6DevError, Result}; use std::collections::HashMap; -use std::fs::{read_dir, OpenOptions}; +use std::fs::{OpenOptions, read_dir}; use std::io::Write; use std::path::{Path, PathBuf}; use tracing::{debug, info, warn}; -use tracing_subscriber::{fmt, EnvFilter}; use tracing_subscriber::filter::LevelFilter; +use tracing_subscriber::{EnvFilter, fmt}; use userland::repology::find_newest_version; use userland::{Component, Makefile}; @@ -63,8 +63,10 @@ fn main() -> Result<()> { let env_filter = EnvFilter::builder() .with_default_directive(LevelFilter::WARN.into()) .from_env() - .map_err(|e| Pkg6DevError::LoggingEnvError(format!("Failed to parse environment filter: {}", e)))?; - + .map_err(|e| { + Pkg6DevError::LoggingEnvError(format!("Failed to parse environment filter: {}", e)) + })?; + fmt::Subscriber::builder() .with_max_level(tracing::Level::DEBUG) .with_env_filter(env_filter) @@ -73,7 +75,7 @@ fn main() -> Result<()> { .with_ansi(false) .with_writer(std::io::stderr) .init(); - + let cli = App::parse(); match &cli.command { @@ -200,7 +202,8 @@ fn diff_component( .write(true) .truncate(true) .create(true) - .open(output_manifest).map_err(|e| Pkg6DevError::IoError(e))?; + .open(output_manifest) + .map_err(|e| Pkg6DevError::IoError(e))?; for action in missing_files { writeln!(&mut f, "file path={}", action.path).map_err(|e| Pkg6DevError::IoError(e))?; } @@ -228,12 +231,14 @@ fn show_component_info>(component_path: P) -> Result<()> { // Parse Makefile // We'll wrap the anyhow errors with our more specific error types - let initial_makefile = Makefile::parse_single_file(&makefile_path) - .map_err(|e| Pkg6DevError::MakefileParseError { + let initial_makefile = Makefile::parse_single_file(&makefile_path).map_err(|e| { + Pkg6DevError::MakefileParseError { message: format!("Failed to parse Makefile: {}", e), - })?; - - let makefile = initial_makefile.parse_all() + } + })?; + + let makefile = initial_makefile + .parse_all() .map_err(|e| Pkg6DevError::MakefileParseError { message: format!("Failed to parse all Makefiles: {}", e), })?; @@ -241,8 +246,8 @@ fn show_component_info>(component_path: P) -> Result<()> { let mut name = String::new(); // Get component information - let component = Component::new_from_makefile(&makefile) - .map_err(|e| Pkg6DevError::ComponentInfoError { + let component = + Component::new_from_makefile(&makefile).map_err(|e| Pkg6DevError::ComponentInfoError { message: format!("Failed to get component information: {}", e), })?; @@ -259,9 +264,12 @@ fn show_component_info>(component_path: P) -> Result<()> { info!("Version: {}", var.replace('\n', "\n\t")); let latest_version = find_newest_version(&name); if latest_version.is_ok() { - info!("Latest Version: {}", latest_version.map_err(|e| Pkg6DevError::ComponentInfoError { - message: format!("Failed to get latest version: {}", e), - })?); + info!( + "Latest Version: {}", + latest_version.map_err(|e| Pkg6DevError::ComponentInfoError { + message: format!("Failed to get latest version: {}", e), + })? + ); } else { warn!( "Could not get latest version info: {}", @@ -448,14 +456,14 @@ fn publish_package( // Use the default publisher match &repo.config.default_publisher { Some(default_pub) => default_pub.clone(), - None => return Err(Pkg6DevError::NoDefaultPublisherError) + None => return Err(Pkg6DevError::NoDefaultPublisherError), } }; // Begin a transaction info!("Beginning transaction for publisher: {}", publisher_name); let mut transaction = repo.begin_transaction()?; - + // Set the publisher for the transaction transaction.set_publisher(&publisher_name); @@ -470,7 +478,9 @@ fn publish_package( // Check if the file exists if !file_path.exists() { - return Err(Pkg6DevError::FileNotFoundInPrototypeError{path: file_path.clone()}) + return Err(Pkg6DevError::FileNotFoundInPrototypeError { + path: file_path.clone(), + }); } // Add the file to the transaction @@ -485,7 +495,7 @@ fn publish_package( // Commit the transaction info!("Committing transaction..."); transaction.commit()?; - + // Regenerate catalog and search index info!("Regenerating catalog and search index..."); repo.rebuild(Some(&publisher_name), false, false)?; diff --git a/pkg6repo/src/e2e_tests.rs b/pkg6repo/src/e2e_tests.rs index b500dcc..89d81c6 100644 --- a/pkg6repo/src/e2e_tests.rs +++ b/pkg6repo/src/e2e_tests.rs @@ -13,7 +13,7 @@ mod e2e_tests { // The base directory for all test repositories const TEST_REPO_BASE_DIR: &str = "/tmp/pkg6repo_e2e_test"; - + // Get the path to the pre-built binaries fn get_bin_dir() -> PathBuf { match env::var("PKG6_TEST_BIN_DIR") { @@ -54,7 +54,7 @@ mod e2e_tests { .args(["run", "-p", "xtask", "--", "setup-test-env"]) .output() .expect("Failed to run xtask setup-test-env"); - + if !output.status.success() { panic!( "Failed to set up test environment: {}", @@ -73,7 +73,7 @@ mod e2e_tests { fn run_pkg6repo(args: &[&str]) -> Result { let bin_dir = get_bin_dir(); let pkg6repo_bin = bin_dir.join("pkg6repo"); - + // Check if the binary exists if !pkg6repo_bin.exists() { return Err(format!( @@ -81,7 +81,7 @@ mod e2e_tests { pkg6repo_bin.display() )); } - + let output = Command::new(pkg6repo_bin) .args(args) .output() @@ -98,7 +98,7 @@ mod e2e_tests { fn run_pkg6dev(args: &[&str]) -> Result { let bin_dir = get_bin_dir(); let pkg6dev_bin = bin_dir.join("pkg6dev"); - + // Check if the binary exists if !pkg6dev_bin.exists() { return Err(format!( @@ -106,7 +106,7 @@ mod e2e_tests { pkg6dev_bin.display() )); } - + let output = Command::new(pkg6dev_bin) .args(args) .output() @@ -161,7 +161,12 @@ mod e2e_tests { ); // Add a publisher using pkg6repo - let result = run_pkg6repo(&["add-publisher", "-s", repo_path.to_str().unwrap(), "example.com"]); + let result = run_pkg6repo(&[ + "add-publisher", + "-s", + repo_path.to_str().unwrap(), + "example.com", + ]); assert!( result.is_ok(), "Failed to add publisher: {:?}", @@ -205,9 +210,12 @@ mod e2e_tests { let manifest_path = manifest_dir.join("example.p5m"); let result = run_pkg6dev(&[ "publish", - "--manifest-path", manifest_path.to_str().unwrap(), - "--prototype-dir", prototype_dir.to_str().unwrap(), - "--repo-path", repo_path.to_str().unwrap(), + "--manifest-path", + manifest_path.to_str().unwrap(), + "--prototype-dir", + prototype_dir.to_str().unwrap(), + "--repo-path", + repo_path.to_str().unwrap(), ]); assert!( result.is_ok(), @@ -262,9 +270,12 @@ mod e2e_tests { let manifest_path = manifest_dir.join("example.p5m"); let result = run_pkg6dev(&[ "publish", - "--manifest-path", manifest_path.to_str().unwrap(), - "--prototype-dir", prototype_dir.to_str().unwrap(), - "--repo-path", repo_path.to_str().unwrap(), + "--manifest-path", + manifest_path.to_str().unwrap(), + "--prototype-dir", + prototype_dir.to_str().unwrap(), + "--repo-path", + repo_path.to_str().unwrap(), ]); assert!( result.is_ok(), @@ -327,9 +338,12 @@ mod e2e_tests { let manifest_path1 = manifest_dir.join("example.p5m"); let result = run_pkg6dev(&[ "publish", - "--manifest-path", manifest_path1.to_str().unwrap(), - "--prototype-dir", prototype_dir.to_str().unwrap(), - "--repo-path", repo_path.to_str().unwrap(), + "--manifest-path", + manifest_path1.to_str().unwrap(), + "--prototype-dir", + prototype_dir.to_str().unwrap(), + "--repo-path", + repo_path.to_str().unwrap(), ]); assert!( result.is_ok(), @@ -341,9 +355,12 @@ mod e2e_tests { let manifest_path2 = manifest_dir.join("example2.p5m"); let result = run_pkg6dev(&[ "publish", - "--manifest-path", manifest_path2.to_str().unwrap(), - "--prototype-dir", prototype_dir.to_str().unwrap(), - "--repo-path", repo_path.to_str().unwrap(), + "--manifest-path", + manifest_path2.to_str().unwrap(), + "--prototype-dir", + prototype_dir.to_str().unwrap(), + "--repo-path", + repo_path.to_str().unwrap(), ]); assert!( result.is_ok(), @@ -379,10 +396,13 @@ mod e2e_tests { let sample_repo_path = PathBuf::from(env::current_dir().unwrap()) .join("sample_data") .join("sample-repo"); - + // Check if the sample repository exists if !sample_repo_path.exists() { - println!("Sample pkg5 repository not found at {}, skipping test", sample_repo_path.display()); + println!( + "Sample pkg5 repository not found at {}, skipping test", + sample_repo_path.display() + ); return; } @@ -393,8 +413,10 @@ mod e2e_tests { // Import the pkg5 repository using pkg6repo let result = run_pkg6repo(&[ "import-pkg5", - "--source", sample_repo_path.to_str().unwrap(), - "--destination", repo_path.to_str().unwrap(), + "--source", + sample_repo_path.to_str().unwrap(), + "--destination", + repo_path.to_str().unwrap(), ]); assert!( result.is_ok(), @@ -422,10 +444,7 @@ mod e2e_tests { ); let output = result.unwrap(); - assert!( - !output.is_empty(), - "No packages found in repository" - ); + assert!(!output.is_empty(), "No packages found in repository"); // Clean up cleanup_test_dir(&test_dir); @@ -437,10 +456,13 @@ mod e2e_tests { let sample_p5p_path = PathBuf::from(env::current_dir().unwrap()) .join("sample_data") .join("sample-repo.p5p"); - + // Check if the sample p5p archive exists if !sample_p5p_path.exists() { - println!("Sample pkg5 p5p archive not found at {}, skipping test", sample_p5p_path.display()); + println!( + "Sample pkg5 p5p archive not found at {}, skipping test", + sample_p5p_path.display() + ); return; } @@ -451,8 +473,10 @@ mod e2e_tests { // Import the pkg5 p5p archive using pkg6repo let result = run_pkg6repo(&[ "import-pkg5", - "--source", sample_p5p_path.to_str().unwrap(), - "--destination", repo_path.to_str().unwrap(), + "--source", + sample_p5p_path.to_str().unwrap(), + "--destination", + repo_path.to_str().unwrap(), ]); assert!( result.is_ok(), @@ -480,10 +504,7 @@ mod e2e_tests { ); let output = result.unwrap(); - assert!( - !output.is_empty(), - "No packages found in repository" - ); + assert!(!output.is_empty(), "No packages found in repository"); // Clean up cleanup_test_dir(&test_dir); diff --git a/pkg6repo/src/error.rs b/pkg6repo/src/error.rs index f05b757..6f2857e 100644 --- a/pkg6repo/src/error.rs +++ b/pkg6repo/src/error.rs @@ -56,10 +56,7 @@ pub enum Pkg6RepoError { LoggingEnvError(String), #[error("other error: {0}")] - #[diagnostic( - code(pkg6repo::other_error), - help("See error message for details") - )] + #[diagnostic(code(pkg6repo::other_error), help("See error message for details"))] Other(String), } @@ -75,4 +72,4 @@ impl From<&str> for Pkg6RepoError { fn from(s: &str) -> Self { Pkg6RepoError::Other(s.to_string()) } -} \ No newline at end of file +} diff --git a/pkg6repo/src/main.rs b/pkg6repo/src/main.rs index 1099c5e..590643a 100644 --- a/pkg6repo/src/main.rs +++ b/pkg6repo/src/main.rs @@ -4,13 +4,13 @@ use error::{Pkg6RepoError, Result}; use pkg5_import::Pkg5Importer; use clap::{Parser, Subcommand}; +use libips::repository::{FileBackend, ReadableRepository, RepositoryVersion, WritableRepository}; use serde::Serialize; use std::convert::TryFrom; use std::path::PathBuf; use tracing::{debug, info}; -use tracing_subscriber::{fmt, EnvFilter}; use tracing_subscriber::filter::LevelFilter; -use libips::repository::{FileBackend, ReadableRepository, RepositoryVersion, WritableRepository}; +use tracing_subscriber::{EnvFilter, fmt}; #[cfg(test)] mod e2e_tests; @@ -326,8 +326,10 @@ fn main() -> Result<()> { let env_filter = EnvFilter::builder() .with_default_directive(LevelFilter::WARN.into()) .from_env() - .map_err(|e| Pkg6RepoError::LoggingEnvError(format!("Failed to parse environment filter: {}", e)))?; - + .map_err(|e| { + Pkg6RepoError::LoggingEnvError(format!("Failed to parse environment filter: {}", e)) + })?; + fmt::Subscriber::builder() .with_max_level(tracing::Level::DEBUG) .with_env_filter(env_filter) @@ -336,7 +338,7 @@ fn main() -> Result<()> { .with_ansi(false) .with_writer(std::io::stderr) .init(); - + let cli = App::parse(); match &cli.command { @@ -525,7 +527,9 @@ fn main() -> Result<()> { } } _ => { - return Err(Pkg6RepoError::UnsupportedOutputFormat(output_format.to_string())); + return Err(Pkg6RepoError::UnsupportedOutputFormat( + output_format.to_string(), + )); } } @@ -618,7 +622,9 @@ fn main() -> Result<()> { } } _ => { - return Err(Pkg6RepoError::UnsupportedOutputFormat(output_format.to_string())); + return Err(Pkg6RepoError::UnsupportedOutputFormat( + output_format.to_string(), + )); } } @@ -727,7 +733,9 @@ fn main() -> Result<()> { } } _ => { - return Err(Pkg6RepoError::UnsupportedOutputFormat(output_format.to_string())); + return Err(Pkg6RepoError::UnsupportedOutputFormat( + output_format.to_string(), + )); } } @@ -933,7 +941,9 @@ fn main() -> Result<()> { // Split the property=value string let parts: Vec<&str> = prop_val.split('=').collect(); if parts.len() != 2 { - return Err(Pkg6RepoError::InvalidPropertyValueFormat(prop_val.to_string())); + return Err(Pkg6RepoError::InvalidPropertyValueFormat( + prop_val.to_string(), + )); } let property = parts[0]; @@ -1052,7 +1062,9 @@ fn main() -> Result<()> { } } _ => { - return Err(Pkg6RepoError::UnsupportedOutputFormat(output_format.to_string())); + return Err(Pkg6RepoError::UnsupportedOutputFormat( + output_format.to_string(), + )); } } @@ -1063,16 +1075,20 @@ fn main() -> Result<()> { destination, publisher, } => { - info!("Importing pkg5 repository from {} to {}", source.display(), destination.display()); - + info!( + "Importing pkg5 repository from {} to {}", + source.display(), + destination.display() + ); + // Create a new Pkg5Importer let mut importer = Pkg5Importer::new(source, destination)?; - + // Import the repository importer.import(publisher.as_deref())?; - + info!("Repository imported successfully"); Ok(()) } } -} \ No newline at end of file +} diff --git a/pkg6repo/src/pkg5_import.rs b/pkg6repo/src/pkg5_import.rs index 23e9b6f..15f234d 100644 --- a/pkg6repo/src/pkg5_import.rs +++ b/pkg6repo/src/pkg5_import.rs @@ -1,5 +1,5 @@ use crate::error::{Pkg6RepoError, Result}; -use libips::actions::{Manifest}; +use libips::actions::Manifest; use libips::repository::{FileBackend, ReadableRepository, WritableRepository}; use std::fs::{self, File}; use std::io::{Read, Seek}; @@ -25,7 +25,11 @@ impl Pkg5Importer { let source_path = source_path.as_ref().to_path_buf(); let dest_path = dest_path.as_ref().to_path_buf(); - debug!("Creating Pkg5Importer with source: {}, destination: {}", source_path.display(), dest_path.display()); + debug!( + "Creating Pkg5Importer with source: {}, destination: {}", + source_path.display(), + dest_path.display() + ); // Check if a source path exists if !source_path.exists() { @@ -38,7 +42,8 @@ impl Pkg5Importer { debug!("Source path exists: {}", source_path.display()); // Determine if a source is a p5p archive - let is_p5p = source_path.is_file() && source_path.extension().map_or(false, |ext| ext == "p5p"); + let is_p5p = + source_path.is_file() && source_path.extension().map_or(false, |ext| ext == "p5p"); debug!("Source is p5p archive: {}", is_p5p); Ok(Self { @@ -56,9 +61,12 @@ impl Pkg5Importer { let temp_dir = tempdir().map_err(|e| { Pkg6RepoError::from(format!("Failed to create temporary directory: {}", e)) })?; - - info!("Extracting p5p archive to temporary directory: {}", temp_dir.path().display()); - + + info!( + "Extracting p5p archive to temporary directory: {}", + temp_dir.path().display() + ); + // Extract the p5p archive to the temporary directory let status = std::process::Command::new("tar") .arg("-xf") @@ -69,18 +77,18 @@ impl Pkg5Importer { .map_err(|e| { Pkg6RepoError::from(format!("Failed to extract p5p archive: {}", e)) })?; - + if !status.success() { return Err(Pkg6RepoError::from(format!( "Failed to extract p5p archive: {}", status ))); } - + // Store the temporary directory let source_path = temp_dir.path().to_path_buf(); self.temp_dir = Some(temp_dir); - + Ok(source_path) } else { // Source is already a directory @@ -91,61 +99,88 @@ impl Pkg5Importer { /// Imports the pkg5 repository pub fn import(&mut self, publisher: Option<&str>) -> Result<()> { debug!("Starting import with publisher: {:?}", publisher); - + // Prepare the source repository debug!("Preparing source repository"); let source_path = self.prepare_source()?; debug!("Source repository prepared: {}", source_path.display()); - + // Check if this is a pkg5 repository let pkg5_repo_file = source_path.join("pkg5.repository"); let pkg5_index_file = source_path.join("pkg5.index.0.gz"); - - debug!("Checking if pkg5.repository exists: {}", pkg5_repo_file.exists()); - debug!("Checking if pkg5.index.0.gz exists: {}", pkg5_index_file.exists()); - + + debug!( + "Checking if pkg5.repository exists: {}", + pkg5_repo_file.exists() + ); + debug!( + "Checking if pkg5.index.0.gz exists: {}", + pkg5_index_file.exists() + ); + if !pkg5_repo_file.exists() && !pkg5_index_file.exists() { - debug!("Source does not appear to be a pkg5 repository: {}", source_path.display()); + debug!( + "Source does not appear to be a pkg5 repository: {}", + source_path.display() + ); return Err(Pkg6RepoError::from(format!( "Source does not appear to be a pkg5 repository: {}", source_path.display() ))); } - + // Open or create the destination repository - debug!("Checking if destination repository exists: {}", self.dest_path.exists()); + debug!( + "Checking if destination repository exists: {}", + self.dest_path.exists() + ); let mut dest_repo = if self.dest_path.exists() { // Check if it's a valid repository by looking for the pkg6.repository file let repo_config_file = self.dest_path.join("pkg6.repository"); - debug!("Checking if repository config file exists: {}", repo_config_file.exists()); - + debug!( + "Checking if repository config file exists: {}", + repo_config_file.exists() + ); + if repo_config_file.exists() { // It's a valid repository, open it info!("Opening existing repository: {}", self.dest_path.display()); - debug!("Attempting to open repository at: {}", self.dest_path.display()); + debug!( + "Attempting to open repository at: {}", + self.dest_path.display() + ); FileBackend::open(&self.dest_path)? } else { // It's not a valid repository, create a new one - info!("Destination exists but is not a valid repository, creating a new one: {}", self.dest_path.display()); - debug!("Attempting to create repository at: {}", self.dest_path.display()); + info!( + "Destination exists but is not a valid repository, creating a new one: {}", + self.dest_path.display() + ); + debug!( + "Attempting to create repository at: {}", + self.dest_path.display() + ); FileBackend::create(&self.dest_path, libips::repository::RepositoryVersion::V4)? } } else { // Destination doesn't exist, create a new repository info!("Creating new repository: {}", self.dest_path.display()); - debug!("Attempting to create repository at: {}", self.dest_path.display()); + debug!( + "Attempting to create repository at: {}", + self.dest_path.display() + ); FileBackend::create(&self.dest_path, libips::repository::RepositoryVersion::V4)? }; - + // Find publishers in the source repository let publishers = self.find_publishers(&source_path)?; - + if publishers.is_empty() { return Err(Pkg6RepoError::from( - "No publishers found in source repository".to_string() + "No publishers found in source repository".to_string(), )); } - + // Determine which publisher to import let publisher_to_import = match publisher { Some(pub_name) => { @@ -156,34 +191,42 @@ impl Pkg5Importer { ))); } pub_name - }, + } None => { // Use the first publisher if none specified &publishers[0] } }; - + info!("Importing from publisher: {}", publisher_to_import); - + // Ensure the publisher exists in the destination repository - if !dest_repo.config.publishers.iter().any(|p| p == publisher_to_import) { - info!("Adding publisher to destination repository: {}", publisher_to_import); + if !dest_repo + .config + .publishers + .iter() + .any(|p| p == publisher_to_import) + { + info!( + "Adding publisher to destination repository: {}", + publisher_to_import + ); dest_repo.add_publisher(publisher_to_import)?; - + // Set as the default publisher if there isn't one already if dest_repo.config.default_publisher.is_none() { info!("Setting as default publisher: {}", publisher_to_import); dest_repo.set_default_publisher(publisher_to_import)?; } } - + // Import packages self.import_packages(&source_path, &mut dest_repo, publisher_to_import)?; - + // Rebuild catalog and search index info!("Rebuilding catalog and search index..."); dest_repo.rebuild(Some(publisher_to_import), false, false)?; - + info!("Import completed successfully"); Ok(()) } @@ -191,88 +234,87 @@ impl Pkg5Importer { /// Finds publishers in the source repository fn find_publishers(&self, source_path: &Path) -> Result> { let publisher_dir = source_path.join("publisher"); - + if !publisher_dir.exists() || !publisher_dir.is_dir() { return Err(Pkg6RepoError::from(format!( "Publisher directory not found: {}", publisher_dir.display() ))); } - + let mut publishers = Vec::new(); - - for entry in fs::read_dir(&publisher_dir).map_err(|e| { - Pkg6RepoError::IoError(e) - })? { - let entry = entry.map_err(|e| { - Pkg6RepoError::IoError(e) - })?; - + + for entry in fs::read_dir(&publisher_dir).map_err(|e| Pkg6RepoError::IoError(e))? { + let entry = entry.map_err(|e| Pkg6RepoError::IoError(e))?; + let path = entry.path(); - + if path.is_dir() { let publisher = path.file_name().unwrap().to_string_lossy().to_string(); publishers.push(publisher); } } - + Ok(publishers) } /// Imports packages from the source repository - fn import_packages(&self, source_path: &Path, dest_repo: &mut FileBackend, publisher: &str) -> Result<()> { + fn import_packages( + &self, + source_path: &Path, + dest_repo: &mut FileBackend, + publisher: &str, + ) -> Result<()> { let pkg_dir = source_path.join("publisher").join(publisher).join("pkg"); - + if !pkg_dir.exists() || !pkg_dir.is_dir() { return Err(Pkg6RepoError::from(format!( "Package directory not found: {}", pkg_dir.display() ))); } - + // Create a temporary directory for extracted files let temp_proto_dir = tempdir().map_err(|e| { - Pkg6RepoError::from(format!("Failed to create temporary prototype directory: {}", e)) + Pkg6RepoError::from(format!( + "Failed to create temporary prototype directory: {}", + e + )) })?; - - info!("Created temporary prototype directory: {}", temp_proto_dir.path().display()); - + + info!( + "Created temporary prototype directory: {}", + temp_proto_dir.path().display() + ); + // Find package directories let mut package_count = 0; - - for pkg_entry in fs::read_dir(&pkg_dir).map_err(|e| { - Pkg6RepoError::IoError(e) - })? { - let pkg_entry = pkg_entry.map_err(|e| { - Pkg6RepoError::IoError(e) - })?; - + + for pkg_entry in fs::read_dir(&pkg_dir).map_err(|e| Pkg6RepoError::IoError(e))? { + let pkg_entry = pkg_entry.map_err(|e| Pkg6RepoError::IoError(e))?; + let pkg_path = pkg_entry.path(); - + if pkg_path.is_dir() { // This is a package directory let pkg_name = pkg_path.file_name().unwrap().to_string_lossy().to_string(); let decoded_pkg_name = url_decode(&pkg_name); - + debug!("Processing package: {}", decoded_pkg_name); - + // Find package versions - for ver_entry in fs::read_dir(&pkg_path).map_err(|e| { - Pkg6RepoError::IoError(e) - })? { - let ver_entry = ver_entry.map_err(|e| { - Pkg6RepoError::IoError(e) - })?; - + for ver_entry in fs::read_dir(&pkg_path).map_err(|e| Pkg6RepoError::IoError(e))? { + let ver_entry = ver_entry.map_err(|e| Pkg6RepoError::IoError(e))?; + let ver_path = ver_entry.path(); - + if ver_path.is_file() { // This is a package version let ver_name = ver_path.file_name().unwrap().to_string_lossy().to_string(); let decoded_ver_name = url_decode(&ver_name); - + debug!("Processing version: {}", decoded_ver_name); - + // Import this package version self.import_package_version( source_path, @@ -283,13 +325,13 @@ impl Pkg5Importer { &decoded_ver_name, temp_proto_dir.path(), )?; - + package_count += 1; } } } } - + info!("Imported {} packages", package_count); Ok(()) } @@ -306,115 +348,118 @@ impl Pkg5Importer { proto_dir: &Path, ) -> Result<()> { debug!("Importing package version from {}", manifest_path.display()); - + // Extract package name from FMRI debug!("Extracted package name from FMRI: {}", pkg_name); - + // Read the manifest file content - debug!("Reading manifest file content from {}", manifest_path.display()); + debug!( + "Reading manifest file content from {}", + manifest_path.display() + ); let manifest_content = fs::read_to_string(manifest_path).map_err(|e| { debug!("Error reading manifest file: {}", e); Pkg6RepoError::IoError(e) })?; - + // Parse the manifest using parse_string debug!("Parsing manifest content"); let manifest = Manifest::parse_string(manifest_content)?; - + // Begin a transaction debug!("Beginning transaction"); let mut transaction = dest_repo.begin_transaction()?; - + // Set the publisher for the transaction debug!("Using specified publisher: {}", publisher); transaction.set_publisher(publisher); - + // Debug the repository structure - debug!("Publisher directory: {}", dest_repo.path.join("pkg").join(publisher).display()); - + debug!( + "Publisher directory: {}", + dest_repo.path.join("pkg").join(publisher).display() + ); + // Extract files referenced in the manifest let file_dir = source_path.join("publisher").join(publisher).join("file"); - + if !file_dir.exists() || !file_dir.is_dir() { return Err(Pkg6RepoError::from(format!( "File directory not found: {}", file_dir.display() ))); } - + // Process file actions for file_action in manifest.files.iter() { // Extract the hash from the file action's payload if let Some(payload) = &file_action.payload { let hash = payload.primary_identifier.hash.clone(); - + // Determine the file path in the source repository let hash_prefix = &hash[0..2]; let file_path = file_dir.join(hash_prefix).join(&hash); - + if !file_path.exists() { - warn!("File not found in source repository: {}", file_path.display()); + warn!( + "File not found in source repository: {}", + file_path.display() + ); continue; } - + // Extract the file to the prototype directory let proto_file_path = proto_dir.join(&file_action.path); - + // Create parent directories if they don't exist if let Some(parent) = proto_file_path.parent() { - fs::create_dir_all(parent).map_err(|e| { - Pkg6RepoError::IoError(e) - })?; + fs::create_dir_all(parent).map_err(|e| Pkg6RepoError::IoError(e))?; } - + // Extract the gzipped file - let mut source_file = File::open(&file_path).map_err(|e| { - Pkg6RepoError::IoError(e) - })?; - - let mut dest_file = File::create(&proto_file_path).map_err(|e| { - Pkg6RepoError::IoError(e) - })?; - + let mut source_file = + File::open(&file_path).map_err(|e| Pkg6RepoError::IoError(e))?; + + let mut dest_file = + File::create(&proto_file_path).map_err(|e| Pkg6RepoError::IoError(e))?; + // Check if the file is gzipped let mut header = [0; 2]; - source_file.read_exact(&mut header).map_err(|e| { - Pkg6RepoError::IoError(e) - })?; - + source_file + .read_exact(&mut header) + .map_err(|e| Pkg6RepoError::IoError(e))?; + // Reset file position - source_file.seek(std::io::SeekFrom::Start(0)).map_err(|e| { - Pkg6RepoError::IoError(e) - })?; - + source_file + .seek(std::io::SeekFrom::Start(0)) + .map_err(|e| Pkg6RepoError::IoError(e))?; + if header[0] == 0x1f && header[1] == 0x8b { // File is gzipped, decompress it let mut decoder = flate2::read::GzDecoder::new(source_file); - std::io::copy(&mut decoder, &mut dest_file).map_err(|e| { - Pkg6RepoError::IoError(e) - })?; + std::io::copy(&mut decoder, &mut dest_file) + .map_err(|e| Pkg6RepoError::IoError(e))?; } else { // File is not gzipped, copy it as is - std::io::copy(&mut source_file, &mut dest_file).map_err(|e| { - Pkg6RepoError::IoError(e) - })?; + std::io::copy(&mut source_file, &mut dest_file) + .map_err(|e| Pkg6RepoError::IoError(e))?; } - + // Add the file to the transaction transaction.add_file(file_action.clone(), &proto_file_path)?; } } - + // Update the manifest in the transaction transaction.update_manifest(manifest); - + // The Transaction.commit() method will handle creating necessary directories // and storing the manifest in the correct location, so we don't need to create // package-specific directories here. - + // Commit the transaction transaction.commit()?; - + Ok(()) } } @@ -423,10 +468,10 @@ impl Pkg5Importer { fn url_decode(s: &str) -> String { let mut result = String::new(); let mut i = 0; - + while i < s.len() { if s[i..].starts_with("%") && i + 2 < s.len() { - if let Ok(hex) = u8::from_str_radix(&s[i+1..i+3], 16) { + if let Ok(hex) = u8::from_str_radix(&s[i + 1..i + 3], 16) { result.push(hex as char); i += 3; } else { @@ -438,14 +483,14 @@ fn url_decode(s: &str) -> String { i += 1; } } - + result } #[cfg(test)] mod tests { use super::*; - + #[test] fn test_url_decode() { assert_eq!(url_decode("test"), "test"); @@ -454,4 +499,4 @@ mod tests { assert_eq!(url_decode("test%2Ctest"), "test,test"); assert_eq!(url_decode("test%3Atest"), "test:test"); } -} \ No newline at end of file +} diff --git a/pkg6repo/src/tests.rs b/pkg6repo/src/tests.rs index 877388a..e7d782f 100644 --- a/pkg6repo/src/tests.rs +++ b/pkg6repo/src/tests.rs @@ -1,8 +1,8 @@ #[cfg(test)] mod tests { use libips::repository::{ - FileBackend, ReadableRepository, RepositoryVersion, - WritableRepository, REPOSITORY_CONFIG_FILENAME, + FileBackend, REPOSITORY_CONFIG_FILENAME, ReadableRepository, RepositoryVersion, + WritableRepository, }; use std::fs; use std::path::PathBuf; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 99165a1..74f35db 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -22,45 +22,45 @@ struct Cli { enum Commands { /// Set up the test environment for repository tests SetupTestEnv, - + /// Build the project Build { /// Build with release optimizations #[arg(short, long)] release: bool, - + /// Specific crate to build #[arg(short, long)] package: Option, }, - + /// Run tests Test { /// Run tests with release optimizations #[arg(short, long)] release: bool, - + /// Specific crate to test #[arg(short, long)] package: Option, }, - + /// Build binaries for end-to-end tests BuildE2E, - + /// Run end-to-end tests using pre-built binaries RunE2E { /// Specific test to run (runs all e2e tests if not specified) #[arg(short, long)] test: Option, }, - + /// Format code Fmt, - + /// Run clippy Clippy, - + /// Clean build artifacts Clean, } @@ -83,20 +83,21 @@ fn main() -> Result<()> { /// Set up the test environment for repository tests fn setup_test_env() -> Result<()> { println!("Setting up test environment..."); - + // Clean up any existing test directories except the bin directory if Path::new(TEST_BASE_DIR).exists() { println!("Cleaning up existing test directory..."); - + // Remove subdirectories individually, preserving the bin directory let entries = fs::read_dir(TEST_BASE_DIR).context("Failed to read test directory")?; for entry in entries { let entry = entry.context("Failed to read directory entry")?; let path = entry.path(); - + // Skip the bin directory if path.is_dir() && path.file_name().unwrap_or_default() != "bin" { - fs::remove_dir_all(&path).context(format!("Failed to remove directory: {:?}", path))?; + fs::remove_dir_all(&path) + .context(format!("Failed to remove directory: {:?}", path))?; } else if path.is_file() { fs::remove_file(&path).context(format!("Failed to remove file: {:?}", path))?; } @@ -105,32 +106,38 @@ fn setup_test_env() -> Result<()> { // Create the base directory if it doesn't exist fs::create_dir_all(TEST_BASE_DIR).context("Failed to create test base directory")?; } - + // Create test directories println!("Creating test directories..."); fs::create_dir_all(PROTOTYPE_DIR).context("Failed to create prototype directory")?; fs::create_dir_all(MANIFEST_DIR).context("Failed to create manifest directory")?; - + // Compile the applications println!("Compiling applications..."); Command::new("cargo") .arg("build") .status() .context("Failed to compile applications")?; - + // Create a simple prototype directory structure with some files println!("Creating prototype directory structure..."); - + // Create some directories - fs::create_dir_all(format!("{}/usr/bin", PROTOTYPE_DIR)).context("Failed to create usr/bin directory")?; - fs::create_dir_all(format!("{}/usr/share/doc/example", PROTOTYPE_DIR)).context("Failed to create usr/share/doc/example directory")?; - fs::create_dir_all(format!("{}/etc/config", PROTOTYPE_DIR)).context("Failed to create etc/config directory")?; - + fs::create_dir_all(format!("{}/usr/bin", PROTOTYPE_DIR)) + .context("Failed to create usr/bin directory")?; + fs::create_dir_all(format!("{}/usr/share/doc/example", PROTOTYPE_DIR)) + .context("Failed to create usr/share/doc/example directory")?; + fs::create_dir_all(format!("{}/etc/config", PROTOTYPE_DIR)) + .context("Failed to create etc/config directory")?; + // Create some files let hello_script = "#!/bin/sh\necho 'Hello, World!'"; - let mut hello_file = File::create(format!("{}/usr/bin/hello", PROTOTYPE_DIR)).context("Failed to create hello script")?; - hello_file.write_all(hello_script.as_bytes()).context("Failed to write hello script")?; - + let mut hello_file = File::create(format!("{}/usr/bin/hello", PROTOTYPE_DIR)) + .context("Failed to create hello script")?; + hello_file + .write_all(hello_script.as_bytes()) + .context("Failed to write hello script")?; + // Make the script executable #[cfg(unix)] { @@ -142,17 +149,24 @@ fn setup_test_env() -> Result<()> { fs::set_permissions(format!("{}/usr/bin/hello", PROTOTYPE_DIR), perms) .context("Failed to set hello script permissions")?; } - + let readme_content = "This is an example document."; - let mut readme_file = File::create(format!("{}/usr/share/doc/example/README.txt", PROTOTYPE_DIR)) - .context("Failed to create README.txt")?; - readme_file.write_all(readme_content.as_bytes()).context("Failed to write README.txt")?; - + let mut readme_file = File::create(format!( + "{}/usr/share/doc/example/README.txt", + PROTOTYPE_DIR + )) + .context("Failed to create README.txt")?; + readme_file + .write_all(readme_content.as_bytes()) + .context("Failed to write README.txt")?; + let config_content = "# Example configuration file\nvalue=42"; let mut config_file = File::create(format!("{}/etc/config/example.conf", PROTOTYPE_DIR)) .context("Failed to create example.conf")?; - config_file.write_all(config_content.as_bytes()).context("Failed to write example.conf")?; - + config_file + .write_all(config_content.as_bytes()) + .context("Failed to write example.conf")?; + // Create a simple manifest println!("Creating package manifest..."); let example_manifest = r#"set name=pkg.fmri value=pkg://test/example@1.0.0 @@ -167,11 +181,13 @@ dir path=usr/bin mode=0755 owner=root group=bin dir path=usr/share/doc/example mode=0755 owner=root group=bin dir path=etc/config mode=0755 owner=root group=sys "#; - + let mut example_file = File::create(format!("{}/example.p5m", MANIFEST_DIR)) .context("Failed to create example.p5m")?; - example_file.write_all(example_manifest.as_bytes()).context("Failed to write example.p5m")?; - + example_file + .write_all(example_manifest.as_bytes()) + .context("Failed to write example.p5m")?; + // Create a second manifest for testing multiple packages let example2_manifest = r#"set name=pkg.fmri value=pkg://test/example2@1.0.0 set name=pkg.summary value="Second example package for testing" @@ -183,15 +199,17 @@ file path=usr/share/doc/example/README.txt mode=0644 owner=root group=bin dir path=usr/bin mode=0755 owner=root group=bin dir path=usr/share/doc/example mode=0755 owner=root group=bin "#; - + let mut example2_file = File::create(format!("{}/example2.p5m", MANIFEST_DIR)) .context("Failed to create example2.p5m")?; - example2_file.write_all(example2_manifest.as_bytes()).context("Failed to write example2.p5m")?; - + example2_file + .write_all(example2_manifest.as_bytes()) + .context("Failed to write example2.p5m")?; + println!("Test environment setup complete!"); println!("Prototype directory: {}", PROTOTYPE_DIR); println!("Manifest directory: {}", MANIFEST_DIR); - + Ok(()) } @@ -199,17 +217,17 @@ dir path=usr/share/doc/example mode=0755 owner=root group=bin fn build(release: &bool, package: &Option) -> Result<()> { let mut cmd = Command::new("cargo"); cmd.arg("build"); - + if *release { cmd.arg("--release"); } - + if let Some(pkg) = package { cmd.args(["--package", pkg]); } - + cmd.status().context("Failed to build project")?; - + Ok(()) } @@ -217,17 +235,17 @@ fn build(release: &bool, package: &Option) -> Result<()> { fn test(release: &bool, package: &Option) -> Result<()> { let mut cmd = Command::new("cargo"); cmd.arg("test"); - + if *release { cmd.arg("--release"); } - + if let Some(pkg) = package { cmd.args(["--package", pkg]); } - + cmd.status().context("Failed to run tests")?; - + Ok(()) } @@ -237,17 +255,24 @@ fn fmt() -> Result<()> { .arg("fmt") .status() .context("Failed to format code")?; - + Ok(()) } /// Run clippy fn clippy() -> Result<()> { Command::new("cargo") - .args(["clippy", "--all-targets", "--all-features", "--", "-D", "warnings"]) + .args([ + "clippy", + "--all-targets", + "--all-features", + "--", + "-D", + "warnings", + ]) .status() .context("Failed to run clippy")?; - + Ok(()) } @@ -257,85 +282,85 @@ fn clean() -> Result<()> { .arg("clean") .status() .context("Failed to clean build artifacts")?; - + Ok(()) } /// Build binaries for end-to-end tests fn build_e2e() -> Result<()> { println!("Building binaries for end-to-end tests..."); - + // Create the bin directory if it doesn't exist fs::create_dir_all(E2E_TEST_BIN_DIR).context("Failed to create bin directory")?; - + // Build pkg6repo in release mode println!("Building pkg6repo..."); Command::new("cargo") .args(["build", "--release", "--package", "pkg6repo"]) .status() .context("Failed to build pkg6repo")?; - + // Build pkg6dev in release mode println!("Building pkg6dev..."); Command::new("cargo") .args(["build", "--release", "--package", "pkg6dev"]) .status() .context("Failed to build pkg6dev")?; - + // Copy the binaries to the bin directory let target_dir = PathBuf::from("target/release"); - + println!("Copying binaries to test directory..."); fs::copy( target_dir.join("pkg6repo"), PathBuf::from(E2E_TEST_BIN_DIR).join("pkg6repo"), ) .context("Failed to copy pkg6repo binary")?; - + fs::copy( target_dir.join("pkg6dev"), PathBuf::from(E2E_TEST_BIN_DIR).join("pkg6dev"), ) .context("Failed to copy pkg6dev binary")?; - + println!("End-to-end test binaries built successfully!"); println!("Binaries are located at: {}", E2E_TEST_BIN_DIR); - + Ok(()) } /// Run end-to-end tests using pre-built binaries fn run_e2e(test: &Option) -> Result<()> { println!("Running end-to-end tests..."); - + // Check if the binaries exist let pkg6repo_bin = PathBuf::from(E2E_TEST_BIN_DIR).join("pkg6repo"); let pkg6dev_bin = PathBuf::from(E2E_TEST_BIN_DIR).join("pkg6dev"); - + if !pkg6repo_bin.exists() || !pkg6dev_bin.exists() { println!("Pre-built binaries not found. Building them first..."); build_e2e()?; } - + // Set up the test environment setup_test_env()?; - + // Run the tests let mut cmd = Command::new("cargo"); cmd.arg("test"); - + if let Some(test_name) = test { cmd.args(["--package", "pkg6repo", "--test", "e2e_tests", test_name]); } else { cmd.args(["--package", "pkg6repo", "--test", "e2e_tests"]); } - + // Set the environment variable for the test binaries cmd.env("PKG6_TEST_BIN_DIR", E2E_TEST_BIN_DIR); - + cmd.status().context("Failed to run end-to-end tests")?; - + println!("End-to-end tests completed!"); - + Ok(()) -} \ No newline at end of file +}