1use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9use std::path::PathBuf;
10use thiserror::Error;
11use tokio::fs;
12
13pub mod analyzers;
14pub mod converters;
15pub mod executors;
16pub mod planners;
17pub mod validators;
18
19#[derive(Error, Debug)]
21pub enum MigrationError {
22 #[error("Legacy system analysis failed: {0}")]
23 AnalysisError(String),
24
25 #[error("Migration plan generation failed: {0}")]
26 PlanningError(String),
27
28 #[error("Migration execution failed: {0}")]
29 ExecutionError(String),
30
31 #[error("Migration validation failed: {0}")]
32 ValidationError(String),
33
34 #[error("Rollback operation failed: {0}")]
35 RollbackError(String),
36
37 #[error("I/O error: {0}")]
38 IoError(#[from] std::io::Error),
39
40 #[error("Serialization error: {0}")]
41 SerializationError(#[from] serde_json::Error),
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46pub enum LegacySystemType {
47 PermissionBased,
49 BasicRbac,
51 Abac,
53 Custom(String),
55 Hybrid(Vec<LegacySystemType>),
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct LegacyRole {
62 pub id: String,
63 pub name: String,
64 pub description: Option<String>,
65 pub permissions: Vec<String>,
66 pub parent_roles: Vec<String>,
67 pub metadata: HashMap<String, String>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct LegacyUserAssignment {
73 pub user_id: String,
74 pub role_id: Option<String>,
75 pub permissions: Vec<String>,
76 pub attributes: HashMap<String, String>,
77 pub expiration: Option<chrono::DateTime<chrono::Utc>>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct LegacyPermission {
83 pub id: String,
84 pub action: String,
85 pub resource: String,
86 pub conditions: HashMap<String, String>,
87 pub metadata: HashMap<String, String>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct LegacySystemAnalysis {
93 pub system_type: LegacySystemType,
95
96 pub role_count: usize,
98
99 pub permission_count: usize,
101
102 pub user_assignment_count: usize,
104
105 pub roles: Vec<LegacyRole>,
107
108 pub permissions: Vec<LegacyPermission>,
110
111 pub user_assignments: Vec<LegacyUserAssignment>,
113
114 pub hierarchy_depth: usize,
116
117 pub duplicates_found: bool,
119
120 pub orphaned_permissions: Vec<String>,
122
123 pub circular_dependencies: Vec<Vec<String>>,
125
126 pub custom_attributes: HashSet<String>,
128
129 pub complexity_score: u8,
131
132 pub recommended_strategy: MigrationStrategy,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
138pub enum MigrationStrategy {
139 DirectMapping,
141 GradualMigration,
143 Rebuild,
145 Custom(String),
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct MigrationPlan {
152 pub id: String,
154
155 pub source_analysis: LegacySystemAnalysis,
157
158 pub strategy: MigrationStrategy,
160
161 pub phases: Vec<MigrationPhase>,
163
164 pub role_mappings: HashMap<String, String>,
166
167 pub permission_mappings: HashMap<String, String>,
169
170 pub user_migrations: Vec<UserMigration>,
172
173 pub pre_validation_steps: Vec<ValidationStep>,
175
176 pub post_validation_steps: Vec<ValidationStep>,
178
179 pub rollback_plan: RollbackPlan,
181
182 pub estimated_duration: chrono::Duration,
184
185 pub risk_level: RiskLevel,
187
188 pub downtime_required: Option<chrono::Duration>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct MigrationPhase {
195 pub id: String,
196 pub name: String,
197 pub description: String,
198 pub order: u32,
199 pub operations: Vec<MigrationOperation>,
200 pub dependencies: Vec<String>,
201 pub estimated_duration: chrono::Duration,
202 pub rollback_operations: Vec<MigrationOperation>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub enum MigrationOperation {
208 CreateRole {
209 role_id: String,
210 name: String,
211 description: Option<String>,
212 permissions: Vec<String>,
213 parent_role: Option<String>,
214 },
215 AssignUserRole {
216 user_id: String,
217 role_id: String,
218 expiration: Option<chrono::DateTime<chrono::Utc>>,
219 },
220 CreatePermission {
221 permission_id: String,
222 action: String,
223 resource: String,
224 conditions: HashMap<String, String>,
225 },
226 MigrateCustomAttribute {
227 attribute_name: String,
228 conversion_logic: String,
229 },
230 ValidateIntegrity {
231 validation_type: String,
232 parameters: HashMap<String, String>,
233 },
234 Backup {
235 backup_location: PathBuf,
236 backup_type: BackupType,
237 },
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct UserMigration {
243 pub user_id: String,
244 pub legacy_roles: Vec<String>,
245 pub legacy_permissions: Vec<String>,
246 pub new_roles: Vec<String>,
247 pub migration_notes: Option<String>,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct ValidationStep {
253 pub id: String,
254 pub name: String,
255 pub description: String,
256 pub validation_type: ValidationType,
257 pub parameters: HashMap<String, String>,
258 pub required: bool,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
263pub enum ValidationType {
264 HierarchyIntegrity,
266 PermissionConsistency,
268 UserAssignmentValidity,
270 PrivilegeEscalationCheck,
272 Custom(String),
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct RollbackPlan {
279 pub phases: Vec<RollbackPhase>,
280 pub backup_locations: Vec<PathBuf>,
281 pub recovery_time_objective: chrono::Duration,
282 pub manual_steps: Vec<String>,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct RollbackPhase {
288 pub id: String,
289 pub name: String,
290 pub operations: Vec<MigrationOperation>,
291 pub order: u32,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
296pub enum RiskLevel {
297 Low,
298 Medium,
299 High,
300 Critical,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
305pub enum BackupType {
306 Full,
308 Incremental,
310 ConfigOnly,
312 DataOnly,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct MigrationResult {
319 pub plan_id: String,
320 pub status: MigrationStatus,
321 pub started_at: chrono::DateTime<chrono::Utc>,
322 pub completed_at: Option<chrono::DateTime<chrono::Utc>>,
323 pub phases_completed: Vec<String>,
324 pub current_phase: Option<String>,
325 pub errors: Vec<String>,
326 pub warnings: Vec<String>,
327 pub metrics: MigrationMetrics,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
332pub enum MigrationStatus {
333 Planned,
334 InProgress,
335 Completed,
336 Failed,
337 RolledBack,
338 Paused,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct MigrationMetrics {
344 pub roles_migrated: usize,
345 pub permissions_migrated: usize,
346 pub users_migrated: usize,
347 pub errors_encountered: usize,
348 pub warnings_generated: usize,
349 pub validation_failures: usize,
350 pub rollback_count: usize,
351}
352
353pub struct MigrationManager {
355 config: MigrationConfig,
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct MigrationConfig {
362 pub working_directory: PathBuf,
364
365 pub backup_directory: PathBuf,
367
368 pub max_concurrent_operations: usize,
370
371 pub operation_timeout: chrono::Duration,
373
374 pub dry_run: bool,
376
377 pub verbose: bool,
379}
380
381impl Default for MigrationConfig {
382 fn default() -> Self {
383 Self {
384 working_directory: PathBuf::from("./migration"),
385 backup_directory: PathBuf::from("./migration/backups"),
386 max_concurrent_operations: 4,
387 operation_timeout: chrono::Duration::minutes(30),
388 dry_run: false,
389 verbose: false,
390 }
391 }
392}
393
394impl MigrationManager {
395 pub fn new(config: MigrationConfig) -> Result<Self, MigrationError> {
397 std::fs::create_dir_all(&config.working_directory)?;
399 std::fs::create_dir_all(&config.backup_directory)?;
400
401 Ok(Self { config })
402 }
403
404 pub async fn analyze_legacy_system<P: AsRef<std::path::Path>>(
406 &self,
407 config_path: P,
408 ) -> Result<LegacySystemAnalysis, MigrationError> {
409 analyzers::analyze_legacy_system(config_path, &self.config).await
410 }
411
412 pub async fn generate_migration_plan(
414 &self,
415 analysis: &LegacySystemAnalysis,
416 strategy: Option<MigrationStrategy>,
417 ) -> Result<MigrationPlan, MigrationError> {
418 planners::generate_migration_plan(analysis, strategy, &self.config).await
419 }
420
421 pub async fn validate_migration_plan(
423 &self,
424 plan: &MigrationPlan,
425 ) -> Result<Vec<String>, MigrationError> {
426 validators::validate_migration_plan(plan, &self.config).await
427 }
428
429 pub async fn execute_migration(
431 &self,
432 plan: &MigrationPlan,
433 ) -> Result<MigrationResult, MigrationError> {
434 executors::execute_migration_plan(plan, &self.config).await
435 }
436
437 pub async fn get_migration_status(
439 &self,
440 plan_id: &str,
441 ) -> Result<Option<MigrationResult>, MigrationError> {
442 let status_file = self
443 .config
444 .working_directory
445 .join(format!("{}_status.json", plan_id));
446
447 if !status_file.exists() {
448 return Ok(None);
449 }
450
451 let content = fs::read_to_string(status_file).await?;
452 let result: MigrationResult = serde_json::from_str(&content)?;
453 Ok(Some(result))
454 }
455
456 pub async fn rollback_migration(
458 &self,
459 plan: &MigrationPlan,
460 ) -> Result<MigrationResult, MigrationError> {
461 executors::rollback_migration(plan, &self.config).await
462 }
463
464 pub async fn list_migration_plans(&self) -> Result<Vec<String>, MigrationError> {
466 let mut plans = Vec::new();
467 let mut entries = fs::read_dir(&self.config.working_directory).await?;
468
469 while let Some(entry) = entries.next_entry().await? {
470 let path = entry.path();
471 if path.extension().is_some_and(|ext| ext == "json")
472 && let Some(file_name) = path.file_stem()
473 && let Some(name) = file_name.to_str()
474 && name.ends_with("_plan")
475 {
476 plans.push(name.trim_end_matches("_plan").to_string());
477 }
478 }
479
480 Ok(plans)
481 }
482
483 pub async fn save_migration_plan(
485 &self,
486 plan: &MigrationPlan,
487 ) -> Result<PathBuf, MigrationError> {
488 let plan_file = self
489 .config
490 .working_directory
491 .join(format!("{}_plan.json", plan.id));
492 let content = serde_json::to_string_pretty(plan)?;
493 fs::write(&plan_file, content).await?;
494 Ok(plan_file)
495 }
496
497 pub async fn load_migration_plan(
499 &self,
500 plan_id: &str,
501 ) -> Result<MigrationPlan, MigrationError> {
502 let plan_file = self
503 .config
504 .working_directory
505 .join(format!("{}_plan.json", plan_id));
506 let content = fs::read_to_string(plan_file).await?;
507 let plan: MigrationPlan = serde_json::from_str(&content)?;
508 Ok(plan)
509 }
510
511 pub async fn generate_migration_report(
513 &self,
514 result: &MigrationResult,
515 ) -> Result<String, MigrationError> {
516 let mut report = String::new();
517
518 report.push_str("# Migration Report\n\n");
519 report.push_str(&format!("**Plan ID**: {}\n", result.plan_id));
520 report.push_str(&format!("**Status**: {:?}\n", result.status));
521 report.push_str(&format!("**Started**: {}\n", result.started_at));
522
523 if let Some(completed) = result.completed_at {
524 report.push_str(&format!("**Completed**: {}\n", completed));
525 let duration = completed - result.started_at;
526 report.push_str(&format!(
527 "**Duration**: {} minutes\n",
528 duration.num_minutes()
529 ));
530 }
531
532 report.push_str("\n## Metrics\n\n");
533 report.push_str(&format!(
534 "- Roles migrated: {}\n",
535 result.metrics.roles_migrated
536 ));
537 report.push_str(&format!(
538 "- Permissions migrated: {}\n",
539 result.metrics.permissions_migrated
540 ));
541 report.push_str(&format!(
542 "- Users migrated: {}\n",
543 result.metrics.users_migrated
544 ));
545 report.push_str(&format!(
546 "- Errors: {}\n",
547 result.metrics.errors_encountered
548 ));
549 report.push_str(&format!(
550 "- Warnings: {}\n",
551 result.metrics.warnings_generated
552 ));
553
554 if !result.errors.is_empty() {
555 report.push_str("\n## Errors\n\n");
556 for error in &result.errors {
557 report.push_str(&format!("- {}\n", error));
558 }
559 }
560
561 if !result.warnings.is_empty() {
562 report.push_str("\n## Warnings\n\n");
563 for warning in &result.warnings {
564 report.push_str(&format!("- {}\n", warning));
565 }
566 }
567
568 Ok(report)
569 }
570}
571
572#[cfg(test)]
573mod tests {
574 use super::*;
575
576 #[tokio::test]
577 async fn test_migration_manager_creation() {
578 let config = MigrationConfig::default();
579 let manager = MigrationManager::new(config);
580 assert!(manager.is_ok());
581 }
582
583 #[test]
584 fn test_legacy_system_type_serialization() {
585 let system_type = LegacySystemType::BasicRbac;
586 let serialized = serde_json::to_string(&system_type).unwrap();
587 let deserialized: LegacySystemType = serde_json::from_str(&serialized).unwrap();
588 assert_eq!(system_type, deserialized);
589 }
590
591 #[test]
592 fn test_migration_strategy_serialization() {
593 let strategy = MigrationStrategy::GradualMigration;
594 let serialized = serde_json::to_string(&strategy).unwrap();
595 let deserialized: MigrationStrategy = serde_json::from_str(&serialized).unwrap();
596 assert_eq!(strategy, deserialized);
597 }
598
599 #[test]
600 fn test_risk_level_ordering() {
601 assert!(RiskLevel::Low < RiskLevel::Medium);
602 assert!(RiskLevel::Medium < RiskLevel::High);
603 assert!(RiskLevel::High < RiskLevel::Critical);
604 }
605}
606
607