apt-ostree/src/daemon/transaction.rs
robojerk 3dec23f8f7 Fix YAML linting issues and update system requirements to Debian 13+
- Fix trailing spaces and blank lines in Forgejo workflows
- Update system requirements from Ubuntu Jammy/Bookworm to Debian 13+ (Trixie)
- Update test treefile to use Debian Trixie instead of Ubuntu Jammy
- Update documentation to reflect modern system requirements
- Fix yamllint errors for CI/CD functionality
- Ensure compatibility with modern OSTree and libapt versions
2025-08-18 11:39:58 -07:00

231 lines
7.1 KiB
Rust

//! Transaction management for apt-ostree daemon
use crate::daemon::{DaemonResult, DaemonError};
use std::collections::HashMap;
use std::fmt;
use uuid::Uuid;
use chrono::{DateTime, Utc};
/// Transaction types
#[derive(Debug, Clone, PartialEq)]
pub enum TransactionType {
Install,
Remove,
Upgrade,
Rollback,
Deploy,
Rebase,
}
/// Transaction states
#[derive(Debug, Clone, PartialEq)]
pub enum TransactionState {
Pending,
Running,
Completed,
Failed,
Cancelled,
}
impl fmt::Display for TransactionState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TransactionState::Pending => write!(f, "pending"),
TransactionState::Running => write!(f, "running"),
TransactionState::Completed => write!(f, "completed"),
TransactionState::Failed => write!(f, "failed"),
TransactionState::Cancelled => write!(f, "cancelled"),
}
}
}
/// Transaction result
#[derive(Debug, Clone)]
pub struct TransactionResult {
pub success: bool,
pub message: String,
pub details: Option<String>,
}
/// Transaction information
#[derive(Debug, Clone)]
pub struct Transaction {
pub id: String,
pub transaction_type: TransactionType,
pub state: TransactionState,
pub created_at: DateTime<Utc>,
pub started_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub result: Option<TransactionResult>,
pub progress: f64, // 0.0 to 1.0
pub metadata: HashMap<String, String>,
}
impl Transaction {
pub fn new(transaction_type: TransactionType) -> Self {
Self {
id: Uuid::new_v4().to_string(),
transaction_type,
state: TransactionState::Pending,
created_at: Utc::now(),
started_at: None,
completed_at: None,
result: None,
progress: 0.0,
metadata: HashMap::new(),
}
}
pub fn start(&mut self) -> DaemonResult<()> {
if self.state != TransactionState::Pending {
return Err(DaemonError::Transaction("Transaction is not in pending state".to_string()));
}
self.state = TransactionState::Running;
self.started_at = Some(Utc::now());
Ok(())
}
pub fn complete(&mut self, result: TransactionResult) -> DaemonResult<()> {
if self.state != TransactionState::Running {
return Err(DaemonError::Transaction("Transaction is not in running state".to_string()));
}
self.state = TransactionState::Completed;
self.completed_at = Some(Utc::now());
self.result = Some(result);
self.progress = 1.0;
Ok(())
}
pub fn fail(&mut self, error: String) -> DaemonResult<()> {
if self.state != TransactionState::Running {
return Err(DaemonError::Transaction("Transaction is not in running state".to_string()));
}
self.state = TransactionState::Failed;
self.completed_at = Some(Utc::now());
self.result = Some(TransactionResult {
success: false,
message: error,
details: None,
});
Ok(())
}
pub fn cancel(&mut self) -> DaemonResult<()> {
if self.state != TransactionState::Pending && self.state != TransactionState::Running {
return Err(DaemonError::Transaction("Transaction cannot be cancelled in current state".to_string()));
}
self.state = TransactionState::Cancelled;
self.completed_at = Some(Utc::now());
self.result = Some(TransactionResult {
success: false,
message: "Transaction cancelled".to_string(),
details: None,
});
Ok(())
}
pub fn update_progress(&mut self, progress: f64) -> DaemonResult<()> {
if !(0.0..=1.0).contains(&progress) {
return Err(DaemonError::Transaction("Progress must be between 0.0 and 1.0".to_string()));
}
self.progress = progress;
Ok(())
}
pub fn add_metadata(&mut self, key: String, value: String) {
self.metadata.insert(key, value);
}
}
/// Transaction manager
#[allow(dead_code, clippy::new_without_default)]
pub struct TransactionManager {
transactions: HashMap<String, Transaction>,
next_transaction_id: u64,
}
impl TransactionManager {
pub fn new() -> Self {
Self {
transactions: HashMap::new(),
next_transaction_id: 1,
}
}
pub fn start_transaction(&mut self, transaction_type: TransactionType) -> String {
let transaction = Transaction::new(transaction_type);
let id = transaction.id.clone();
self.transactions.insert(id.clone(), transaction);
id
}
pub fn get_transaction(&self, transaction_id: &str) -> Option<&Transaction> {
self.transactions.get(transaction_id)
}
pub fn get_transaction_mut(&mut self, transaction_id: &str) -> Option<&mut Transaction> {
self.transactions.get_mut(transaction_id)
}
pub fn get_transaction_status(&self, transaction_id: &str) -> DaemonResult<TransactionState> {
let transaction = self.transactions.get(transaction_id)
.ok_or_else(|| DaemonError::Transaction(format!("Transaction {} not found", transaction_id)))?;
Ok(transaction.state.clone())
}
pub fn list_transactions(&self) -> Vec<&Transaction> {
self.transactions.values().collect()
}
pub fn cleanup_completed_transactions(&mut self, max_age_hours: u64) {
let cutoff = Utc::now() - chrono::Duration::hours(max_age_hours as i64);
self.transactions.retain(|_, transaction| {
if let Some(completed_at) = transaction.completed_at {
completed_at > cutoff
} else {
true // Keep incomplete transactions
}
});
}
pub fn get_active_transactions(&self) -> Vec<&Transaction> {
self.transactions.values()
.filter(|t| t.state == TransactionState::Pending || t.state == TransactionState::Running)
.collect()
}
pub fn start_existing_transaction(&mut self, transaction_id: &str) -> DaemonResult<()> {
if let Some(transaction) = self.transactions.get_mut(transaction_id) {
transaction.start()?;
}
Ok(())
}
pub fn complete_transaction(&mut self, transaction_id: &str, success: bool, message: String) -> DaemonResult<()> {
if let Some(transaction) = self.transactions.get_mut(transaction_id) {
let result = TransactionResult {
success,
message: message.clone(),
details: None,
};
if success {
transaction.complete(result)?;
} else {
transaction.fail(message)?;
}
}
Ok(())
}
pub fn get_active_transaction_count(&self) -> usize {
self.transactions.values()
.filter(|t| t.state == TransactionState::Running || t.state == TransactionState::Pending)
.count()
}
}
impl Default for TransactionManager {
fn default() -> Self {
Self::new()
}
}