Add enhanced rollback functionality and comprehensive documentation
- Implement two-phase rollback system inspired by apt-tx project - Add package state tracking (newly_installed, upgraded, previously_installed) - Enhance both core and advanced transaction APIs with rollback methods - Add comprehensive rollback documentation in docs/rollback.md - Create rollback_demo.rs example demonstrating functionality - Update README with detailed crate usage instructions - Add rollback features to feature flags documentation - Fix import issues in advanced crate modules - Add get_installed_packages method to AptCommands - Include both crates.io and git installation options in README
This commit is contained in:
parent
06cafa0366
commit
88d57cd3a1
15 changed files with 945 additions and 47 deletions
203
docs/rollback.md
Normal file
203
docs/rollback.md
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
# Rollback Implementation
|
||||
|
||||
This document explains how the enhanced rollback functionality works in the APT-DNF Bridge.
|
||||
|
||||
## Overview
|
||||
|
||||
The rollback system provides a comprehensive way to undo package installations and upgrades. It tracks what packages were changed during a transaction and can restore the system to its previous state using a two-phase approach.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Package Tracking
|
||||
|
||||
Both `Transaction` (core) and `TransactionV2` (advanced) track three types of package information:
|
||||
|
||||
- **`previously_installed`**: Packages that were installed before the transaction
|
||||
- **`newly_installed`**: Packages that weren't installed before the transaction
|
||||
- **`upgraded`**: Packages that were upgraded, with their previous versions stored
|
||||
|
||||
### Two-Phase Rollback
|
||||
|
||||
When `rollback()` is called, it performs two phases:
|
||||
|
||||
1. **Phase 1 - Remove New Packages**: Uses `apt remove -y` to remove all packages in `newly_installed`
|
||||
2. **Phase 2 - Downgrade Upgraded Packages**: Uses `apt install -y package=oldversion` to restore previous versions
|
||||
|
||||
### Example Usage
|
||||
|
||||
#### Core API
|
||||
|
||||
```rust
|
||||
use apt_dnf_bridge::{Transaction, Package};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create and commit a transaction
|
||||
let mut tx = Transaction::new();
|
||||
tx.add_install(Package::new("vim", "2:8.1.2269-1ubuntu5.14", "amd64")).await?;
|
||||
tx.add_install(Package::new("curl", "7.68.0-1ubuntu2.18", "amd64")).await?;
|
||||
tx.resolve().await?;
|
||||
tx.commit().await?;
|
||||
|
||||
// Later, rollback the changes
|
||||
tx.rollback().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
#### Advanced API
|
||||
|
||||
```rust
|
||||
use apt_dnf_bridge::{TransactionV2, Package, BackendConfig};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create transaction with backend
|
||||
let config = BackendConfig::default();
|
||||
let mut tx = TransactionV2::with_shell_backend(config).await?;
|
||||
|
||||
// Add packages and commit
|
||||
tx.add_install(Package::new("git", "1:2.25.1-1ubuntu3.11", "amd64")).await?;
|
||||
tx.resolve().await?;
|
||||
tx.commit().await?;
|
||||
|
||||
// Rollback using backend
|
||||
tx.rollback().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Core Transaction Methods
|
||||
|
||||
```rust
|
||||
impl Transaction {
|
||||
/// Get packages that were newly installed in this transaction.
|
||||
pub fn newly_installed(&self) -> &HashSet<String>
|
||||
|
||||
/// Get packages that were upgraded in this transaction.
|
||||
pub fn upgraded(&self) -> &HashMap<String, String>
|
||||
|
||||
/// Get packages that were previously installed before this transaction.
|
||||
pub fn previously_installed(&self) -> &HashSet<String>
|
||||
|
||||
/// Check if the transaction has any changes that can be rolled back.
|
||||
pub fn can_rollback(&self) -> bool
|
||||
|
||||
/// Attempt to rollback the transaction (best-effort).
|
||||
pub async fn rollback(&self) -> Result<()>
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Transaction Methods
|
||||
|
||||
```rust
|
||||
impl TransactionV2 {
|
||||
/// Get packages that were newly installed in this transaction.
|
||||
pub fn newly_installed(&self) -> &HashSet<String>
|
||||
|
||||
/// Get packages that were upgraded in this transaction.
|
||||
pub fn upgraded(&self) -> &HashMap<String, String>
|
||||
|
||||
/// Get packages that were previously installed before this transaction.
|
||||
pub fn previously_installed(&self) -> &HashSet<String>
|
||||
|
||||
/// Check if the transaction has any changes that can be rolled back.
|
||||
pub fn can_rollback(&self) -> bool
|
||||
|
||||
/// Get a list of all changed packages (newly installed + upgraded).
|
||||
pub fn changed_packages(&self) -> Vec<String>
|
||||
|
||||
/// Attempt to rollback the transaction using the two-phase approach.
|
||||
pub async fn rollback(&self) -> Result<()>
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The rollback system uses a "fail fast" approach:
|
||||
|
||||
- **Critical errors** (package not found, permission denied) cause immediate failure
|
||||
- **Soft errors** (package already removed) generate warnings but continue
|
||||
- If a specific version can't be installed, the package is removed instead
|
||||
|
||||
## Limitations
|
||||
|
||||
This implementation handles the most common rollback scenarios:
|
||||
|
||||
- ✅ New package installations
|
||||
- ✅ Package upgrades with available previous versions
|
||||
- ✅ Mixed transactions (some new, some upgrades)
|
||||
|
||||
It does **not** handle:
|
||||
|
||||
- Complex dependency changes
|
||||
- Configuration file modifications
|
||||
- System state beyond package versions
|
||||
- Concurrent package operations
|
||||
|
||||
For more advanced rollback scenarios, consider using OSTree's native checkpoint/rollback functionality.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Data Structures
|
||||
|
||||
```rust
|
||||
pub struct Transaction {
|
||||
operations: Vec<Operation>,
|
||||
previously_installed: HashSet<String>, // packages that were installed before
|
||||
newly_installed: HashSet<String>, // packages that weren't installed before
|
||||
upgraded: HashMap<String, String>, // package -> old_version for upgrades
|
||||
log_commands: bool,
|
||||
}
|
||||
```
|
||||
|
||||
### Key Methods
|
||||
|
||||
- `add_install()`: Adds a package to the transaction
|
||||
- `commit()`: Installs packages and categorizes them as new vs upgraded
|
||||
- `rollback()`: Performs the two-phase rollback process
|
||||
- `changed_packages()`: Returns a list of what was changed
|
||||
|
||||
### State Management
|
||||
|
||||
The tracking fields (`newly_installed`, `upgraded`) are only populated after a successful `commit()`. This ensures the rollback data reflects what actually happened during the transaction.
|
||||
|
||||
## Testing
|
||||
|
||||
The rollback functionality is tested in the test suite, but actual package installation/removal is not performed in tests to avoid side effects on the test environment.
|
||||
|
||||
## OSTree Integration
|
||||
|
||||
For atomic operations, use OSTree's native checkpoint/rollback:
|
||||
|
||||
```rust
|
||||
// 1. Create OSTree checkpoint
|
||||
let checkpoint = ostree_create_checkpoint()?;
|
||||
|
||||
// 2. Run APT transaction
|
||||
let mut tx = Transaction::new();
|
||||
tx.add_install(Package::new("vim", "", "")).await?;
|
||||
tx.commit().await?;
|
||||
|
||||
// 3. Commit or rollback based on result
|
||||
if success {
|
||||
ostree_commit_changes()?;
|
||||
} else {
|
||||
ostree_rollback_to_checkpoint(checkpoint)?;
|
||||
}
|
||||
```
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
The current implementation follows these principles:
|
||||
|
||||
- **Simplicity over completeness**: Handle common cases well
|
||||
- **Fail fast**: Clear error messages when things go wrong
|
||||
- **Maintainability**: Easy to understand and modify
|
||||
- **Reliability**: Works consistently in real-world scenarios
|
||||
|
||||
This approach ensures the rollback system is robust, understandable, and maintainable while handling the vast majority of actual use cases.
|
||||
Loading…
Add table
Add a link
Reference in a new issue