""" Migration orchestrator - runs all migration steps in order. This script coordinates the full migration from VBScript ShopDB to Flask. Usage: python -m scripts.migration.run_migration --source """ import argparse import logging import sys from datetime import datetime logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) def run_full_migration(source_conn_str, target_conn_str, dry_run=False, steps=None): """ Run the full migration process. Args: source_conn_str: Source database connection string target_conn_str: Target database connection string dry_run: If True, don't commit changes steps: List of specific steps to run, or None for all """ from . import migrate_assets from . import migrate_communications from . import migrate_notifications from . import migrate_usb from . import verify_migration all_steps = [ ('assets', 'Migrate machines to assets', migrate_assets.run_migration), ('communications', 'Update communications FKs', migrate_communications.run_migration), ('notifications', 'Migrate notifications', migrate_notifications.run_migration), ('usb', 'Migrate USB devices', migrate_usb.run_migration), ] logger.info("=" * 60) logger.info("SHOPDB MIGRATION") logger.info(f"Started: {datetime.utcnow().isoformat()}") logger.info(f"Dry Run: {dry_run}") logger.info("=" * 60) results = {} for step_name, description, migration_func in all_steps: if steps and step_name not in steps: logger.info(f"\nSkipping: {description}") continue logger.info(f"\n{'=' * 40}") logger.info(f"Step: {description}") logger.info('=' * 40) try: # Different migrations have different signatures if step_name == 'communications': migration_func(target_conn_str, dry_run) else: migration_func(source_conn_str, target_conn_str, dry_run) results[step_name] = 'SUCCESS' logger.info(f"Step completed: {step_name}") except Exception as e: results[step_name] = f'FAILED: {e}' logger.error(f"Step failed: {step_name} - {e}") # Ask to continue if not dry_run: response = input("Continue with next step? (y/n): ") if response.lower() != 'y': logger.info("Migration aborted by user") break # Run verification logger.info(f"\n{'=' * 40}") logger.info("Running verification...") logger.info('=' * 40) try: verify_migration.run_verification(source_conn_str, target_conn_str) results['verification'] = 'SUCCESS' except Exception as e: results['verification'] = f'FAILED: {e}' logger.error(f"Verification failed: {e}") # Summary logger.info("\n" + "=" * 60) logger.info("MIGRATION SUMMARY") logger.info("=" * 60) for step, result in results.items(): status = "OK" if result == 'SUCCESS' else "FAILED" logger.info(f" {step}: {status}") if result != 'SUCCESS': logger.info(f" {result}") logger.info("=" * 60) logger.info(f"Completed: {datetime.utcnow().isoformat()}") return results def main(): parser = argparse.ArgumentParser(description='Run full ShopDB migration') parser.add_argument('--source', required=True, help='Source database connection string') parser.add_argument('--target', help='Target database connection string') parser.add_argument('--dry-run', action='store_true', help='Dry run without committing') parser.add_argument('--steps', nargs='+', choices=['assets', 'communications', 'notifications', 'usb'], help='Specific steps to run') args = parser.parse_args() target = args.target if not target: import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) from shopdb import create_app app = create_app() target = app.config['SQLALCHEMY_DATABASE_URI'] results = run_full_migration(args.source, target, args.dry_run, args.steps) # Exit with error if any step failed if any(r != 'SUCCESS' for r in results.values()): sys.exit(1) if __name__ == '__main__': main()