1use super::{
7 MigrationConfig, MigrationError, MigrationMetrics, MigrationOperation, MigrationPlan,
8 MigrationResult, MigrationStatus,
9};
10use std::collections::HashMap;
11use tokio::fs;
12use uuid::Uuid;
13
14pub async fn execute_migration_plan(
16 plan: &MigrationPlan,
17 config: &MigrationConfig,
18) -> Result<MigrationResult, MigrationError> {
19 let _execution_id = Uuid::new_v4().to_string();
20 let started_at = chrono::Utc::now();
21
22 let mut result = MigrationResult {
23 plan_id: plan.id.clone(),
24 status: MigrationStatus::InProgress,
25 started_at,
26 completed_at: None,
27 phases_completed: Vec::new(),
28 current_phase: None,
29 errors: Vec::new(),
30 warnings: Vec::new(),
31 metrics: MigrationMetrics {
32 roles_migrated: 0,
33 permissions_migrated: 0,
34 users_migrated: 0,
35 errors_encountered: 0,
36 warnings_generated: 0,
37 validation_failures: 0,
38 rollback_count: 0,
39 },
40 };
41
42 save_migration_status(&result, config).await?;
44
45 if config.dry_run {
46 log_message(config, "DRY RUN MODE - No actual changes will be made");
47 return execute_dry_run(plan, config, result).await;
48 }
49
50 if let Err(e) = execute_pre_validation(plan, config, &mut result).await {
52 result.status = MigrationStatus::Failed;
53 result.errors.push(format!("Pre-validation failed: {}", e));
54 save_migration_status(&result, config).await?;
55 return Ok(result);
56 }
57
58 for phase in &plan.phases {
60 result.current_phase = Some(phase.id.clone());
61 save_migration_status(&result, config).await?;
62
63 log_message(
64 config,
65 &format!("Executing phase: {} - {}", phase.id, phase.name),
66 );
67
68 match execute_phase(phase, config, &mut result).await {
69 Ok(_) => {
70 result.phases_completed.push(phase.id.clone());
71 log_message(
72 config,
73 &format!("Phase '{}' completed successfully", phase.id),
74 );
75 }
76 Err(e) => {
77 result.status = MigrationStatus::Failed;
78 result
79 .errors
80 .push(format!("Phase '{}' failed: {}", phase.id, e));
81 result.metrics.errors_encountered += 1;
82
83 log_message(config, &format!("Phase '{}' failed: {}", phase.id, e));
84
85 if let Err(rollback_error) =
87 execute_rollback_for_phase(phase, config, &mut result).await
88 {
89 result.errors.push(format!(
90 "Rollback for phase '{}' failed: {}",
91 phase.id, rollback_error
92 ));
93 }
94
95 save_migration_status(&result, config).await?;
96 return Ok(result);
97 }
98 }
99 }
100
101 if let Err(e) = execute_post_validation(plan, config, &mut result).await {
103 result.status = MigrationStatus::Failed;
104 result.errors.push(format!("Post-validation failed: {}", e));
105 save_migration_status(&result, config).await?;
106 return Ok(result);
107 }
108
109 result.status = MigrationStatus::Completed;
111 result.completed_at = Some(chrono::Utc::now());
112 result.current_phase = None;
113
114 log_message(config, "Migration completed successfully");
115 save_migration_status(&result, config).await?;
116
117 Ok(result)
118}
119
120async fn execute_dry_run(
122 plan: &MigrationPlan,
123 config: &MigrationConfig,
124 mut result: MigrationResult,
125) -> Result<MigrationResult, MigrationError> {
126 log_message(config, "=== DRY RUN EXECUTION ===");
127
128 for phase in &plan.phases {
129 log_message(
130 config,
131 &format!("DRY RUN - Phase: {} - {}", phase.id, phase.name),
132 );
133
134 for operation in &phase.operations {
135 match operation {
136 MigrationOperation::CreateRole { role_id, name, .. } => {
137 log_message(
138 config,
139 &format!(" [DRY RUN] Would create role: {} ({})", role_id, name),
140 );
141 result.metrics.roles_migrated += 1;
142 }
143 MigrationOperation::CreatePermission {
144 permission_id,
145 action,
146 resource,
147 ..
148 } => {
149 log_message(
150 config,
151 &format!(
152 " [DRY RUN] Would create permission: {} ({}:{})",
153 permission_id, action, resource
154 ),
155 );
156 result.metrics.permissions_migrated += 1;
157 }
158 MigrationOperation::AssignUserRole {
159 user_id, role_id, ..
160 } => {
161 log_message(
162 config,
163 &format!(
164 " [DRY RUN] Would assign role {} to user {}",
165 role_id, user_id
166 ),
167 );
168 result.metrics.users_migrated += 1;
169 }
170 MigrationOperation::Backup {
171 backup_location,
172 backup_type,
173 } => {
174 log_message(
175 config,
176 &format!(
177 " [DRY RUN] Would create {:?} backup at {:?}",
178 backup_type, backup_location
179 ),
180 );
181 }
182 MigrationOperation::ValidateIntegrity {
183 validation_type, ..
184 } => {
185 log_message(
186 config,
187 &format!(" [DRY RUN] Would validate: {}", validation_type),
188 );
189 }
190 MigrationOperation::MigrateCustomAttribute { attribute_name, .. } => {
191 log_message(
192 config,
193 &format!(
194 " [DRY RUN] Would migrate custom attribute: {}",
195 attribute_name
196 ),
197 );
198 }
199 }
200 }
201
202 result.phases_completed.push(phase.id.clone());
203 }
204
205 result.status = MigrationStatus::Completed;
206 result.completed_at = Some(chrono::Utc::now());
207
208 log_message(config, "=== DRY RUN COMPLETED ===");
209
210 Ok(result)
211}
212
213async fn execute_pre_validation(
215 plan: &MigrationPlan,
216 config: &MigrationConfig,
217 result: &mut MigrationResult,
218) -> Result<(), MigrationError> {
219 log_message(config, "Executing pre-validation steps");
220
221 for step in &plan.pre_validation_steps {
222 log_message(
223 config,
224 &format!("Pre-validation: {} - {}", step.id, step.name),
225 );
226
227 match execute_validation_step(step, config).await {
228 Ok(_) => {
229 log_message(config, &format!("Pre-validation '{}' passed", step.id));
230 }
231 Err(e) => {
232 if step.required {
233 return Err(MigrationError::ValidationError(format!(
234 "Required pre-validation '{}' failed: {}",
235 step.id, e
236 )));
237 } else {
238 result.warnings.push(format!(
239 "Optional pre-validation '{}' failed: {}",
240 step.id, e
241 ));
242 result.metrics.warnings_generated += 1;
243 }
244 }
245 }
246 }
247
248 Ok(())
249}
250
251async fn execute_post_validation(
253 plan: &MigrationPlan,
254 config: &MigrationConfig,
255 result: &mut MigrationResult,
256) -> Result<(), MigrationError> {
257 log_message(config, "Executing post-validation steps");
258
259 for step in &plan.post_validation_steps {
260 log_message(
261 config,
262 &format!("Post-validation: {} - {}", step.id, step.name),
263 );
264
265 match execute_validation_step(step, config).await {
266 Ok(_) => {
267 log_message(config, &format!("Post-validation '{}' passed", step.id));
268 }
269 Err(e) => {
270 if step.required {
271 result.metrics.validation_failures += 1;
272 return Err(MigrationError::ValidationError(format!(
273 "Required post-validation '{}' failed: {}",
274 step.id, e
275 )));
276 } else {
277 result.warnings.push(format!(
278 "Optional post-validation '{}' failed: {}",
279 step.id, e
280 ));
281 result.metrics.warnings_generated += 1;
282 }
283 }
284 }
285 }
286
287 Ok(())
288}
289
290async fn execute_validation_step(
292 step: &super::ValidationStep,
293 config: &MigrationConfig,
294) -> Result<(), MigrationError> {
295 use super::ValidationType;
296
297 match &step.validation_type {
298 ValidationType::HierarchyIntegrity => validate_hierarchy_integrity(config).await,
299 ValidationType::PermissionConsistency => validate_permission_consistency(config).await,
300 ValidationType::UserAssignmentValidity => validate_user_assignments(config).await,
301 ValidationType::PrivilegeEscalationCheck => validate_no_privilege_escalation(config).await,
302 ValidationType::Custom(validation_name) => {
303 execute_custom_validation(validation_name, &step.parameters, config).await
304 }
305 }
306}
307
308async fn execute_phase(
310 phase: &super::MigrationPhase,
311 config: &MigrationConfig,
312 result: &mut MigrationResult,
313) -> Result<(), MigrationError> {
314 for operation in &phase.operations {
315 if let Err(e) = execute_operation(operation, config, result).await {
316 return Err(MigrationError::ExecutionError(format!(
317 "Operation failed in phase '{}': {}",
318 phase.id, e
319 )));
320 }
321 }
322 Ok(())
323}
324
325async fn execute_operation(
327 operation: &MigrationOperation,
328 config: &MigrationConfig,
329 result: &mut MigrationResult,
330) -> Result<(), MigrationError> {
331 match operation {
332 MigrationOperation::CreateRole {
333 role_id,
334 name,
335 description,
336 permissions,
337 parent_role,
338 } => {
339 execute_create_role(
340 role_id,
341 name,
342 description.as_deref(),
343 permissions,
344 parent_role.as_deref(),
345 config,
346 )
347 .await?;
348 result.metrics.roles_migrated += 1;
349 }
350 MigrationOperation::CreatePermission {
351 permission_id,
352 action,
353 resource,
354 conditions,
355 } => {
356 execute_create_permission(permission_id, action, resource, conditions, config).await?;
357 result.metrics.permissions_migrated += 1;
358 }
359 MigrationOperation::AssignUserRole {
360 user_id,
361 role_id,
362 expiration,
363 } => {
364 execute_assign_user_role(user_id, role_id, expiration.as_ref(), config).await?;
365 result.metrics.users_migrated += 1;
366 }
367 MigrationOperation::Backup {
368 backup_location,
369 backup_type,
370 } => {
371 execute_backup(backup_location, backup_type, config).await?;
372 }
373 MigrationOperation::ValidateIntegrity {
374 validation_type,
375 parameters,
376 } => {
377 execute_integrity_validation(validation_type, parameters, config).await?;
378 }
379 MigrationOperation::MigrateCustomAttribute {
380 attribute_name,
381 conversion_logic,
382 } => {
383 execute_custom_attribute_migration(attribute_name, conversion_logic, config).await?;
384 }
385 }
386
387 Ok(())
388}
389
390async fn execute_create_role(
392 role_id: &str,
393 name: &str,
394 description: Option<&str>,
395 permissions: &[String],
396 parent_role: Option<&str>,
397 config: &MigrationConfig,
398) -> Result<(), MigrationError> {
399 log_message(config, &format!("Creating role: {} ({})", role_id, name));
400
401 if config.verbose {
405 log_message(config, &format!(" Description: {:?}", description));
406 log_message(config, &format!(" Permissions: {:?}", permissions));
407 log_message(config, &format!(" Parent role: {:?}", parent_role));
408 }
409
410 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
412
413 Ok(())
419}
420
421async fn execute_create_permission(
423 permission_id: &str,
424 action: &str,
425 resource: &str,
426 conditions: &HashMap<String, String>,
427 config: &MigrationConfig,
428) -> Result<(), MigrationError> {
429 log_message(
430 config,
431 &format!(
432 "Creating permission: {} ({}:{})",
433 permission_id, action, resource
434 ),
435 );
436
437 if config.verbose {
438 log_message(config, &format!(" Conditions: {:?}", conditions));
439 }
440
441 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
443
444 Ok(())
450}
451
452async fn execute_assign_user_role(
454 user_id: &str,
455 role_id: &str,
456 expiration: Option<&chrono::DateTime<chrono::Utc>>,
457 config: &MigrationConfig,
458) -> Result<(), MigrationError> {
459 log_message(
460 config,
461 &format!("Assigning role {} to user {}", role_id, user_id),
462 );
463
464 if config.verbose {
465 log_message(config, &format!(" Expiration: {:?}", expiration));
466 }
467
468 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
470
471 Ok(())
477}
478
479async fn execute_backup(
481 backup_location: &std::path::Path,
482 backup_type: &super::BackupType,
483 config: &MigrationConfig,
484) -> Result<(), MigrationError> {
485 log_message(
486 config,
487 &format!("Creating {:?} backup at {:?}", backup_type, backup_location),
488 );
489
490 if let Some(parent) = backup_location.parent() {
492 fs::create_dir_all(parent).await?;
493 }
494
495 let backup_data = match backup_type {
497 super::BackupType::Full => create_full_backup(config).await?,
498 super::BackupType::Incremental => create_incremental_backup(config).await?,
499 super::BackupType::ConfigOnly => create_config_backup(config).await?,
500 super::BackupType::DataOnly => create_data_backup(config).await?,
501 };
502
503 fs::write(backup_location, backup_data).await?;
504
505 log_message(
506 config,
507 &format!("Backup created successfully at {:?}", backup_location),
508 );
509
510 Ok(())
511}
512
513async fn execute_integrity_validation(
515 validation_type: &str,
516 parameters: &HashMap<String, String>,
517 config: &MigrationConfig,
518) -> Result<(), MigrationError> {
519 log_message(
520 config,
521 &format!("Executing integrity validation: {}", validation_type),
522 );
523
524 if config.verbose {
525 log_message(config, &format!(" Parameters: {:?}", parameters));
526 }
527
528 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
530
531 match validation_type {
532 "pre_migration_check" => validate_pre_migration_state(config).await,
533 "post_migration_check" => validate_post_migration_state(config).await,
534 "stop_migration" => Ok(()), _ => {
536 log_message(
537 config,
538 &format!("Unknown validation type: {}", validation_type),
539 );
540 Ok(())
541 }
542 }
543}
544
545async fn execute_custom_attribute_migration(
547 attribute_name: &str,
548 conversion_logic: &str,
549 config: &MigrationConfig,
550) -> Result<(), MigrationError> {
551 log_message(
552 config,
553 &format!("Migrating custom attribute: {}", attribute_name),
554 );
555
556 if config.verbose {
557 log_message(config, &format!(" Conversion logic: {}", conversion_logic));
558 }
559
560 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
562
563 Ok(())
567}
568
569async fn execute_rollback_for_phase(
571 phase: &super::MigrationPhase,
572 config: &MigrationConfig,
573 result: &mut MigrationResult,
574) -> Result<(), MigrationError> {
575 log_message(
576 config,
577 &format!("Executing rollback for phase: {}", phase.id),
578 );
579
580 for operation in &phase.rollback_operations {
581 if let Err(e) = execute_operation(operation, config, result).await {
582 return Err(MigrationError::RollbackError(format!(
583 "Rollback operation failed: {}",
584 e
585 )));
586 }
587 }
588
589 result.metrics.rollback_count += 1;
590 Ok(())
591}
592
593pub async fn rollback_migration(
595 plan: &MigrationPlan,
596 config: &MigrationConfig,
597) -> Result<MigrationResult, MigrationError> {
598 let started_at = chrono::Utc::now();
599
600 let mut result = MigrationResult {
601 plan_id: plan.id.clone(),
602 status: MigrationStatus::InProgress,
603 started_at,
604 completed_at: None,
605 phases_completed: Vec::new(),
606 current_phase: Some("rollback".to_string()),
607 errors: Vec::new(),
608 warnings: Vec::new(),
609 metrics: MigrationMetrics {
610 roles_migrated: 0,
611 permissions_migrated: 0,
612 users_migrated: 0,
613 errors_encountered: 0,
614 warnings_generated: 0,
615 validation_failures: 0,
616 rollback_count: 0,
617 },
618 };
619
620 log_message(config, "Starting migration rollback");
621
622 for phase in plan.rollback_plan.phases.iter().rev() {
624 log_message(config, &format!("Executing rollback phase: {}", phase.id));
625
626 for operation in &phase.operations {
627 if let Err(e) = execute_operation(operation, config, &mut result).await {
628 result.status = MigrationStatus::Failed;
629 result
630 .errors
631 .push(format!("Rollback operation failed: {}", e));
632 save_migration_status(&result, config).await?;
633 return Ok(result);
634 }
635 }
636
637 result.phases_completed.push(phase.id.clone());
638 }
639
640 result.status = MigrationStatus::RolledBack;
641 result.completed_at = Some(chrono::Utc::now());
642 result.current_phase = None;
643
644 log_message(config, "Migration rollback completed");
645 save_migration_status(&result, config).await?;
646
647 Ok(result)
648}
649
650async fn validate_hierarchy_integrity(_config: &MigrationConfig) -> Result<(), MigrationError> {
652 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
654 Ok(())
655}
656
657async fn validate_permission_consistency(_config: &MigrationConfig) -> Result<(), MigrationError> {
658 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
660 Ok(())
661}
662
663async fn validate_user_assignments(_config: &MigrationConfig) -> Result<(), MigrationError> {
664 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
666 Ok(())
667}
668
669async fn validate_no_privilege_escalation(_config: &MigrationConfig) -> Result<(), MigrationError> {
670 tokio::time::sleep(tokio::time::Duration::from_millis(150)).await;
672 Ok(())
673}
674
675async fn execute_custom_validation(
676 validation_name: &str,
677 _parameters: &HashMap<String, String>,
678 config: &MigrationConfig,
679) -> Result<(), MigrationError> {
680 log_message(
681 config,
682 &format!("Executing custom validation: {}", validation_name),
683 );
684 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
685 Ok(())
686}
687
688async fn validate_pre_migration_state(_config: &MigrationConfig) -> Result<(), MigrationError> {
689 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
691 Ok(())
692}
693
694async fn validate_post_migration_state(_config: &MigrationConfig) -> Result<(), MigrationError> {
695 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
697 Ok(())
698}
699
700async fn create_full_backup(_config: &MigrationConfig) -> Result<String, MigrationError> {
702 Ok("FULL_BACKUP_DATA".to_string())
703}
704
705async fn create_incremental_backup(_config: &MigrationConfig) -> Result<String, MigrationError> {
706 Ok("INCREMENTAL_BACKUP_DATA".to_string())
707}
708
709async fn create_config_backup(_config: &MigrationConfig) -> Result<String, MigrationError> {
710 Ok("CONFIG_BACKUP_DATA".to_string())
711}
712
713async fn create_data_backup(_config: &MigrationConfig) -> Result<String, MigrationError> {
714 Ok("DATA_BACKUP_DATA".to_string())
715}
716
717async fn save_migration_status(
719 result: &MigrationResult,
720 config: &MigrationConfig,
721) -> Result<(), MigrationError> {
722 let status_file = config
723 .working_directory
724 .join(format!("{}_status.json", result.plan_id));
725 let content = serde_json::to_string_pretty(result)?;
726 fs::write(status_file, content).await?;
727 Ok(())
728}
729
730fn log_message(config: &MigrationConfig, message: &str) {
732 if config.verbose {
733 let timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S");
734 println!("[{}] {}", timestamp, message);
735 }
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741 use crate::migration::{
742 LegacySystemAnalysis, LegacySystemType, MigrationPhase, MigrationStrategy, RiskLevel,
743 };
744
745 fn create_test_plan() -> MigrationPlan {
746 MigrationPlan {
747 id: "test_plan".to_string(),
748 source_analysis: LegacySystemAnalysis {
749 system_type: LegacySystemType::BasicRbac,
750 role_count: 1,
751 permission_count: 1,
752 user_assignment_count: 1,
753 roles: vec![],
754 permissions: vec![],
755 user_assignments: vec![],
756 hierarchy_depth: 0,
757 duplicates_found: false,
758 orphaned_permissions: vec![],
759 circular_dependencies: vec![],
760 custom_attributes: std::collections::HashSet::new(),
761 complexity_score: 3,
762 recommended_strategy: MigrationStrategy::DirectMapping,
763 },
764 strategy: MigrationStrategy::DirectMapping,
765 phases: vec![MigrationPhase {
766 id: "test_phase".to_string(),
767 name: "Test Phase".to_string(),
768 description: "Test phase".to_string(),
769 order: 1,
770 operations: vec![MigrationOperation::CreateRole {
771 role_id: "test_role".to_string(),
772 name: "Test Role".to_string(),
773 description: None,
774 permissions: vec!["read".to_string()],
775 parent_role: None,
776 }],
777 dependencies: vec![],
778 estimated_duration: chrono::Duration::minutes(1),
779 rollback_operations: vec![],
780 }],
781 role_mappings: std::collections::HashMap::new(),
782 permission_mappings: std::collections::HashMap::new(),
783 user_migrations: vec![],
784 pre_validation_steps: vec![],
785 post_validation_steps: vec![],
786 rollback_plan: super::super::RollbackPlan {
787 phases: vec![],
788 backup_locations: vec![],
789 recovery_time_objective: chrono::Duration::hours(1),
790 manual_steps: vec![],
791 },
792 estimated_duration: chrono::Duration::minutes(30),
793 risk_level: RiskLevel::Low,
794 downtime_required: None,
795 }
796 }
797
798 #[tokio::test]
799 async fn test_execute_migration_plan_dry_run() {
800 let plan = create_test_plan();
801 let config = MigrationConfig {
802 dry_run: true,
803 verbose: false, ..Default::default()
805 };
806
807 let result = execute_migration_plan(&plan, &config).await.unwrap();
808
809 assert_eq!(result.status, MigrationStatus::Completed);
810 assert_eq!(result.phases_completed.len(), 1);
811 assert_eq!(result.metrics.roles_migrated, 1);
812 }
813
814 #[tokio::test]
815 async fn test_execute_migration_plan_real() {
816 let plan = create_test_plan();
817 let config = MigrationConfig {
818 dry_run: false,
819 verbose: false, ..Default::default()
821 };
822
823 let result = execute_migration_plan(&plan, &config).await.unwrap();
824
825 assert_eq!(result.status, MigrationStatus::Completed);
826 assert_eq!(result.phases_completed.len(), 1);
827 assert_eq!(result.metrics.roles_migrated, 1);
828 }
829}
830
831