- 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
231 lines
7.1 KiB
Rust
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()
|
|
}
|
|
}
|