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
This commit is contained in:
parent
ec0da91864
commit
3dec23f8f7
85 changed files with 12569 additions and 1088 deletions
|
|
@ -6,6 +6,7 @@ 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 {
|
||||
|
|
@ -29,6 +30,34 @@ pub enum TransactionType {
|
|||
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 {
|
||||
|
|
@ -44,6 +73,51 @@ pub enum TransactionState {
|
|||
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 {
|
||||
|
|
@ -51,6 +125,9 @@ pub struct TransactionResult {
|
|||
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
|
||||
|
|
@ -69,6 +146,12 @@ pub struct Transaction {
|
|||
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 {
|
||||
|
|
@ -96,6 +179,12 @@ impl Transaction {
|
|||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +204,7 @@ impl Transaction {
|
|||
|
||||
/// Update progress
|
||||
pub fn update_progress(&mut self, progress: f64) {
|
||||
self.progress = progress.max(0.0).min(1.0);
|
||||
self.progress = progress.clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
/// Set transaction result
|
||||
|
|
@ -128,6 +217,66 @@ impl Transaction {
|
|||
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!(
|
||||
|
|
@ -154,6 +303,7 @@ impl Transaction {
|
|||
}
|
||||
|
||||
/// Transaction manager for handling all transactions
|
||||
#[allow(dead_code, clippy::new_without_default)]
|
||||
pub struct TransactionManager {
|
||||
transactions: Arc<RwLock<HashMap<String, Transaction>>>,
|
||||
}
|
||||
|
|
@ -165,7 +315,15 @@ impl TransactionManager {
|
|||
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,
|
||||
|
|
@ -267,6 +425,186 @@ impl TransactionManager {
|
|||
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;
|
||||
|
|
@ -278,7 +616,7 @@ impl TransactionManager {
|
|||
matches!(
|
||||
t.state,
|
||||
TransactionState::Completed | TransactionState::Failed | TransactionState::Cancelled | TransactionState::RolledBack
|
||||
) && t.completed_at.map_or(false, |time| time < cutoff_time)
|
||||
) && t.completed_at.is_some_and(|time| time < cutoff_time)
|
||||
})
|
||||
.map(|(id, _)| id.clone())
|
||||
.collect();
|
||||
|
|
@ -290,4 +628,174 @@ impl TransactionManager {
|
|||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue