apt-dnf-bridge-workspace/docs/rollback.md
robojerk 88d57cd3a1 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
2025-09-13 21:02:29 -07:00

6.3 KiB

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

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

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

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

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

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:

// 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.