use super::{
MigrationConfig, MigrationError, MigrationMetrics, MigrationOperation, MigrationPlan,
MigrationResult, MigrationStatus,
};
use std::collections::HashMap;
use tokio::fs;
use uuid::Uuid;
pub async fn execute_migration_plan(
plan: &MigrationPlan,
config: &MigrationConfig,
) -> Result<MigrationResult, MigrationError> {
let _execution_id = Uuid::new_v4().to_string();
let started_at = chrono::Utc::now();
let mut result = MigrationResult {
plan_id: plan.id.clone(),
status: MigrationStatus::InProgress,
started_at,
completed_at: None,
phases_completed: Vec::new(),
current_phase: None,
errors: Vec::new(),
warnings: Vec::new(),
metrics: MigrationMetrics {
roles_migrated: 0,
permissions_migrated: 0,
users_migrated: 0,
errors_encountered: 0,
warnings_generated: 0,
validation_failures: 0,
rollback_count: 0,
},
};
save_migration_status(&result, config).await?;
if config.dry_run {
log_message(config, "DRY RUN MODE - No actual changes will be made");
return execute_dry_run(plan, config, result).await;
}
if let Err(e) = execute_pre_validation(plan, config, &mut result).await {
result.status = MigrationStatus::Failed;
result.errors.push(format!("Pre-validation failed: {}", e));
save_migration_status(&result, config).await?;
return Ok(result);
}
for phase in &plan.phases {
result.current_phase = Some(phase.id.clone());
save_migration_status(&result, config).await?;
log_message(
config,
&format!("Executing phase: {} - {}", phase.id, phase.name),
);
match execute_phase(phase, config, &mut result).await {
Ok(_) => {
result.phases_completed.push(phase.id.clone());
log_message(
config,
&format!("Phase '{}' completed successfully", phase.id),
);
}
Err(e) => {
result.status = MigrationStatus::Failed;
result
.errors
.push(format!("Phase '{}' failed: {}", phase.id, e));
result.metrics.errors_encountered += 1;
log_message(config, &format!("Phase '{}' failed: {}", phase.id, e));
if let Err(rollback_error) =
execute_rollback_for_phase(phase, config, &mut result).await
{
result.errors.push(format!(
"Rollback for phase '{}' failed: {}",
phase.id, rollback_error
));
}
save_migration_status(&result, config).await?;
return Ok(result);
}
}
}
if let Err(e) = execute_post_validation(plan, config, &mut result).await {
result.status = MigrationStatus::Failed;
result.errors.push(format!("Post-validation failed: {}", e));
save_migration_status(&result, config).await?;
return Ok(result);
}
result.status = MigrationStatus::Completed;
result.completed_at = Some(chrono::Utc::now());
result.current_phase = None;
log_message(config, "Migration completed successfully");
save_migration_status(&result, config).await?;
Ok(result)
}
async fn execute_dry_run(
plan: &MigrationPlan,
config: &MigrationConfig,
mut result: MigrationResult,
) -> Result<MigrationResult, MigrationError> {
log_message(config, "=== DRY RUN EXECUTION ===");
for phase in &plan.phases {
log_message(
config,
&format!("DRY RUN - Phase: {} - {}", phase.id, phase.name),
);
for operation in &phase.operations {
match operation {
MigrationOperation::CreateRole { role_id, name, .. } => {
log_message(
config,
&format!(" [DRY RUN] Would create role: {} ({})", role_id, name),
);
result.metrics.roles_migrated += 1;
}
MigrationOperation::CreatePermission {
permission_id,
action,
resource,
..
} => {
log_message(
config,
&format!(
" [DRY RUN] Would create permission: {} ({}:{})",
permission_id, action, resource
),
);
result.metrics.permissions_migrated += 1;
}
MigrationOperation::AssignUserRole {
user_id, role_id, ..
} => {
log_message(
config,
&format!(
" [DRY RUN] Would assign role {} to user {}",
role_id, user_id
),
);
result.metrics.users_migrated += 1;
}
MigrationOperation::Backup {
backup_location,
backup_type,
} => {
log_message(
config,
&format!(
" [DRY RUN] Would create {:?} backup at {:?}",
backup_type, backup_location
),
);
}
MigrationOperation::ValidateIntegrity {
validation_type, ..
} => {
log_message(
config,
&format!(" [DRY RUN] Would validate: {}", validation_type),
);
}
MigrationOperation::MigrateCustomAttribute { attribute_name, .. } => {
log_message(
config,
&format!(
" [DRY RUN] Would migrate custom attribute: {}",
attribute_name
),
);
}
}
}
result.phases_completed.push(phase.id.clone());
}
result.status = MigrationStatus::Completed;
result.completed_at = Some(chrono::Utc::now());
log_message(config, "=== DRY RUN COMPLETED ===");
Ok(result)
}
async fn execute_pre_validation(
plan: &MigrationPlan,
config: &MigrationConfig,
result: &mut MigrationResult,
) -> Result<(), MigrationError> {
log_message(config, "Executing pre-validation steps");
for step in &plan.pre_validation_steps {
log_message(
config,
&format!("Pre-validation: {} - {}", step.id, step.name),
);
match execute_validation_step(step, config).await {
Ok(_) => {
log_message(config, &format!("Pre-validation '{}' passed", step.id));
}
Err(e) => {
if step.required {
return Err(MigrationError::ValidationError(format!(
"Required pre-validation '{}' failed: {}",
step.id, e
)));
} else {
result.warnings.push(format!(
"Optional pre-validation '{}' failed: {}",
step.id, e
));
result.metrics.warnings_generated += 1;
}
}
}
}
Ok(())
}
async fn execute_post_validation(
plan: &MigrationPlan,
config: &MigrationConfig,
result: &mut MigrationResult,
) -> Result<(), MigrationError> {
log_message(config, "Executing post-validation steps");
for step in &plan.post_validation_steps {
log_message(
config,
&format!("Post-validation: {} - {}", step.id, step.name),
);
match execute_validation_step(step, config).await {
Ok(_) => {
log_message(config, &format!("Post-validation '{}' passed", step.id));
}
Err(e) => {
if step.required {
result.metrics.validation_failures += 1;
return Err(MigrationError::ValidationError(format!(
"Required post-validation '{}' failed: {}",
step.id, e
)));
} else {
result.warnings.push(format!(
"Optional post-validation '{}' failed: {}",
step.id, e
));
result.metrics.warnings_generated += 1;
}
}
}
}
Ok(())
}
async fn execute_validation_step(
step: &super::ValidationStep,
config: &MigrationConfig,
) -> Result<(), MigrationError> {
use super::ValidationType;
match &step.validation_type {
ValidationType::HierarchyIntegrity => validate_hierarchy_integrity(config).await,
ValidationType::PermissionConsistency => validate_permission_consistency(config).await,
ValidationType::UserAssignmentValidity => validate_user_assignments(config).await,
ValidationType::PrivilegeEscalationCheck => validate_no_privilege_escalation(config).await,
ValidationType::Custom(validation_name) => {
execute_custom_validation(validation_name, &step.parameters, config).await
}
}
}
async fn execute_phase(
phase: &super::MigrationPhase,
config: &MigrationConfig,
result: &mut MigrationResult,
) -> Result<(), MigrationError> {
for operation in &phase.operations {
if let Err(e) = execute_operation(operation, config, result).await {
return Err(MigrationError::ExecutionError(format!(
"Operation failed in phase '{}': {}",
phase.id, e
)));
}
}
Ok(())
}
async fn execute_operation(
operation: &MigrationOperation,
config: &MigrationConfig,
result: &mut MigrationResult,
) -> Result<(), MigrationError> {
match operation {
MigrationOperation::CreateRole {
role_id,
name,
description,
permissions,
parent_role,
} => {
execute_create_role(
role_id,
name,
description.as_deref(),
permissions,
parent_role.as_deref(),
config,
)
.await?;
result.metrics.roles_migrated += 1;
}
MigrationOperation::CreatePermission {
permission_id,
action,
resource,
conditions,
} => {
execute_create_permission(permission_id, action, resource, conditions, config).await?;
result.metrics.permissions_migrated += 1;
}
MigrationOperation::AssignUserRole {
user_id,
role_id,
expiration,
} => {
execute_assign_user_role(user_id, role_id, expiration.as_ref(), config).await?;
result.metrics.users_migrated += 1;
}
MigrationOperation::Backup {
backup_location,
backup_type,
} => {
execute_backup(backup_location, backup_type, config).await?;
}
MigrationOperation::ValidateIntegrity {
validation_type,
parameters,
} => {
execute_integrity_validation(validation_type, parameters, config).await?;
}
MigrationOperation::MigrateCustomAttribute {
attribute_name,
conversion_logic,
} => {
execute_custom_attribute_migration(attribute_name, conversion_logic, config).await?;
}
}
Ok(())
}
async fn execute_create_role(
role_id: &str,
name: &str,
description: Option<&str>,
permissions: &[String],
parent_role: Option<&str>,
config: &MigrationConfig,
) -> Result<(), MigrationError> {
log_message(config, &format!("Creating role: {} ({})", role_id, name));
if config.verbose {
log_message(config, &format!(" Description: {:?}", description));
log_message(config, &format!(" Permissions: {:?}", permissions));
log_message(config, &format!(" Parent role: {:?}", parent_role));
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
Ok(())
}
async fn execute_create_permission(
permission_id: &str,
action: &str,
resource: &str,
conditions: &HashMap<String, String>,
config: &MigrationConfig,
) -> Result<(), MigrationError> {
log_message(
config,
&format!(
"Creating permission: {} ({}:{})",
permission_id, action, resource
),
);
if config.verbose {
log_message(config, &format!(" Conditions: {:?}", conditions));
}
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
Ok(())
}
async fn execute_assign_user_role(
user_id: &str,
role_id: &str,
expiration: Option<&chrono::DateTime<chrono::Utc>>,
config: &MigrationConfig,
) -> Result<(), MigrationError> {
log_message(
config,
&format!("Assigning role {} to user {}", role_id, user_id),
);
if config.verbose {
log_message(config, &format!(" Expiration: {:?}", expiration));
}
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
Ok(())
}
async fn execute_backup(
backup_location: &std::path::Path,
backup_type: &super::BackupType,
config: &MigrationConfig,
) -> Result<(), MigrationError> {
log_message(
config,
&format!("Creating {:?} backup at {:?}", backup_type, backup_location),
);
if let Some(parent) = backup_location.parent() {
fs::create_dir_all(parent).await?;
}
let backup_data = match backup_type {
super::BackupType::Full => create_full_backup(config).await?,
super::BackupType::Incremental => create_incremental_backup(config).await?,
super::BackupType::ConfigOnly => create_config_backup(config).await?,
super::BackupType::DataOnly => create_data_backup(config).await?,
};
fs::write(backup_location, backup_data).await?;
log_message(
config,
&format!("Backup created successfully at {:?}", backup_location),
);
Ok(())
}
async fn execute_integrity_validation(
validation_type: &str,
parameters: &HashMap<String, String>,
config: &MigrationConfig,
) -> Result<(), MigrationError> {
log_message(
config,
&format!("Executing integrity validation: {}", validation_type),
);
if config.verbose {
log_message(config, &format!(" Parameters: {:?}", parameters));
}
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
match validation_type {
"pre_migration_check" => validate_pre_migration_state(config).await,
"post_migration_check" => validate_post_migration_state(config).await,
"stop_migration" => Ok(()), _ => {
log_message(
config,
&format!("Unknown validation type: {}", validation_type),
);
Ok(())
}
}
}
async fn execute_custom_attribute_migration(
attribute_name: &str,
conversion_logic: &str,
config: &MigrationConfig,
) -> Result<(), MigrationError> {
log_message(
config,
&format!("Migrating custom attribute: {}", attribute_name),
);
if config.verbose {
log_message(config, &format!(" Conversion logic: {}", conversion_logic));
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
Ok(())
}
async fn execute_rollback_for_phase(
phase: &super::MigrationPhase,
config: &MigrationConfig,
result: &mut MigrationResult,
) -> Result<(), MigrationError> {
log_message(
config,
&format!("Executing rollback for phase: {}", phase.id),
);
for operation in &phase.rollback_operations {
if let Err(e) = execute_operation(operation, config, result).await {
return Err(MigrationError::RollbackError(format!(
"Rollback operation failed: {}",
e
)));
}
}
result.metrics.rollback_count += 1;
Ok(())
}
pub async fn rollback_migration(
plan: &MigrationPlan,
config: &MigrationConfig,
) -> Result<MigrationResult, MigrationError> {
let started_at = chrono::Utc::now();
let mut result = MigrationResult {
plan_id: plan.id.clone(),
status: MigrationStatus::InProgress,
started_at,
completed_at: None,
phases_completed: Vec::new(),
current_phase: Some("rollback".to_string()),
errors: Vec::new(),
warnings: Vec::new(),
metrics: MigrationMetrics {
roles_migrated: 0,
permissions_migrated: 0,
users_migrated: 0,
errors_encountered: 0,
warnings_generated: 0,
validation_failures: 0,
rollback_count: 0,
},
};
log_message(config, "Starting migration rollback");
for phase in plan.rollback_plan.phases.iter().rev() {
log_message(config, &format!("Executing rollback phase: {}", phase.id));
for operation in &phase.operations {
if let Err(e) = execute_operation(operation, config, &mut result).await {
result.status = MigrationStatus::Failed;
result
.errors
.push(format!("Rollback operation failed: {}", e));
save_migration_status(&result, config).await?;
return Ok(result);
}
}
result.phases_completed.push(phase.id.clone());
}
result.status = MigrationStatus::RolledBack;
result.completed_at = Some(chrono::Utc::now());
result.current_phase = None;
log_message(config, "Migration rollback completed");
save_migration_status(&result, config).await?;
Ok(result)
}
async fn validate_hierarchy_integrity(_config: &MigrationConfig) -> Result<(), MigrationError> {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
Ok(())
}
async fn validate_permission_consistency(_config: &MigrationConfig) -> Result<(), MigrationError> {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
Ok(())
}
async fn validate_user_assignments(_config: &MigrationConfig) -> Result<(), MigrationError> {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
Ok(())
}
async fn validate_no_privilege_escalation(_config: &MigrationConfig) -> Result<(), MigrationError> {
tokio::time::sleep(tokio::time::Duration::from_millis(150)).await;
Ok(())
}
async fn execute_custom_validation(
validation_name: &str,
_parameters: &HashMap<String, String>,
config: &MigrationConfig,
) -> Result<(), MigrationError> {
log_message(
config,
&format!("Executing custom validation: {}", validation_name),
);
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
Ok(())
}
async fn validate_pre_migration_state(_config: &MigrationConfig) -> Result<(), MigrationError> {
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
Ok(())
}
async fn validate_post_migration_state(_config: &MigrationConfig) -> Result<(), MigrationError> {
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
Ok(())
}
async fn create_full_backup(_config: &MigrationConfig) -> Result<String, MigrationError> {
Ok("FULL_BACKUP_DATA".to_string())
}
async fn create_incremental_backup(_config: &MigrationConfig) -> Result<String, MigrationError> {
Ok("INCREMENTAL_BACKUP_DATA".to_string())
}
async fn create_config_backup(_config: &MigrationConfig) -> Result<String, MigrationError> {
Ok("CONFIG_BACKUP_DATA".to_string())
}
async fn create_data_backup(_config: &MigrationConfig) -> Result<String, MigrationError> {
Ok("DATA_BACKUP_DATA".to_string())
}
async fn save_migration_status(
result: &MigrationResult,
config: &MigrationConfig,
) -> Result<(), MigrationError> {
let status_file = config
.working_directory
.join(format!("{}_status.json", result.plan_id));
let content = serde_json::to_string_pretty(result)?;
fs::write(status_file, content).await?;
Ok(())
}
fn log_message(config: &MigrationConfig, message: &str) {
if config.verbose {
let timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S");
println!("[{}] {}", timestamp, message);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::migration::{
LegacySystemAnalysis, LegacySystemType, MigrationPhase, MigrationStrategy, RiskLevel,
};
fn create_test_plan() -> MigrationPlan {
MigrationPlan {
id: "test_plan".to_string(),
source_analysis: LegacySystemAnalysis {
system_type: LegacySystemType::BasicRbac,
role_count: 1,
permission_count: 1,
user_assignment_count: 1,
roles: vec![],
permissions: vec![],
user_assignments: vec![],
hierarchy_depth: 0,
duplicates_found: false,
orphaned_permissions: vec![],
circular_dependencies: vec![],
custom_attributes: std::collections::HashSet::new(),
complexity_score: 3,
recommended_strategy: MigrationStrategy::DirectMapping,
},
strategy: MigrationStrategy::DirectMapping,
phases: vec![MigrationPhase {
id: "test_phase".to_string(),
name: "Test Phase".to_string(),
description: "Test phase".to_string(),
order: 1,
operations: vec![MigrationOperation::CreateRole {
role_id: "test_role".to_string(),
name: "Test Role".to_string(),
description: None,
permissions: vec!["read".to_string()],
parent_role: None,
}],
dependencies: vec![],
estimated_duration: chrono::Duration::minutes(1),
rollback_operations: vec![],
}],
role_mappings: std::collections::HashMap::new(),
permission_mappings: std::collections::HashMap::new(),
user_migrations: vec![],
pre_validation_steps: vec![],
post_validation_steps: vec![],
rollback_plan: super::super::RollbackPlan {
phases: vec![],
backup_locations: vec![],
recovery_time_objective: chrono::Duration::hours(1),
manual_steps: vec![],
},
estimated_duration: chrono::Duration::minutes(30),
risk_level: RiskLevel::Low,
downtime_required: None,
}
}
#[tokio::test]
async fn test_execute_migration_plan_dry_run() {
let plan = create_test_plan();
let config = MigrationConfig {
dry_run: true,
verbose: false, ..Default::default()
};
let result = execute_migration_plan(&plan, &config).await.unwrap();
assert_eq!(result.status, MigrationStatus::Completed);
assert_eq!(result.phases_completed.len(), 1);
assert_eq!(result.metrics.roles_migrated, 1);
}
#[tokio::test]
async fn test_execute_migration_plan_real() {
let plan = create_test_plan();
let config = MigrationConfig {
dry_run: false,
verbose: false, ..Default::default()
};
let result = execute_migration_plan(&plan, &config).await.unwrap();
assert_eq!(result.status, MigrationStatus::Completed);
assert_eq!(result.phases_completed.len(), 1);
assert_eq!(result.metrics.roles_migrated, 1);
}
}