Add package publishing functionality and default publisher management

Signed-off-by: Till Wegmueller <toasterson@gmail.com>
This commit is contained in:
Till Wegmueller 2025-07-21 23:20:19 +02:00
parent 8a32bf3176
commit a9584fa6d2
No known key found for this signature in database
4 changed files with 189 additions and 4 deletions

View file

@ -64,6 +64,37 @@ impl Transaction {
}) })
} }
/// Update the manifest in the transaction
///
/// This intelligently merges the provided manifest with the existing one,
/// preserving file actions that have already been added to the transaction.
///
/// The merge strategy:
/// - Keeps all file actions from the transaction's manifest (these have been processed with checksums, etc.)
/// - Adds any file actions from the provided manifest that don't exist in the transaction's manifest
/// - Merges other types of actions (attributes, directories, dependencies, licenses, links) from both manifests
pub fn update_manifest(&mut self, manifest: Manifest) {
// Keep track of file paths that are already in the transaction's manifest
let existing_file_paths: std::collections::HashSet<String> = self.manifest.files
.iter()
.map(|f| f.path.clone())
.collect();
// Add file actions from the provided manifest that don't exist in the transaction's manifest
for file in manifest.files {
if !existing_file_paths.contains(&file.path) {
self.manifest.add_file(file);
}
}
// Merge other types of actions
self.manifest.attributes.extend(manifest.attributes);
self.manifest.directories.extend(manifest.directories);
self.manifest.dependencies.extend(manifest.dependencies);
self.manifest.licenses.extend(manifest.licenses);
self.manifest.links.extend(manifest.links);
}
/// Process a file for the transaction /// Process a file for the transaction
/// ///
/// Takes a FileAction and a path to a file in a prototype directory. /// Takes a FileAction and a path to a file in a prototype directory.
@ -299,6 +330,11 @@ impl Repository for FileBackend {
fs::create_dir_all(self.path.join("catalog").join(publisher))?; fs::create_dir_all(self.path.join("catalog").join(publisher))?;
fs::create_dir_all(self.path.join("pkg").join(publisher))?; fs::create_dir_all(self.path.join("pkg").join(publisher))?;
// Set as default publisher if no default publisher is set
if self.config.default_publisher.is_none() {
self.config.default_publisher = Some(publisher.to_string());
}
// Save the updated configuration // Save the updated configuration
self.save_config()?; self.save_config()?;
} }
@ -417,7 +453,7 @@ impl Repository for FileBackend {
// For each package, list contents // For each package, list contents
let mut contents = Vec::new(); let mut contents = Vec::new();
for (pkg_name, pkg_version, pub_name) in packages { for (pkg_name, pkg_version, _pub_name) in packages {
// Example content data (package, path, type) // Example content data (package, path, type)
let example_contents = vec![ let example_contents = vec![
(format!("{}@{}", pkg_name, pkg_version), "/usr/bin/example".to_string(), "file".to_string()), (format!("{}@{}", pkg_name, pkg_version), "/usr/bin/example".to_string(), "file".to_string()),
@ -504,6 +540,22 @@ impl Repository for FileBackend {
Ok(()) Ok(())
} }
/// Set the default publisher for the repository
fn set_default_publisher(&mut self, publisher: &str) -> Result<()> {
// Check if the publisher exists
if !self.config.publishers.contains(&publisher.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", publisher));
}
// Set the default publisher
self.config.default_publisher = Some(publisher.to_string());
// Save the updated configuration
self.save_config()?;
Ok(())
}
} }
impl FileBackend { impl FileBackend {

View file

@ -45,6 +45,7 @@ pub struct RepositoryConfig {
pub version: RepositoryVersion, pub version: RepositoryVersion,
pub publishers: Vec<String>, pub publishers: Vec<String>,
pub properties: HashMap<String, String>, pub properties: HashMap<String, String>,
pub default_publisher: Option<String>,
} }
impl Default for RepositoryConfig { impl Default for RepositoryConfig {
@ -53,6 +54,7 @@ impl Default for RepositoryConfig {
version: RepositoryVersion::default(), version: RepositoryVersion::default(),
publishers: Vec::new(), publishers: Vec::new(),
properties: HashMap::new(), properties: HashMap::new(),
default_publisher: None,
} }
} }
} }
@ -94,4 +96,7 @@ pub trait Repository {
/// Refresh repository metadata /// Refresh repository metadata
fn refresh(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()>; fn refresh(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()>;
/// Set the default publisher for the repository
fn set_default_publisher(&mut self, publisher: &str) -> Result<()>;
} }

View file

@ -3,9 +3,8 @@
// MPL was not distributed with this file, You can // MPL was not distributed with this file, You can
// obtain one at https://mozilla.org/MPL/2.0/. // obtain one at https://mozilla.org/MPL/2.0/.
use anyhow::{Result, anyhow}; use anyhow::{anyhow, Result};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::collections::HashMap;
use super::{Repository, RepositoryConfig, RepositoryVersion}; use super::{Repository, RepositoryConfig, RepositoryVersion};
@ -299,6 +298,25 @@ impl Repository for RestBackend {
Ok(()) Ok(())
} }
/// Set the default publisher for the repository
fn set_default_publisher(&mut self, publisher: &str) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to set the default publisher
// Check if the publisher exists
if !self.config.publishers.contains(&publisher.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", publisher));
}
// Set the default publisher
self.config.default_publisher = Some(publisher.to_string());
// Save the updated configuration
self.save_config()?;
Ok(())
}
} }
impl RestBackend { impl RestBackend {

View file

@ -1,7 +1,8 @@
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use libips::actions::{ActionError, File, Manifest}; use libips::actions::{ActionError, File, Manifest};
use libips::repository::{Repository, FileBackend};
use anyhow::Result; use anyhow::{Result, anyhow};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::{read_dir, OpenOptions}; use std::fs::{read_dir, OpenOptions};
use std::io::Write; use std::io::Write;
@ -31,6 +32,24 @@ enum Commands {
ShowComponent { ShowComponent {
component: String, component: String,
}, },
/// Publish a package to a repository
Publish {
/// Path to the manifest file
#[clap(short = 'm', long)]
manifest_path: PathBuf,
/// Path to the prototype directory containing the files to publish
#[clap(short = 'p', long)]
prototype_dir: PathBuf,
/// Path to the repository
#[clap(short = 'r', long)]
repo_path: PathBuf,
/// Publisher name (defaults to "test" if not specified)
#[clap(short = 'u', long)]
publisher: Option<String>,
},
} }
fn main() -> Result<()> { fn main() -> Result<()> {
@ -43,6 +62,12 @@ fn main() -> Result<()> {
replacements, replacements,
output_manifest, output_manifest,
} => diff_component(component, replacements, output_manifest), } => diff_component(component, replacements, output_manifest),
Commands::Publish {
manifest_path,
prototype_dir,
repo_path,
publisher,
} => publish_package(manifest_path, prototype_dir, repo_path, publisher),
} }
} }
@ -285,3 +310,88 @@ fn make_file_map(files: Vec<File>) -> HashMap<String, File> {
}) })
.collect() .collect()
} }
/// Publish a package to a repository
///
/// This function:
/// 1. Opens the repository at the specified path
/// 2. Parses the manifest file
/// 3. Uses the FileBackend's publish_files method to publish the files from the prototype directory
fn publish_package(
manifest_path: &PathBuf,
prototype_dir: &PathBuf,
repo_path: &PathBuf,
publisher: &Option<String>,
) -> Result<()> {
// Check if the manifest file exists
if !manifest_path.exists() {
return Err(anyhow!("Manifest file does not exist: {}", manifest_path.display()));
}
// Check if the prototype directory exists
if !prototype_dir.exists() {
return Err(anyhow!("Prototype directory does not exist: {}", prototype_dir.display()));
}
// Parse the manifest file
println!("Parsing manifest file: {}", manifest_path.display());
let manifest = Manifest::parse_file(manifest_path)?;
// Open the repository
println!("Opening repository at: {}", repo_path.display());
let repo = match FileBackend::open(repo_path) {
Ok(repo) => repo,
Err(_) => {
println!("Repository does not exist, creating a new one...");
// Create a new repository with version 4
FileBackend::create(repo_path, libips::repository::RepositoryVersion::V4)?
}
};
// Determine which publisher to use
let publisher_name = if let Some(pub_name) = publisher {
// Use the explicitly specified publisher
if !repo.config.publishers.contains(pub_name) {
return Err(anyhow!("Publisher '{}' does not exist in the repository. Please add it first using pkg6repo add-publisher.", pub_name));
}
pub_name.clone()
} else {
// Use the default publisher
match &repo.config.default_publisher {
Some(default_pub) => default_pub.clone(),
None => return Err(anyhow!("No default publisher set in the repository. Please specify a publisher using the --publisher option or set a default publisher."))
}
};
// Begin a transaction
println!("Beginning transaction for publisher: {}", publisher_name);
let mut transaction = repo.begin_transaction()?;
// Add files from the prototype directory to the transaction
println!("Adding files from prototype directory: {}", prototype_dir.display());
for file_action in manifest.files.iter() {
// Construct the full path to the file in the prototype directory
let file_path = prototype_dir.join(&file_action.path);
// Check if the file exists
if !file_path.exists() {
println!("Warning: File does not exist in prototype directory: {}", file_path.display());
continue;
}
// Add the file to the transaction
println!("Adding file: {}", file_action.path);
transaction.add_file(file_action.clone(), &file_path)?;
}
// Update the manifest in the transaction
println!("Updating manifest in the transaction...");
transaction.update_manifest(manifest);
// Commit the transaction
println!("Committing transaction...");
transaction.commit()?;
println!("Package published successfully!");
Ok(())
}