Add support for backward compatibility with legacy repository files

- Introduced creation of `pub.p5i` files for publishers to maintain compatibility with older repository formats.
- Implemented saving repository configuration in legacy INI format (`pkg5.repository`).
- Updated tests to validate the generation and structure of legacy files.
- Added new dependencies (`rust-ini`) and updated `Cargo.toml` and `Cargo.lock` accordingly.
This commit is contained in:
Till Wegmueller 2025-08-02 13:17:49 +02:00
parent fc00304038
commit dfe8ac1305
No known key found for this signature in database
4 changed files with 286 additions and 0 deletions

71
Cargo.lock generated
View file

@ -316,6 +316,26 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
dependencies = [
"const-random-macro",
]
[[package]]
name = "const-random-macro"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
"getrandom 0.2.16",
"once_cell",
"tiny-keccak",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -350,6 +370,12 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -432,6 +458,15 @@ dependencies = [
"syn 2.0.104", "syn 2.0.104",
] ]
[[package]]
name = "dlv-list"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
dependencies = [
"const-random",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.15.0" version = "1.15.0"
@ -627,6 +662,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.4" version = "0.15.4"
@ -981,6 +1022,7 @@ dependencies = [
"pest_derive", "pest_derive",
"redb", "redb",
"regex", "regex",
"rust-ini",
"semver", "semver",
"serde", "serde",
"serde_cbor", "serde_cbor",
@ -1321,6 +1363,16 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "ordered-multimap"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
dependencies = [
"dlv-list",
"hashbrown 0.14.5",
]
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.6.1" version = "6.6.1"
@ -1644,6 +1696,16 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rust-ini"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7295b7ce3bf4806b419dc3420745998b447178b7005e2011947b38fc5aa6791"
dependencies = [
"cfg-if",
"ordered-multimap",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.25" version = "0.1.25"
@ -2147,6 +2209,15 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]] [[package]]
name = "tinystr" name = "tinystr"
version = "0.8.1" version = "0.8.1"

View file

@ -39,6 +39,7 @@ tempfile = "3.20.0"
walkdir = "2.4.0" walkdir = "2.4.0"
redb = "1.5.0" redb = "1.5.0"
bincode = "1.3.3" bincode = "1.3.3"
rust-ini = "0.21.2"
[features] [features]
default = ["redb-index"] default = ["redb-index"]

View file

@ -29,6 +29,7 @@ use super::{
PackageContents, PackageInfo, PublisherInfo, ReadableRepository, RepositoryConfig, PackageContents, PackageInfo, PublisherInfo, ReadableRepository, RepositoryConfig,
RepositoryInfo, RepositoryVersion, WritableRepository, REPOSITORY_CONFIG_FILENAME, RepositoryInfo, RepositoryVersion, WritableRepository, REPOSITORY_CONFIG_FILENAME,
}; };
use ini::Ini;
// Define a struct to hold the content vectors for each package // Define a struct to hold the content vectors for each package
struct PackageContentVectors { struct PackageContentVectors {
@ -586,6 +587,31 @@ impl Transaction {
); );
fs::copy(&manifest_path, &pkg_manifest_path)?; fs::copy(&manifest_path, &pkg_manifest_path)?;
// Check if we need to create a pub.p5i file for the publisher
let config_path = self.repo.join(REPOSITORY_CONFIG_FILENAME);
if config_path.exists() {
let config_content = fs::read_to_string(&config_path)?;
let config: RepositoryConfig = serde_json::from_str(&config_content)?;
// Check if this publisher was just added in this transaction
let publisher_dir = self.repo.join("publisher").join(&publisher);
let pub_p5i_path = publisher_dir.join("pub.p5i");
if !pub_p5i_path.exists() {
debug!("Creating pub.p5i file for publisher: {}", publisher);
// Create the pub.p5i file
let repo = FileBackend {
path: self.repo.clone(),
config,
catalog_manager: None,
obsoleted_manager: None,
};
repo.create_pub_p5i_file(&publisher)?;
}
}
// Clean up the transaction directory // Clean up the transaction directory
fs::remove_dir_all(self.path)?; fs::remove_dir_all(self.path)?;
@ -1333,9 +1359,14 @@ impl WritableRepository for FileBackend {
/// Save the repository configuration /// Save the repository configuration
fn save_config(&self) -> Result<()> { fn save_config(&self) -> Result<()> {
// Save the modern JSON format
let config_path = self.path.join(REPOSITORY_CONFIG_FILENAME); let config_path = self.path.join(REPOSITORY_CONFIG_FILENAME);
let config_data = serde_json::to_string_pretty(&self.config)?; let config_data = serde_json::to_string_pretty(&self.config)?;
fs::write(config_path, config_data)?; fs::write(config_path, config_data)?;
// Save the legacy INI format for backward compatibility
self.save_legacy_config()?;
Ok(()) Ok(())
} }
@ -1348,6 +1379,13 @@ impl WritableRepository for FileBackend {
fs::create_dir_all(Self::construct_catalog_path(&self.path, publisher))?; fs::create_dir_all(Self::construct_catalog_path(&self.path, publisher))?;
fs::create_dir_all(Self::construct_package_dir(&self.path, publisher, ""))?; fs::create_dir_all(Self::construct_package_dir(&self.path, publisher, ""))?;
// Create the publisher directory if it doesn't exist
let publisher_dir = self.path.join("publisher").join(publisher);
fs::create_dir_all(&publisher_dir)?;
// Create the pub.p5i file for backward compatibility
self.create_pub_p5i_file(publisher)?;
// Set as the default publisher if no default publisher is set // Set as the default publisher if no default publisher is set
if self.config.default_publisher.is_none() { if self.config.default_publisher.is_none() {
self.config.default_publisher = Some(publisher.to_string()); self.config.default_publisher = Some(publisher.to_string());
@ -1513,6 +1551,81 @@ impl WritableRepository for FileBackend {
} }
impl FileBackend { impl FileBackend {
/// Save the legacy pkg5.repository INI file for backward compatibility
pub fn save_legacy_config(&self) -> Result<()> {
let legacy_config_path = self.path.join("pkg5.repository");
let mut conf = Ini::new();
// Add publisher section with default publisher
if let Some(default_publisher) = &self.config.default_publisher {
conf.with_section(Some("publisher"))
.set("prefix", default_publisher);
}
// Add repository section with version and default values
conf.with_section(Some("repository"))
.set("version", "4")
.set("trust-anchor-directory", "/etc/certs/CA/")
.set("signature-required-names", "[]")
.set("check-certificate-revocation", "False");
// Add CONFIGURATION section with version
conf.with_section(Some("CONFIGURATION"))
.set("version", "4");
// Write the INI file
conf.write_to_file(legacy_config_path)?;
Ok(())
}
/// Create a pub.p5i file for a publisher for backward compatibility
///
/// Format: base_path/publisher/publisher_name/pub.p5i
fn create_pub_p5i_file(&self, publisher: &str) -> Result<()> {
// Define the structure for the pub.p5i file
#[derive(serde::Serialize)]
struct P5iPublisherInfo {
alias: Option<String>,
name: String,
packages: Vec<String>,
repositories: Vec<String>,
}
#[derive(serde::Serialize)]
struct P5iFile {
packages: Vec<String>,
publishers: Vec<P5iPublisherInfo>,
version: u32,
}
// Create the publisher info
let publisher_info = P5iPublisherInfo {
alias: None,
name: publisher.to_string(),
packages: Vec::new(),
repositories: Vec::new(),
};
// Create the p5i file content
let p5i_content = P5iFile {
packages: Vec::new(),
publishers: vec![publisher_info],
version: 1,
};
// Serialize to JSON
let json_content = serde_json::to_string_pretty(&p5i_content)?;
// Create the path for the pub.p5i file
let pub_p5i_path = self.path.join("publisher").join(publisher).join("pub.p5i");
// Write the file
fs::write(pub_p5i_path, json_content)?;
Ok(())
}
/// Helper method to construct a catalog path consistently /// Helper method to construct a catalog path consistently
/// ///
/// Format: base_path/publisher/publisher_name/catalog /// Format: base_path/publisher/publisher_name/catalog

View file

@ -206,6 +206,20 @@ mod tests {
assert!(repo.config.publishers.contains(&"example.com".to_string())); assert!(repo.config.publishers.contains(&"example.com".to_string()));
assert!(FileBackend::construct_catalog_path(&repo_path, "example.com").exists()); assert!(FileBackend::construct_catalog_path(&repo_path, "example.com").exists());
assert!(FileBackend::construct_package_dir(&repo_path, "example.com", "").exists()); assert!(FileBackend::construct_package_dir(&repo_path, "example.com", "").exists());
// Check that the pub.p5i file was created for backward compatibility
let pub_p5i_path = repo_path.join("publisher").join("example.com").join("pub.p5i");
assert!(pub_p5i_path.exists(), "pub.p5i file should be created for backward compatibility");
// Verify the content of the pub.p5i file
let pub_p5i_content = fs::read_to_string(&pub_p5i_path).unwrap();
let pub_p5i_json: serde_json::Value = serde_json::from_str(&pub_p5i_content).unwrap();
// Check the structure of the pub.p5i file
assert_eq!(pub_p5i_json["version"], 1);
assert!(pub_p5i_json["packages"].is_array());
assert!(pub_p5i_json["publishers"].is_array());
assert_eq!(pub_p5i_json["publishers"][0]["name"], "example.com");
// Clean up // Clean up
cleanup_test_dir(&test_dir); cleanup_test_dir(&test_dir);
@ -432,4 +446,91 @@ mod tests {
// Clean up // Clean up
cleanup_test_dir(&test_dir); cleanup_test_dir(&test_dir);
} }
#[test]
fn test_transaction_pub_p5i_creation() {
// Run the setup script to prepare the test environment
let (prototype_dir, manifest_dir) = run_setup_script();
// Create a test directory
let test_dir = create_test_dir("transaction_pub_p5i");
let repo_path = test_dir.join("repo");
// Create a repository
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
// Create a new publisher through a transaction
let publisher = "transaction_test";
// Start a transaction
let mut transaction = repo.begin_transaction().unwrap();
// Set the publisher for the transaction
transaction.set_publisher(publisher);
// Add a simple manifest to the transaction
let manifest_path = manifest_dir.join("example.p5m");
let manifest = Manifest::parse_file(&manifest_path).unwrap();
transaction.update_manifest(manifest);
// Commit the transaction
transaction.commit().unwrap();
// Check that the pub.p5i file was created for the new publisher
let pub_p5i_path = repo_path.join("publisher").join(publisher).join("pub.p5i");
assert!(pub_p5i_path.exists(), "pub.p5i file should be created for new publisher in transaction");
// Verify the content of the pub.p5i file
let pub_p5i_content = fs::read_to_string(&pub_p5i_path).unwrap();
let pub_p5i_json: serde_json::Value = serde_json::from_str(&pub_p5i_content).unwrap();
// Check the structure of the pub.p5i file
assert_eq!(pub_p5i_json["version"], 1);
assert!(pub_p5i_json["packages"].is_array());
assert!(pub_p5i_json["publishers"].is_array());
assert_eq!(pub_p5i_json["publishers"][0]["name"], publisher);
// Clean up
cleanup_test_dir(&test_dir);
}
#[test]
fn test_legacy_pkg5_repository_creation() {
// Create a test directory
let test_dir = create_test_dir("legacy_pkg5_repository");
let repo_path = test_dir.join("repo");
// Create a repository
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
// Add a publisher
let publisher = "openindiana.org";
repo.add_publisher(publisher).unwrap();
// Set as default publisher
repo.set_default_publisher(publisher).unwrap();
// Check that the pkg5.repository file was created
let pkg5_repo_path = repo_path.join("pkg5.repository");
assert!(pkg5_repo_path.exists(), "pkg5.repository file should be created for backward compatibility");
// Verify the content of the pkg5.repository file
let pkg5_content = fs::read_to_string(&pkg5_repo_path).unwrap();
// Print the content for debugging
println!("pkg5.repository content:\n{}", pkg5_content);
// Check that the file contains the expected sections and values
assert!(pkg5_content.contains("[publisher]"));
assert!(pkg5_content.contains("prefix=openindiana.org"));
assert!(pkg5_content.contains("[repository]"));
assert!(pkg5_content.contains("version=4"));
assert!(pkg5_content.contains("trust-anchor-directory=/etc/certs/CA/"));
assert!(pkg5_content.contains("signature-required-names=[]"));
assert!(pkg5_content.contains("check-certificate-revocation=False"));
assert!(pkg5_content.contains("[CONFIGURATION]"));
// Clean up
cleanup_test_dir(&test_dir);
}
} }