migrate_all.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. from __future__ import annotations
  2. import argparse
  3. import os
  4. import subprocess
  5. import sys
  6. from dataclasses import dataclass
  7. from pathlib import Path
  8. DEFAULT_SERVICE_ORDER = [
  9. "workflow-service",
  10. "session-service",
  11. "tool-service",
  12. "runtime-service",
  13. "memory-service",
  14. "skill-service",
  15. "agent-service",
  16. "team-service",
  17. "human-service",
  18. "knowledge-service",
  19. "event-service",
  20. "auth-service",
  21. "scheduler-service",
  22. "api-gateway",
  23. ]
  24. @dataclass(frozen=True)
  25. class MigrationTarget:
  26. service_name: str
  27. service_path: Path
  28. alembic_ini_path: Path
  29. def main() -> int:
  30. args = parse_args()
  31. repo_root = Path(__file__).resolve().parents[1]
  32. targets = discover_targets(
  33. repo_root=repo_root,
  34. only_services=args.only,
  35. skip_missing=args.skip_missing,
  36. )
  37. if args.dry_run:
  38. for target in targets:
  39. print(f"{target.service_name}: {target.alembic_ini_path}")
  40. return 0
  41. failed_services: list[str] = []
  42. for target in targets:
  43. print(f"==> migrating {target.service_name}", flush=True)
  44. result = run_alembic_upgrade(target=target, python_executable=args.python)
  45. if result.returncode != 0:
  46. failed_services.append(target.service_name)
  47. if not args.continue_on_error:
  48. break
  49. if failed_services:
  50. print("migration failed for: " + ", ".join(failed_services), file=sys.stderr)
  51. return 1
  52. print(f"migrated {len(targets)} service(s)")
  53. return 0
  54. def parse_args() -> argparse.Namespace:
  55. parser = argparse.ArgumentParser(description="Run Alembic migrations for all services.")
  56. parser.add_argument(
  57. "--only",
  58. action="append",
  59. default=[],
  60. help="Only migrate a service. Can be passed multiple times.",
  61. )
  62. parser.add_argument(
  63. "--python",
  64. default=sys.executable,
  65. help="Python executable to use for `python -m alembic`.",
  66. )
  67. parser.add_argument(
  68. "--continue-on-error",
  69. action="store_true",
  70. help="Continue migrating remaining services if one migration fails.",
  71. )
  72. parser.add_argument(
  73. "--skip-missing",
  74. action="store_true",
  75. help="Skip services without alembic.ini instead of failing.",
  76. )
  77. parser.add_argument(
  78. "--dry-run",
  79. action="store_true",
  80. help="Print migration targets without executing migrations.",
  81. )
  82. return parser.parse_args()
  83. def discover_targets(
  84. *,
  85. repo_root: Path,
  86. only_services: list[str],
  87. skip_missing: bool,
  88. ) -> list[MigrationTarget]:
  89. requested_services = only_services or DEFAULT_SERVICE_ORDER
  90. targets: list[MigrationTarget] = []
  91. for service_name in requested_services:
  92. service_path = repo_root / "services" / service_name
  93. alembic_ini_path = service_path / "alembic.ini"
  94. if not alembic_ini_path.exists():
  95. if skip_missing:
  96. continue
  97. raise FileNotFoundError(f"alembic.ini not found for service: {service_name}")
  98. targets.append(
  99. MigrationTarget(
  100. service_name=service_name,
  101. service_path=service_path,
  102. alembic_ini_path=alembic_ini_path,
  103. )
  104. )
  105. return targets
  106. def run_alembic_upgrade(
  107. *,
  108. target: MigrationTarget,
  109. python_executable: str,
  110. ) -> subprocess.CompletedProcess[str]:
  111. env = os.environ.copy()
  112. result = subprocess.run(
  113. [python_executable, "-m", "alembic", "upgrade", "head"],
  114. cwd=target.service_path,
  115. env=env,
  116. text=True,
  117. check=False,
  118. )
  119. return result
  120. if __name__ == "__main__":
  121. raise SystemExit(main())