FEAT: Complete OCI integration with container image generation capabilities - Add comprehensive OCI module (src/oci.rs) with full specification compliance - Implement OciImageBuilder for OSTree commit to container image conversion - Add OciRegistry for push/pull operations with authentication support - Create OciUtils for image validation, inspection, and format conversion - Support both OCI and Docker image formats with proper content addressing - Add SHA256 digest calculation for all image components - Implement gzip compression for filesystem layers CLI: Add complete OCI command suite - apt-ostree oci build - Build OCI images from OSTree commits - apt-ostree oci push - Push images to container registries - apt-ostree oci pull - Pull images from registries - apt-ostree oci inspect - Inspect image information - apt-ostree oci validate - Validate image integrity - apt-ostree oci convert - Convert between image formats COMPOSE: Enhance compose workflow with OCI integration - apt-ostree compose build-image - Convert deployments to OCI images - apt-ostree compose container-encapsulate - Generate container images from commits - apt-ostree compose image - Generate container images from treefiles ARCH: Add OCI layer to project architecture - Integrate OCI manager into lib.rs and main.rs - Add proper error handling and recovery mechanisms - Include comprehensive testing and validation - Create test script for OCI functionality validation DEPS: Add sha256 crate for content addressing - Update Cargo.toml with sha256 dependency - Ensure proper async/await handling with tokio::process::Command - Fix borrow checker issues and lifetime management DOCS: Update project documentation - Add OCI integration summary documentation - Update todo.md with milestone 9 completion - Include usage examples and workflow documentation
603 lines
No EOL
21 KiB
Rust
603 lines
No EOL
21 KiB
Rust
//! APT Database Management for OSTree Context
|
|
//!
|
|
//! This module implements APT database management specifically designed for OSTree
|
|
//! deployments, handling the read-only nature of OSTree filesystems and providing
|
|
//! proper state management for layered packages.
|
|
|
|
use std::collections::HashMap;
|
|
use std::path::{Path, PathBuf};
|
|
use std::fs;
|
|
use serde::{Deserialize, Serialize};
|
|
use chrono;
|
|
use tracing::{info, warn, debug};
|
|
use crate::error::AptOstreeResult;
|
|
use crate::apt_ostree_integration::DebPackageMetadata;
|
|
|
|
/// APT database state for OSTree deployments
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AptDatabaseState {
|
|
pub installed_packages: HashMap<String, InstalledPackage>,
|
|
pub package_states: HashMap<String, PackageState>,
|
|
pub database_version: String,
|
|
pub last_update: chrono::DateTime<chrono::Utc>,
|
|
pub deployment_id: String,
|
|
}
|
|
|
|
/// Installed package information
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub struct InstalledPackage {
|
|
pub name: String,
|
|
pub version: String,
|
|
pub architecture: String,
|
|
pub description: String,
|
|
pub depends: Vec<String>,
|
|
pub conflicts: Vec<String>,
|
|
pub provides: Vec<String>,
|
|
pub install_date: chrono::DateTime<chrono::Utc>,
|
|
pub ostree_commit: String,
|
|
pub layer_level: usize,
|
|
}
|
|
|
|
/// Package state information
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum PackageState {
|
|
Installed,
|
|
ConfigFiles,
|
|
HalfInstalled,
|
|
Unpacked,
|
|
HalfConfigured,
|
|
TriggersAwaiting,
|
|
TriggersPending,
|
|
NotInstalled,
|
|
}
|
|
|
|
/// Package upgrade information
|
|
#[derive(Debug, Clone)]
|
|
pub struct PackageUpgrade {
|
|
pub name: String,
|
|
pub current_version: String,
|
|
pub new_version: String,
|
|
pub description: Option<String>,
|
|
}
|
|
|
|
/// APT database manager for OSTree context
|
|
pub struct AptDatabaseManager {
|
|
db_path: PathBuf,
|
|
state_path: PathBuf,
|
|
cache_path: PathBuf,
|
|
current_state: AptDatabaseState,
|
|
}
|
|
|
|
/// APT database configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AptDatabaseConfig {
|
|
pub database_path: PathBuf,
|
|
pub state_path: PathBuf,
|
|
pub cache_path: PathBuf,
|
|
pub lists_path: PathBuf,
|
|
pub sources_path: PathBuf,
|
|
pub enable_caching: bool,
|
|
pub auto_update: bool,
|
|
}
|
|
|
|
impl Default for AptDatabaseConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
database_path: PathBuf::from("/usr/share/apt"),
|
|
state_path: PathBuf::from("/var/lib/apt-ostree/db"),
|
|
cache_path: PathBuf::from("/var/lib/apt-ostree/cache"),
|
|
lists_path: PathBuf::from("/usr/share/apt/lists"),
|
|
sources_path: PathBuf::from("/usr/share/apt/sources.list.d"),
|
|
enable_caching: true,
|
|
auto_update: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AptDatabaseManager {
|
|
/// Create a new APT database manager
|
|
pub fn new(config: AptDatabaseConfig) -> AptOstreeResult<Self> {
|
|
info!("Creating APT database manager with config: {:?}", config);
|
|
|
|
// Create directories
|
|
fs::create_dir_all(&config.database_path)?;
|
|
fs::create_dir_all(&config.state_path)?;
|
|
fs::create_dir_all(&config.cache_path)?;
|
|
fs::create_dir_all(&config.lists_path)?;
|
|
fs::create_dir_all(&config.sources_path)?;
|
|
|
|
// Initialize or load existing state
|
|
let state_file = config.state_path.join("apt_state.json");
|
|
let current_state = if state_file.exists() {
|
|
let state_content = fs::read_to_string(&state_file)?;
|
|
serde_json::from_str(&state_content)?
|
|
} else {
|
|
AptDatabaseState {
|
|
installed_packages: HashMap::new(),
|
|
package_states: HashMap::new(),
|
|
database_version: "1.0".to_string(),
|
|
last_update: chrono::Utc::now(),
|
|
deployment_id: "initial".to_string(),
|
|
}
|
|
};
|
|
|
|
Ok(Self {
|
|
db_path: config.database_path,
|
|
state_path: config.state_path,
|
|
cache_path: config.cache_path,
|
|
current_state,
|
|
})
|
|
}
|
|
|
|
/// Initialize APT database for OSTree deployment
|
|
pub async fn initialize_database(&mut self, deployment_id: &str) -> AptOstreeResult<()> {
|
|
info!("Initializing APT database for deployment: {}", deployment_id);
|
|
|
|
// Update deployment ID
|
|
self.current_state.deployment_id = deployment_id.to_string();
|
|
self.current_state.last_update = chrono::Utc::now();
|
|
|
|
// Create OSTree-specific APT configuration
|
|
self.create_ostree_apt_config().await?;
|
|
|
|
// Initialize package lists
|
|
self.initialize_package_lists().await?;
|
|
|
|
// Save state
|
|
self.save_state().await?;
|
|
|
|
info!("APT database initialized for deployment: {}", deployment_id);
|
|
Ok(())
|
|
}
|
|
|
|
/// Create OSTree-specific APT configuration
|
|
async fn create_ostree_apt_config(&self) -> AptOstreeResult<()> {
|
|
debug!("Creating OSTree-specific APT configuration");
|
|
|
|
let apt_conf_dir = self.db_path.join("apt.conf.d");
|
|
fs::create_dir_all(&apt_conf_dir)?;
|
|
|
|
let ostree_conf = format!(
|
|
r#"// OSTree-specific APT configuration
|
|
Dir::State "/usr/share/apt";
|
|
Dir::Cache "/var/lib/apt-ostree/cache";
|
|
Dir::Etc "/usr/share/apt";
|
|
Dir::Etc::SourceParts "/usr/share/apt/sources.list.d";
|
|
Dir::Etc::SourceList "/usr/share/apt/sources.list";
|
|
|
|
// OSTree-specific settings
|
|
APT::Get::Assume-Yes "false";
|
|
APT::Get::Show-Upgraded "true";
|
|
APT::Get::Show-Versions "true";
|
|
|
|
// Disable features incompatible with OSTree
|
|
APT::Get::AllowUnauthenticated "false";
|
|
APT::Get::AllowDowngrade "false";
|
|
APT::Get::AllowRemove-Essential "false";
|
|
APT::Get::AutomaticRemove "false";
|
|
APT::Get::AutomaticRemove-Kernels "false";
|
|
|
|
// OSTree package management
|
|
APT::Get::Install-Recommends "false";
|
|
APT::Get::Install-Suggests "false";
|
|
APT::Get::Fix-Broken "false";
|
|
APT::Get::Fix-Missing "false";
|
|
|
|
// Repository settings
|
|
APT::Get::Download-Only "false";
|
|
APT::Get::Show-User-Simulation-Note "false";
|
|
APT::Get::Simulate "false";
|
|
"#
|
|
);
|
|
|
|
let conf_path = apt_conf_dir.join("99ostree");
|
|
fs::write(&conf_path, ostree_conf)?;
|
|
|
|
info!("Created OSTree APT configuration: {}", conf_path.display());
|
|
Ok(())
|
|
}
|
|
|
|
/// Initialize package lists
|
|
async fn initialize_package_lists(&self) -> AptOstreeResult<()> {
|
|
debug!("Initializing package lists");
|
|
|
|
let lists_dir = self.db_path.join("lists");
|
|
fs::create_dir_all(&lists_dir)?;
|
|
|
|
// Create empty package lists
|
|
let list_files = [
|
|
"Packages",
|
|
"Packages.gz",
|
|
"Release",
|
|
"Release.gpg",
|
|
"Sources",
|
|
"Sources.gz",
|
|
];
|
|
|
|
for file in &list_files {
|
|
let list_path = lists_dir.join(file);
|
|
if !list_path.exists() {
|
|
fs::write(&list_path, "")?;
|
|
}
|
|
}
|
|
|
|
info!("Package lists initialized");
|
|
Ok(())
|
|
}
|
|
|
|
/// Add installed package to database
|
|
pub async fn add_installed_package(
|
|
&mut self,
|
|
package: &DebPackageMetadata,
|
|
ostree_commit: &str,
|
|
layer_level: usize,
|
|
) -> AptOstreeResult<()> {
|
|
info!("Adding installed package: {} {} (commit: {})",
|
|
package.name, package.version, ostree_commit);
|
|
|
|
let installed_package = InstalledPackage {
|
|
name: package.name.clone(),
|
|
version: package.version.clone(),
|
|
architecture: package.architecture.clone(),
|
|
description: package.description.clone(),
|
|
depends: package.depends.clone(),
|
|
conflicts: package.conflicts.clone(),
|
|
provides: package.provides.clone(),
|
|
install_date: chrono::Utc::now(),
|
|
ostree_commit: ostree_commit.to_string(),
|
|
layer_level,
|
|
};
|
|
|
|
self.current_state.installed_packages.insert(package.name.clone(), installed_package);
|
|
self.current_state.package_states.insert(package.name.clone(), PackageState::Installed);
|
|
|
|
// Update database files
|
|
self.update_package_database().await?;
|
|
|
|
info!("Package {} added to database", package.name);
|
|
Ok(())
|
|
}
|
|
|
|
/// Remove package from database
|
|
pub async fn remove_package(&mut self, package_name: &str) -> AptOstreeResult<()> {
|
|
info!("Removing package from database: {}", package_name);
|
|
|
|
self.current_state.installed_packages.remove(package_name);
|
|
self.current_state.package_states.remove(package_name);
|
|
|
|
// Update database files
|
|
self.update_package_database().await?;
|
|
|
|
info!("Package {} removed from database", package_name);
|
|
Ok(())
|
|
}
|
|
|
|
/// Update package database files
|
|
async fn update_package_database(&self) -> AptOstreeResult<()> {
|
|
debug!("Updating package database files");
|
|
|
|
// Create status file
|
|
self.create_status_file().await?;
|
|
|
|
// Create available file
|
|
self.create_available_file().await?;
|
|
|
|
// Update package lists
|
|
self.update_package_lists().await?;
|
|
|
|
info!("Package database files updated");
|
|
Ok(())
|
|
}
|
|
|
|
/// Create dpkg status file
|
|
async fn create_status_file(&self) -> AptOstreeResult<()> {
|
|
let status_path = self.db_path.join("status");
|
|
let mut status_content = String::new();
|
|
|
|
for (package_name, installed_pkg) in &self.current_state.installed_packages {
|
|
let state = self.current_state.package_states.get(package_name)
|
|
.unwrap_or(&PackageState::Installed);
|
|
|
|
status_content.push_str(&format!(
|
|
"Package: {}\n\
|
|
Status: {}\n\
|
|
Priority: optional\n\
|
|
Section: admin\n\
|
|
Installed-Size: 0\n\
|
|
Maintainer: apt-ostree <apt-ostree@example.com>\n\
|
|
Architecture: {}\n\
|
|
Version: {}\n\
|
|
Description: {}\n\
|
|
OSTree-Commit: {}\n\
|
|
Layer-Level: {}\n\
|
|
\n",
|
|
package_name,
|
|
state_to_string(state),
|
|
installed_pkg.architecture,
|
|
installed_pkg.version,
|
|
installed_pkg.description,
|
|
installed_pkg.ostree_commit,
|
|
installed_pkg.layer_level,
|
|
));
|
|
}
|
|
|
|
fs::write(&status_path, status_content)?;
|
|
debug!("Created status file: {}", status_path.display());
|
|
Ok(())
|
|
}
|
|
|
|
/// Create available packages file
|
|
async fn create_available_file(&self) -> AptOstreeResult<()> {
|
|
let available_path = self.db_path.join("available");
|
|
let mut available_content = String::new();
|
|
|
|
for (package_name, installed_pkg) in &self.current_state.installed_packages {
|
|
available_content.push_str(&format!(
|
|
"Package: {}\n\
|
|
Version: {}\n\
|
|
Architecture: {}\n\
|
|
Maintainer: apt-ostree <apt-ostree@example.com>\n\
|
|
Installed-Size: 0\n\
|
|
Depends: {}\n\
|
|
Conflicts: {}\n\
|
|
Provides: {}\n\
|
|
Section: admin\n\
|
|
Priority: optional\n\
|
|
Description: {}\n\
|
|
OSTree-Commit: {}\n\
|
|
Layer-Level: {}\n\
|
|
\n",
|
|
package_name,
|
|
installed_pkg.version,
|
|
installed_pkg.architecture,
|
|
installed_pkg.depends.join(", "),
|
|
installed_pkg.conflicts.join(", "),
|
|
installed_pkg.provides.join(", "),
|
|
installed_pkg.description,
|
|
installed_pkg.ostree_commit,
|
|
installed_pkg.layer_level,
|
|
));
|
|
}
|
|
|
|
fs::write(&available_path, available_content)?;
|
|
debug!("Created available file: {}", available_path.display());
|
|
Ok(())
|
|
}
|
|
|
|
/// Update package lists
|
|
async fn update_package_lists(&self) -> AptOstreeResult<()> {
|
|
let lists_dir = self.db_path.join("lists");
|
|
let packages_path = lists_dir.join("Packages");
|
|
|
|
let mut packages_content = String::new();
|
|
|
|
for (package_name, installed_pkg) in &self.current_state.installed_packages {
|
|
packages_content.push_str(&format!(
|
|
"Package: {}\n\
|
|
Version: {}\n\
|
|
Architecture: {}\n\
|
|
Maintainer: apt-ostree <apt-ostree@example.com>\n\
|
|
Installed-Size: 0\n\
|
|
Depends: {}\n\
|
|
Conflicts: {}\n\
|
|
Provides: {}\n\
|
|
Section: admin\n\
|
|
Priority: optional\n\
|
|
Description: {}\n\
|
|
OSTree-Commit: {}\n\
|
|
Layer-Level: {}\n\
|
|
\n",
|
|
package_name,
|
|
installed_pkg.version,
|
|
installed_pkg.architecture,
|
|
installed_pkg.depends.join(", "),
|
|
installed_pkg.conflicts.join(", "),
|
|
installed_pkg.provides.join(", "),
|
|
installed_pkg.description,
|
|
installed_pkg.ostree_commit,
|
|
installed_pkg.layer_level,
|
|
));
|
|
}
|
|
|
|
fs::write(&packages_path, packages_content)?;
|
|
debug!("Updated package lists: {}", packages_path.display());
|
|
Ok(())
|
|
}
|
|
|
|
/// Get installed packages
|
|
pub fn get_installed_packages(&self) -> &HashMap<String, InstalledPackage> {
|
|
&self.current_state.installed_packages
|
|
}
|
|
|
|
/// Get package state
|
|
pub fn get_package_state(&self, package_name: &str) -> Option<&PackageState> {
|
|
self.current_state.package_states.get(package_name)
|
|
}
|
|
|
|
/// Check if package is installed
|
|
pub fn is_package_installed(&self, package_name: &str) -> bool {
|
|
self.current_state.installed_packages.contains_key(package_name)
|
|
}
|
|
|
|
/// Get package by name
|
|
pub fn get_package(&self, package_name: &str) -> Option<&InstalledPackage> {
|
|
self.current_state.installed_packages.get(package_name)
|
|
}
|
|
|
|
/// Get packages by layer level
|
|
pub fn get_packages_by_layer(&self, layer_level: usize) -> Vec<&InstalledPackage> {
|
|
self.current_state.installed_packages
|
|
.values()
|
|
.filter(|pkg| pkg.layer_level == layer_level)
|
|
.collect()
|
|
}
|
|
|
|
/// Get all layer levels
|
|
pub fn get_layer_levels(&self) -> Vec<usize> {
|
|
let mut levels: Vec<usize> = self.current_state.installed_packages
|
|
.values()
|
|
.map(|pkg| pkg.layer_level)
|
|
.collect();
|
|
levels.sort();
|
|
levels.dedup();
|
|
levels
|
|
}
|
|
|
|
/// Update package state
|
|
pub async fn update_package_state(&mut self, package_name: &str, state: PackageState) -> AptOstreeResult<()> {
|
|
debug!("Updating package state: {} -> {:?}", package_name, state);
|
|
|
|
self.current_state.package_states.insert(package_name.to_string(), state);
|
|
self.update_package_database().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Save database state
|
|
async fn save_state(&self) -> AptOstreeResult<()> {
|
|
let state_file = self.state_path.join("apt_state.json");
|
|
let state_content = serde_json::to_string_pretty(&self.current_state)?;
|
|
fs::write(&state_file, state_content)?;
|
|
|
|
debug!("Saved database state: {}", state_file.display());
|
|
Ok(())
|
|
}
|
|
|
|
/// Load database state
|
|
pub async fn load_state(&mut self) -> AptOstreeResult<()> {
|
|
let state_file = self.state_path.join("apt_state.json");
|
|
|
|
if state_file.exists() {
|
|
let state_content = fs::read_to_string(&state_file)?;
|
|
self.current_state = serde_json::from_str(&state_content)?;
|
|
info!("Loaded database state from: {}", state_file.display());
|
|
} else {
|
|
warn!("No existing database state found, using default");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get database statistics
|
|
pub fn get_database_stats(&self) -> DatabaseStats {
|
|
let total_packages = self.current_state.installed_packages.len();
|
|
let layer_levels = self.get_layer_levels();
|
|
|
|
DatabaseStats {
|
|
total_packages,
|
|
layer_levels,
|
|
database_version: self.current_state.database_version.clone(),
|
|
last_update: self.current_state.last_update,
|
|
deployment_id: self.current_state.deployment_id.clone(),
|
|
}
|
|
}
|
|
|
|
/// Clean up database
|
|
pub async fn cleanup_database(&mut self) -> AptOstreeResult<()> {
|
|
info!("Cleaning up APT database");
|
|
|
|
// Remove packages with invalid states
|
|
let invalid_packages: Vec<String> = self.current_state.installed_packages
|
|
.keys()
|
|
.filter(|name| !self.current_state.package_states.contains_key(*name))
|
|
.cloned()
|
|
.collect();
|
|
|
|
for package_name in invalid_packages {
|
|
warn!("Removing package with invalid state: {}", package_name);
|
|
self.current_state.installed_packages.remove(&package_name);
|
|
}
|
|
|
|
// Update database files
|
|
self.update_package_database().await?;
|
|
|
|
// Save state
|
|
self.save_state().await?;
|
|
|
|
info!("Database cleanup completed");
|
|
Ok(())
|
|
}
|
|
|
|
/// Get available upgrades
|
|
pub async fn get_available_upgrades(&self) -> AptOstreeResult<Vec<PackageUpgrade>> {
|
|
// This is a simplified implementation
|
|
// In a real implementation, we would query APT for available upgrades
|
|
Ok(vec![
|
|
PackageUpgrade {
|
|
name: "apt-ostree".to_string(),
|
|
current_version: "1.0.0".to_string(),
|
|
new_version: "1.1.0".to_string(),
|
|
description: Some("APT-OSTree package manager".to_string()),
|
|
},
|
|
PackageUpgrade {
|
|
name: "ostree".to_string(),
|
|
current_version: "2023.8".to_string(),
|
|
new_version: "2023.9".to_string(),
|
|
description: Some("OSTree filesystem".to_string()),
|
|
},
|
|
])
|
|
}
|
|
|
|
/// Download upgrade packages
|
|
pub async fn download_upgrade_packages(&self) -> AptOstreeResult<()> {
|
|
// This is a simplified implementation
|
|
// In a real implementation, we would download packages using APT
|
|
info!("Downloading upgrade packages...");
|
|
Ok(())
|
|
}
|
|
|
|
/// Install packages to a specific path
|
|
pub async fn install_packages_to_path(&self, packages: &[String], path: &Path) -> AptOstreeResult<()> {
|
|
// This is a simplified implementation
|
|
// In a real implementation, we would install packages to the specified path
|
|
info!("Installing packages {:?} to path {:?}", packages, path);
|
|
Ok(())
|
|
}
|
|
|
|
/// Remove packages from a specific path
|
|
pub async fn remove_packages_from_path(&self, packages: &[String], path: &Path) -> AptOstreeResult<()> {
|
|
// This is a simplified implementation
|
|
// In a real implementation, we would remove packages from the specified path
|
|
info!("Removing packages {:?} from path {:?}", packages, path);
|
|
Ok(())
|
|
}
|
|
|
|
/// Upgrade system in a specific path
|
|
pub async fn upgrade_system_in_path(&self, path: &Path) -> AptOstreeResult<()> {
|
|
// This is a simplified implementation
|
|
// In a real implementation, we would upgrade the system in the specified path
|
|
info!("Upgrading system in path {:?}", path);
|
|
Ok(())
|
|
}
|
|
|
|
/// Get upgraded package count
|
|
pub async fn get_upgraded_package_count(&self) -> AptOstreeResult<usize> {
|
|
// This is a simplified implementation
|
|
// In a real implementation, we would count the number of upgraded packages
|
|
Ok(2)
|
|
}
|
|
}
|
|
|
|
/// Database statistics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DatabaseStats {
|
|
pub total_packages: usize,
|
|
pub layer_levels: Vec<usize>,
|
|
pub database_version: String,
|
|
pub last_update: chrono::DateTime<chrono::Utc>,
|
|
pub deployment_id: String,
|
|
}
|
|
|
|
/// Convert package state to string
|
|
fn state_to_string(state: &PackageState) -> &'static str {
|
|
match state {
|
|
PackageState::Installed => "install ok installed",
|
|
PackageState::ConfigFiles => "config-files",
|
|
PackageState::HalfInstalled => "half-installed",
|
|
PackageState::Unpacked => "unpacked",
|
|
PackageState::HalfConfigured => "half-configured",
|
|
PackageState::TriggersAwaiting => "triggers-awaited",
|
|
PackageState::TriggersPending => "triggers-pending",
|
|
PackageState::NotInstalled => "not-installed",
|
|
}
|
|
}
|