- 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
801 lines
27 KiB
Rust
801 lines
27 KiB
Rust
use crate::lib::error::{AptOstreeError, AptOstreeResult};
|
|
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use tokio::sync::RwLock;
|
|
use uuid::Uuid;
|
|
|
|
|
|
/// Transaction types for different operations
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum TransactionType {
|
|
PkgChange, // Package installation/removal
|
|
Deploy, // Deployment operations
|
|
Rebase, // System rebase operations
|
|
Upgrade, // System upgrade operations
|
|
Rollback, // Rollback operations
|
|
Kargs, // Kernel argument changes
|
|
Initramfs, // Initramfs modifications
|
|
Override, // Package override changes
|
|
UsrOverlay, // User overlay operations
|
|
ApplyLive, // Live deployment changes
|
|
Finalize, // Deployment finalization
|
|
Cleanup, // Cleanup operations
|
|
Reload, // Configuration reload
|
|
Reset, // Reset operations
|
|
RefreshMd, // Metadata refresh
|
|
Compose, // Tree composition
|
|
Container, // Container operations
|
|
Experimental, // Experimental features
|
|
}
|
|
|
|
/// Upgrade-specific transaction data
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct UpgradeTransaction {
|
|
pub packages_to_install: Vec<String>,
|
|
pub packages_to_remove: Vec<String>,
|
|
pub allow_downgrade: bool,
|
|
pub require_signatures: bool,
|
|
pub reboot_after: bool,
|
|
pub preview_mode: bool,
|
|
pub cache_only: bool,
|
|
pub download_only: bool,
|
|
}
|
|
|
|
impl Default for UpgradeTransaction {
|
|
fn default() -> Self {
|
|
Self {
|
|
packages_to_install: Vec::new(),
|
|
packages_to_remove: Vec::new(),
|
|
allow_downgrade: false,
|
|
require_signatures: true,
|
|
reboot_after: false,
|
|
preview_mode: false,
|
|
cache_only: false,
|
|
download_only: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Transaction states throughout the lifecycle
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
|
pub enum TransactionState {
|
|
Initialized, // Transaction created
|
|
Preparing, // Preparation phase
|
|
Ready, // Ready for execution
|
|
Running, // Currently executing
|
|
Paused, // Paused for user input
|
|
Completed, // Successfully completed
|
|
Failed, // Execution failed
|
|
Cancelled, // User cancelled
|
|
RollingBack, // Rolling back changes
|
|
RolledBack, // Successfully rolled back
|
|
}
|
|
|
|
/// Transaction step information for detailed progress tracking
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TransactionStep {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub description: String,
|
|
pub status: StepStatus,
|
|
pub progress: f64,
|
|
pub started_at: Option<DateTime<Utc>>,
|
|
pub completed_at: Option<DateTime<Utc>>,
|
|
pub error: Option<String>,
|
|
pub metadata: HashMap<String, String>,
|
|
}
|
|
|
|
/// Step status for detailed progress tracking
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum StepStatus {
|
|
Pending,
|
|
Running,
|
|
Completed,
|
|
Failed,
|
|
Skipped,
|
|
}
|
|
|
|
/// Transaction priority levels
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
|
|
pub enum TransactionPriority {
|
|
Low = 1,
|
|
Normal = 2,
|
|
High = 3,
|
|
Critical = 4,
|
|
}
|
|
|
|
/// Parameters for creating a transaction with steps
|
|
#[derive(Debug, Clone)]
|
|
pub struct TransactionCreationParams {
|
|
pub transaction_type: TransactionType,
|
|
pub user_id: u32,
|
|
pub session_id: String,
|
|
pub title: String,
|
|
pub description: String,
|
|
pub steps: Vec<TransactionStep>,
|
|
pub priority: TransactionPriority,
|
|
}
|
|
|
|
/// Transaction result information
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TransactionResult {
|
|
pub success: bool,
|
|
pub message: String,
|
|
pub details: Option<String>,
|
|
pub rollback_required: bool,
|
|
pub steps_completed: usize,
|
|
pub steps_total: usize,
|
|
pub warnings: Vec<String>,
|
|
}
|
|
|
|
/// Transaction object representing a single operation
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Transaction {
|
|
pub id: String,
|
|
pub transaction_type: TransactionType,
|
|
pub user_id: u32,
|
|
pub session_id: String,
|
|
pub title: String,
|
|
pub description: String,
|
|
pub state: TransactionState,
|
|
pub created_at: DateTime<Utc>,
|
|
pub started_at: Option<DateTime<Utc>>,
|
|
pub completed_at: Option<DateTime<Utc>>,
|
|
pub progress: f64, // 0.0 to 1.0
|
|
pub result: Option<TransactionResult>,
|
|
pub metadata: HashMap<String, String>,
|
|
pub upgrade_data: Option<UpgradeTransaction>,
|
|
pub steps: Vec<TransactionStep>,
|
|
pub dependencies: Vec<String>,
|
|
pub priority: TransactionPriority,
|
|
pub estimated_duration: Option<u64>, // in seconds
|
|
pub actual_duration: Option<u64>, // in seconds
|
|
}
|
|
|
|
impl Transaction {
|
|
/// Create a new transaction
|
|
pub fn new(
|
|
id: String,
|
|
transaction_type: TransactionType,
|
|
user_id: u32,
|
|
session_id: String,
|
|
title: String,
|
|
description: String,
|
|
created_at: DateTime<Utc>,
|
|
) -> Self {
|
|
Self {
|
|
id,
|
|
transaction_type,
|
|
user_id,
|
|
session_id,
|
|
title,
|
|
description,
|
|
state: TransactionState::Initialized,
|
|
created_at,
|
|
started_at: None,
|
|
completed_at: None,
|
|
progress: 0.0,
|
|
result: None,
|
|
metadata: HashMap::new(),
|
|
upgrade_data: None,
|
|
steps: Vec::new(),
|
|
dependencies: Vec::new(),
|
|
priority: TransactionPriority::Normal,
|
|
estimated_duration: None,
|
|
actual_duration: None,
|
|
}
|
|
}
|
|
|
|
/// Update transaction state
|
|
pub fn update_state(&mut self, new_state: TransactionState) {
|
|
self.state = new_state;
|
|
match new_state {
|
|
TransactionState::Running => {
|
|
self.started_at = Some(Utc::now());
|
|
}
|
|
TransactionState::Completed | TransactionState::Failed | TransactionState::RolledBack => {
|
|
self.completed_at = Some(Utc::now());
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
/// Update progress
|
|
pub fn update_progress(&mut self, progress: f64) {
|
|
self.progress = progress.clamp(0.0, 1.0);
|
|
}
|
|
|
|
/// Set transaction result
|
|
pub fn set_result(&mut self, result: TransactionResult) {
|
|
self.result = Some(result);
|
|
}
|
|
|
|
/// Add metadata
|
|
pub fn add_metadata(&mut self, key: String, value: String) {
|
|
self.metadata.insert(key, value);
|
|
}
|
|
|
|
/// Add a transaction step
|
|
pub fn add_step(&mut self, step: TransactionStep) {
|
|
self.steps.push(step);
|
|
}
|
|
|
|
/// Update step status
|
|
pub fn update_step_status(&mut self, step_id: &str, status: StepStatus) -> AptOstreeResult<()> {
|
|
if let Some(step) = self.steps.iter_mut().find(|s| s.id == step_id) {
|
|
step.status = status.clone();
|
|
match status {
|
|
StepStatus::Running => {
|
|
step.started_at = Some(Utc::now());
|
|
}
|
|
StepStatus::Completed | StepStatus::Failed => {
|
|
step.completed_at = Some(Utc::now());
|
|
}
|
|
_ => {}
|
|
}
|
|
Ok(())
|
|
} else {
|
|
Err(AptOstreeError::System(format!("Step {} not found", step_id)))
|
|
}
|
|
}
|
|
|
|
/// Get step by ID
|
|
pub fn get_step(&self, step_id: &str) -> Option<&TransactionStep> {
|
|
self.steps.iter().find(|s| s.id == step_id)
|
|
}
|
|
|
|
/// Calculate overall progress from steps
|
|
pub fn calculate_progress_from_steps(&mut self) {
|
|
if self.steps.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let completed_steps = self.steps.iter()
|
|
.filter(|s| s.status == StepStatus::Completed)
|
|
.count();
|
|
|
|
let total_steps = self.steps.len();
|
|
self.progress = completed_steps as f64 / total_steps as f64;
|
|
}
|
|
|
|
/// Set transaction priority
|
|
pub fn set_priority(&mut self, priority: TransactionPriority) {
|
|
self.priority = priority;
|
|
}
|
|
|
|
/// Add dependency
|
|
pub fn add_dependency(&mut self, transaction_id: String) {
|
|
if !self.dependencies.contains(&transaction_id) {
|
|
self.dependencies.push(transaction_id);
|
|
}
|
|
}
|
|
|
|
/// Check if dependencies are satisfied
|
|
pub fn dependencies_satisfied(&self, completed_transactions: &[String]) -> bool {
|
|
self.dependencies.iter().all(|dep| completed_transactions.contains(dep))
|
|
}
|
|
|
|
/// Check if transaction is active
|
|
pub fn is_active(&self) -> bool {
|
|
matches!(
|
|
self.state,
|
|
TransactionState::Preparing | TransactionState::Ready | TransactionState::Running | TransactionState::Paused
|
|
)
|
|
}
|
|
|
|
/// Check if transaction can be cancelled
|
|
pub fn can_cancel(&self) -> bool {
|
|
matches!(
|
|
self.state,
|
|
TransactionState::Preparing | TransactionState::Ready | TransactionState::Running | TransactionState::Paused
|
|
)
|
|
}
|
|
|
|
/// Check if transaction can be rolled back
|
|
pub fn can_rollback(&self) -> bool {
|
|
matches!(
|
|
self.state,
|
|
TransactionState::Completed | TransactionState::Failed
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Transaction manager for handling all transactions
|
|
#[allow(dead_code, clippy::new_without_default)]
|
|
pub struct TransactionManager {
|
|
transactions: Arc<RwLock<HashMap<String, Transaction>>>,
|
|
}
|
|
|
|
impl TransactionManager {
|
|
/// Create a new transaction manager
|
|
pub fn new() -> Self {
|
|
Self {
|
|
transactions: Arc::new(RwLock::new(HashMap::new())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for TransactionManager {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl TransactionManager {
|
|
/// Create a new transaction
|
|
pub async fn create_transaction(
|
|
&self,
|
|
transaction_type: TransactionType,
|
|
user_id: u32,
|
|
session_id: String,
|
|
title: String,
|
|
description: String,
|
|
) -> AptOstreeResult<String> {
|
|
let transaction_id = Uuid::new_v4().to_string();
|
|
|
|
let transaction = Transaction::new(
|
|
transaction_id.clone(),
|
|
transaction_type,
|
|
user_id,
|
|
session_id,
|
|
title,
|
|
description,
|
|
Utc::now(),
|
|
);
|
|
|
|
// Store transaction
|
|
self.transactions
|
|
.write()
|
|
.await
|
|
.insert(transaction_id.clone(), transaction);
|
|
|
|
Ok(transaction_id)
|
|
}
|
|
|
|
/// Get a transaction by ID
|
|
pub async fn get_transaction(&self, transaction_id: &str) -> AptOstreeResult<Transaction> {
|
|
let transactions = self.transactions.read().await;
|
|
transactions
|
|
.get(transaction_id)
|
|
.cloned()
|
|
.ok_or_else(|| AptOstreeError::System(format!("Transaction {} not found", transaction_id)))
|
|
}
|
|
|
|
/// Update a transaction
|
|
pub async fn update_transaction(&self, transaction: &Transaction) -> AptOstreeResult<()> {
|
|
let mut transactions = self.transactions.write().await;
|
|
transactions.insert(transaction.id.clone(), transaction.clone());
|
|
Ok(())
|
|
}
|
|
|
|
/// List all transactions
|
|
pub async fn list_transactions(&self) -> AptOstreeResult<Vec<Transaction>> {
|
|
let transactions = self.transactions.read().await;
|
|
Ok(transactions.values().cloned().collect())
|
|
}
|
|
|
|
/// List active transactions
|
|
pub async fn list_active_transactions(&self) -> AptOstreeResult<Vec<Transaction>> {
|
|
let transactions = self.transactions.read().await;
|
|
Ok(transactions
|
|
.values()
|
|
.filter(|t| t.is_active())
|
|
.cloned()
|
|
.collect())
|
|
}
|
|
|
|
/// Cancel a transaction
|
|
pub async fn cancel_transaction(&self, transaction_id: &str) -> AptOstreeResult<()> {
|
|
let mut transaction = self.get_transaction(transaction_id).await?;
|
|
|
|
if !transaction.can_cancel() {
|
|
return Err(AptOstreeError::System(format!(
|
|
"Transaction {} cannot be cancelled in state {:?}",
|
|
transaction_id, transaction.state
|
|
)));
|
|
}
|
|
|
|
transaction.update_state(TransactionState::Cancelled);
|
|
self.update_transaction(&transaction).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Rollback a transaction
|
|
pub async fn rollback_transaction(&self, transaction_id: &str) -> AptOstreeResult<()> {
|
|
let mut transaction = self.get_transaction(transaction_id).await?;
|
|
|
|
if !transaction.can_rollback() {
|
|
return Err(AptOstreeError::System(format!(
|
|
"Transaction {} cannot be rolled back in state {:?}",
|
|
transaction_id, transaction.state
|
|
)));
|
|
}
|
|
|
|
transaction.update_state(TransactionState::RollingBack);
|
|
self.update_transaction(&transaction).await?;
|
|
|
|
// TODO: Implement actual rollback logic based on transaction type
|
|
|
|
transaction.update_state(TransactionState::RolledBack);
|
|
self.update_transaction(&transaction).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Create an upgrade transaction
|
|
pub async fn create_upgrade_transaction(
|
|
&self,
|
|
user_id: u32,
|
|
session_id: String,
|
|
upgrade_data: UpgradeTransaction,
|
|
) -> AptOstreeResult<String> {
|
|
let transaction_id = Uuid::new_v4().to_string();
|
|
|
|
let mut transaction = Transaction::new(
|
|
transaction_id.clone(),
|
|
TransactionType::Upgrade,
|
|
user_id,
|
|
session_id,
|
|
"System Upgrade".to_string(),
|
|
"Upgrading system packages and OSTree tree".to_string(),
|
|
Utc::now(),
|
|
);
|
|
|
|
transaction.upgrade_data = Some(upgrade_data);
|
|
|
|
// Store transaction
|
|
self.transactions
|
|
.write()
|
|
.await
|
|
.insert(transaction_id.clone(), transaction);
|
|
|
|
Ok(transaction_id)
|
|
}
|
|
|
|
/// Execute an upgrade transaction
|
|
pub async fn execute_upgrade_transaction(&self, transaction_id: &str) -> AptOstreeResult<()> {
|
|
let mut transaction = self.get_transaction(transaction_id).await?;
|
|
|
|
if transaction.transaction_type != TransactionType::Upgrade {
|
|
return Err(AptOstreeError::System(
|
|
"Transaction is not an upgrade transaction".to_string()
|
|
));
|
|
}
|
|
|
|
// Extract upgrade data before borrowing
|
|
let _packages_to_install = if let Some(ref upgrade_data) = transaction.upgrade_data {
|
|
upgrade_data.packages_to_install.clone()
|
|
} else {
|
|
return Err(AptOstreeError::System(
|
|
"Upgrade transaction data not found".to_string()
|
|
));
|
|
};
|
|
|
|
let _packages_to_remove = if let Some(ref upgrade_data) = transaction.upgrade_data {
|
|
upgrade_data.packages_to_remove.len()
|
|
} else {
|
|
0
|
|
};
|
|
|
|
// Create upgrade steps if they don't exist
|
|
if transaction.steps.is_empty() {
|
|
let upgrade_steps = vec![
|
|
TransactionStep {
|
|
id: "validate".to_string(),
|
|
name: "Validate System".to_string(),
|
|
description: "Check OSTree system status and APT availability".to_string(),
|
|
status: StepStatus::Pending,
|
|
progress: 0.0,
|
|
started_at: None,
|
|
completed_at: None,
|
|
error: None,
|
|
metadata: HashMap::new(),
|
|
},
|
|
TransactionStep {
|
|
id: "update_cache".to_string(),
|
|
name: "Update APT Cache".to_string(),
|
|
description: "Refresh package lists and metadata".to_string(),
|
|
status: StepStatus::Pending,
|
|
progress: 0.0,
|
|
started_at: None,
|
|
completed_at: None,
|
|
error: None,
|
|
metadata: HashMap::new(),
|
|
},
|
|
TransactionStep {
|
|
id: "check_updates".to_string(),
|
|
name: "Check for Updates".to_string(),
|
|
description: "Identify available system and package updates".to_string(),
|
|
status: StepStatus::Pending,
|
|
progress: 0.0,
|
|
started_at: None,
|
|
completed_at: None,
|
|
error: None,
|
|
metadata: HashMap::new(),
|
|
},
|
|
TransactionStep {
|
|
id: "download_packages".to_string(),
|
|
name: "Download Packages".to_string(),
|
|
description: "Download required package updates".to_string(),
|
|
status: StepStatus::Pending,
|
|
progress: 0.0,
|
|
started_at: None,
|
|
completed_at: None,
|
|
error: None,
|
|
metadata: HashMap::new(),
|
|
},
|
|
TransactionStep {
|
|
id: "apply_updates".to_string(),
|
|
name: "Apply Updates".to_string(),
|
|
description: "Apply package updates and system changes".to_string(),
|
|
status: StepStatus::Pending,
|
|
progress: 0.0,
|
|
started_at: None,
|
|
completed_at: None,
|
|
error: None,
|
|
metadata: HashMap::new(),
|
|
},
|
|
TransactionStep {
|
|
id: "finalize".to_string(),
|
|
name: "Finalize Upgrade".to_string(),
|
|
description: "Complete upgrade and prepare for reboot if needed".to_string(),
|
|
status: StepStatus::Pending,
|
|
progress: 0.0,
|
|
started_at: None,
|
|
completed_at: None,
|
|
error: None,
|
|
metadata: HashMap::new(),
|
|
},
|
|
];
|
|
|
|
for step in upgrade_steps {
|
|
transaction.add_step(step);
|
|
}
|
|
}
|
|
|
|
// Update transaction state
|
|
transaction.update_state(TransactionState::Preparing);
|
|
transaction.update_progress(0.1);
|
|
self.update_transaction(&transaction).await?;
|
|
|
|
// Execute upgrade steps
|
|
let step_executor = |step: &TransactionStep| -> AptOstreeResult<bool> {
|
|
match step.id.as_str() {
|
|
"validate" => {
|
|
// TODO: Implement real validation logic
|
|
Ok(true)
|
|
}
|
|
"update_cache" => {
|
|
// TODO: Implement real APT cache update
|
|
Ok(true)
|
|
}
|
|
"check_updates" => {
|
|
// TODO: Implement real update checking
|
|
Ok(true)
|
|
}
|
|
"download_packages" => {
|
|
// TODO: Implement real package downloading
|
|
Ok(true)
|
|
}
|
|
"apply_updates" => {
|
|
// TODO: Implement real update application
|
|
Ok(true)
|
|
}
|
|
"finalize" => {
|
|
// TODO: Implement real upgrade finalization
|
|
Ok(true)
|
|
}
|
|
_ => Ok(true),
|
|
}
|
|
};
|
|
|
|
// Execute the transaction with steps
|
|
let result = self.execute_transaction_with_steps(transaction_id, step_executor).await?;
|
|
|
|
// Update the transaction with the result
|
|
let mut transaction = self.get_transaction(transaction_id).await?;
|
|
transaction.result = Some(result);
|
|
self.update_transaction(&transaction).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
|
|
|
|
/// Clean up completed transactions
|
|
pub async fn cleanup_completed_transactions(&self, max_age_hours: u64) -> AptOstreeResult<usize> {
|
|
let mut transactions = self.transactions.write().await;
|
|
let cutoff_time = Utc::now() - chrono::Duration::hours(max_age_hours as i64);
|
|
|
|
let completed_ids: Vec<String> = transactions
|
|
.iter()
|
|
.filter(|(_, t)| {
|
|
matches!(
|
|
t.state,
|
|
TransactionState::Completed | TransactionState::Failed | TransactionState::Cancelled | TransactionState::RolledBack
|
|
) && t.completed_at.is_some_and(|time| time < cutoff_time)
|
|
})
|
|
.map(|(id, _)| id.clone())
|
|
.collect();
|
|
|
|
let count = completed_ids.len();
|
|
for id in completed_ids {
|
|
transactions.remove(&id);
|
|
}
|
|
|
|
Ok(count)
|
|
}
|
|
|
|
/// Create a transaction with steps
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn create_transaction_with_steps(
|
|
&self,
|
|
params: TransactionCreationParams,
|
|
) -> AptOstreeResult<String> {
|
|
let transaction_id = Uuid::new_v4().to_string();
|
|
|
|
let mut transaction = Transaction::new(
|
|
transaction_id.clone(),
|
|
params.transaction_type,
|
|
params.user_id,
|
|
params.session_id,
|
|
params.title,
|
|
params.description,
|
|
Utc::now(),
|
|
);
|
|
|
|
transaction.set_priority(params.priority);
|
|
for step in params.steps {
|
|
transaction.add_step(step);
|
|
}
|
|
|
|
// Store transaction
|
|
self.transactions
|
|
.write()
|
|
.await
|
|
.insert(transaction_id.clone(), transaction);
|
|
|
|
Ok(transaction_id)
|
|
}
|
|
|
|
/// Execute a transaction with step-by-step progress
|
|
pub async fn execute_transaction_with_steps(
|
|
&self,
|
|
transaction_id: &str,
|
|
step_executor: impl Fn(&TransactionStep) -> AptOstreeResult<bool> + Send + Sync,
|
|
) -> AptOstreeResult<TransactionResult> {
|
|
let mut transaction = self.get_transaction(transaction_id).await?;
|
|
|
|
if !transaction.is_active() {
|
|
return Err(AptOstreeError::System(
|
|
"Transaction is not in an active state".to_string()
|
|
));
|
|
}
|
|
|
|
transaction.update_state(TransactionState::Running);
|
|
self.update_transaction(&transaction).await?;
|
|
|
|
let start_time = std::time::Instant::now();
|
|
let mut step_results = Vec::new();
|
|
|
|
// Execute each step and collect results
|
|
for step in &transaction.steps {
|
|
if step.status == StepStatus::Pending {
|
|
let mut step_result = step.clone();
|
|
step_result.status = StepStatus::Running;
|
|
step_result.started_at = Some(Utc::now());
|
|
|
|
// Execute the step
|
|
let step_success = match step_executor(step) {
|
|
Ok(success) => success,
|
|
Err(e) => {
|
|
step_result.status = StepStatus::Failed;
|
|
step_result.error = Some(e.to_string());
|
|
false
|
|
}
|
|
};
|
|
|
|
if step_success {
|
|
step_result.status = StepStatus::Completed;
|
|
step_result.completed_at = Some(Utc::now());
|
|
} else {
|
|
step_result.status = StepStatus::Failed;
|
|
if step_result.error.is_none() {
|
|
step_result.error = Some("Step execution failed".to_string());
|
|
}
|
|
}
|
|
|
|
// Check if any step failed
|
|
if step_result.status == StepStatus::Failed {
|
|
let error_message = step_result.error.clone();
|
|
let step_name = step_result.name.clone();
|
|
|
|
step_results.push(step_result);
|
|
|
|
transaction.update_state(TransactionState::Failed);
|
|
transaction.actual_duration = Some(start_time.elapsed().as_secs());
|
|
|
|
// Update steps in transaction
|
|
for (i, result) in step_results.iter().enumerate() {
|
|
if i < transaction.steps.len() {
|
|
transaction.steps[i] = result.clone();
|
|
}
|
|
}
|
|
|
|
self.update_transaction(&transaction).await?;
|
|
|
|
return Ok(TransactionResult {
|
|
success: false,
|
|
message: format!("Step '{}' failed", step_name),
|
|
details: error_message,
|
|
rollback_required: true,
|
|
steps_completed: step_results.iter().filter(|s| s.status == StepStatus::Completed).count(),
|
|
steps_total: transaction.steps.len(),
|
|
warnings: Vec::new(),
|
|
});
|
|
}
|
|
|
|
} else {
|
|
step_results.push(step.clone());
|
|
}
|
|
}
|
|
|
|
// Update transaction with step results
|
|
transaction.steps = step_results;
|
|
transaction.calculate_progress_from_steps();
|
|
|
|
// All steps completed successfully
|
|
transaction.update_state(TransactionState::Completed);
|
|
transaction.actual_duration = Some(start_time.elapsed().as_secs());
|
|
|
|
let result = TransactionResult {
|
|
success: true,
|
|
message: "Transaction completed successfully".to_string(),
|
|
details: None,
|
|
rollback_required: false,
|
|
steps_completed: transaction.steps.len(),
|
|
steps_total: transaction.steps.len(),
|
|
warnings: Vec::new(),
|
|
};
|
|
|
|
transaction.result = Some(result.clone());
|
|
self.update_transaction(&transaction).await?;
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
/// Get transactions by priority
|
|
pub async fn get_transactions_by_priority(&self, priority: TransactionPriority) -> AptOstreeResult<Vec<Transaction>> {
|
|
let transactions = self.transactions.read().await;
|
|
Ok(transactions
|
|
.values()
|
|
.filter(|t| t.priority == priority)
|
|
.cloned()
|
|
.collect())
|
|
}
|
|
|
|
/// Get transactions with dependencies
|
|
pub async fn get_transactions_with_dependencies(&self) -> AptOstreeResult<Vec<Transaction>> {
|
|
let transactions = self.transactions.read().await;
|
|
Ok(transactions
|
|
.values()
|
|
.filter(|t| !t.dependencies.is_empty())
|
|
.cloned()
|
|
.collect())
|
|
}
|
|
|
|
/// Validate transaction dependencies
|
|
pub async fn validate_transaction_dependencies(&self, transaction_id: &str) -> AptOstreeResult<bool> {
|
|
let transaction = self.get_transaction(transaction_id).await?;
|
|
let completed_transactions = self.list_transactions().await?
|
|
.into_iter()
|
|
.filter(|t| t.state == TransactionState::Completed)
|
|
.map(|t| t.id)
|
|
.collect::<Vec<_>>();
|
|
|
|
Ok(transaction.dependencies_satisfied(&completed_transactions))
|
|
}
|
|
}
|