scan_repos.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. """
  2. Scheduled Task — Repository Scanner
  3. ====================================
  4. Periodically scans all admin-accessible Gogs repositories,
  5. detects repos with a ``manifest.yaml``, and auto-configures
  6. the Data Nexus webhook so data pushes are captured automatically.
  7. Usage
  8. -----
  9. Run directly::
  10. python -m app.tasks.scan_repos # single run
  11. python -m app.tasks.scan_repos --loop # loop with interval
  12. Or import and call programmatically::
  13. from app.tasks.scan_repos import run_once
  14. await run_once()
  15. """
  16. import asyncio
  17. import argparse
  18. import logging
  19. import sys
  20. from app.services.repo_scanner import RepoScanner
  21. # ── Logging ──────────────────────────────────────────────────────────
  22. logging.basicConfig(
  23. level=logging.INFO,
  24. format="%(asctime)s %(levelname)-8s %(name)s %(message)s",
  25. datefmt="%Y-%m-%d %H:%M:%S",
  26. )
  27. logger = logging.getLogger("scan_repos")
  28. # ── Default scan interval (seconds) ─────────────────────────────────
  29. DEFAULT_INTERVAL_SECONDS = 60 * 60 # 1 hour
  30. # ── Core routines ───────────────────────────────────────────────────
  31. async def run_once() -> None:
  32. """Execute a single scan‑and‑configure cycle."""
  33. scanner = RepoScanner()
  34. result = await scanner.scan_and_configure()
  35. logger.info("=" * 60)
  36. logger.info(" Scan Summary")
  37. logger.info("-" * 60)
  38. logger.info(f" Total repos discovered : {result.total_repos}")
  39. logger.info(f" Admin repos : {result.admin_repos}")
  40. logger.info(f" With manifest.yaml : {result.manifest_repos}")
  41. logger.info(f" Webhooks created : {result.webhooks_created}")
  42. logger.info(f" Webhooks skipped (dup) : {result.webhooks_skipped}")
  43. logger.info(f" Errors : {result.errors}")
  44. logger.info("=" * 60)
  45. async def run_loop(interval: int = DEFAULT_INTERVAL_SECONDS) -> None:
  46. """Run the scan repeatedly with a fixed delay between cycles."""
  47. logger.info(f"Starting scan loop (interval={interval}s)")
  48. while True:
  49. try:
  50. await run_once()
  51. except Exception as exc:
  52. logger.error(f"Scan cycle failed: {exc}", exc_info=True)
  53. logger.info(f"Next scan in {interval} seconds …")
  54. await asyncio.sleep(interval)
  55. # ── CLI entry‑point ─────────────────────────────────────────────────
  56. def main() -> None:
  57. parser = argparse.ArgumentParser(
  58. description="Scan Gogs repos and auto-configure Data Nexus webhooks.",
  59. )
  60. parser.add_argument(
  61. "--loop",
  62. action="store_true",
  63. help="Run continuously with a fixed interval (default: 1 hour).",
  64. )
  65. parser.add_argument(
  66. "--interval",
  67. type=int,
  68. default=DEFAULT_INTERVAL_SECONDS,
  69. help=f"Interval in seconds between scans (default: {DEFAULT_INTERVAL_SECONDS}).",
  70. )
  71. args = parser.parse_args()
  72. if args.loop:
  73. asyncio.run(run_loop(interval=args.interval))
  74. else:
  75. asyncio.run(run_once())
  76. if __name__ == "__main__":
  77. main()