"""Dedicated allowlisted Ansible execution broker process.""" from __future__ import annotations import asyncio import signal from pathlib import Path from typing import Any import structlog from src.core.config import settings from src.core.redis_client import close_redis_pool, init_redis_pool from src.db.base import close_db from src.jobs.awooop_ansible_check_mode_job import ( run_awooop_ansible_check_mode_loop, ) logger = structlog.get_logger(__name__) _HEALTH_PATH = Path("/tmp/executor-broker-healthy") _READY_PATH = Path("/tmp/executor-broker-ready") async def _heartbeat_loop(shutdown_event: asyncio.Event) -> None: while not shutdown_event.is_set(): _HEALTH_PATH.touch() try: await asyncio.wait_for(shutdown_event.wait(), timeout=15) except TimeoutError: pass async def _init_optional_projection_redis() -> bool: """Keep the controlled executor available when the Redis read model is down.""" try: await init_redis_pool() return True except Exception as exc: logger.warning( "ansible_execution_broker_redis_projection_degraded", error_type=type(exc).__name__, controlled_executor_available=True, durable_source="postgresql", ) return False async def _main() -> None: if not settings.ENABLE_AWOOOP_ANSIBLE_CHECK_MODE_WORKER: raise RuntimeError("ansible_execution_broker_disabled_by_config") ssh_key_path = Path(settings.AWOOOP_ANSIBLE_CHECK_MODE_SSH_KEY_PATH) known_hosts_path = Path(settings.AWOOOP_ANSIBLE_CHECK_MODE_KNOWN_HOSTS_PATH) if not ssh_key_path.is_file() or not known_hosts_path.is_file(): raise RuntimeError("ansible_execution_broker_transport_not_mounted") redis_projection_ready = await _init_optional_projection_redis() shutdown_event = asyncio.Event() def _shutdown_handler(signum: int, _frame: Any) -> None: logger.info("ansible_execution_broker_shutdown_requested", signal=signum) shutdown_event.set() signal.signal(signal.SIGTERM, _shutdown_handler) signal.signal(signal.SIGINT, _shutdown_handler) _HEALTH_PATH.touch() _READY_PATH.touch() heartbeat_task = asyncio.create_task( _heartbeat_loop(shutdown_event), name="ansible_execution_broker_heartbeat", ) execution_task = asyncio.create_task( run_awooop_ansible_check_mode_loop(), name="run_awooop_ansible_check_mode_loop", ) shutdown_wait_task = asyncio.create_task( shutdown_event.wait(), name="ansible_execution_broker_shutdown_wait", ) logger.info( "ansible_execution_broker_started", interval_seconds=settings.AWOOOP_ANSIBLE_CHECK_MODE_INTERVAL_SECONDS, batch_limit=settings.AWOOOP_ANSIBLE_CHECK_MODE_BATCH_LIMIT, allowed_risk_levels=( settings.AWOOOP_ANSIBLE_CONTROLLED_APPLY_ALLOWED_RISK_LEVELS ), worker_raw_credential_access=False, redis_projection_ready=redis_projection_ready, redis_projection_blocks_executor=False, ) try: done, _pending = await asyncio.wait( {execution_task, shutdown_wait_task}, return_when=asyncio.FIRST_COMPLETED, ) if execution_task in done and not shutdown_event.is_set(): _READY_PATH.unlink(missing_ok=True) await execution_task raise RuntimeError("ansible_execution_broker_loop_exited_unexpectedly") finally: shutdown_wait_task.cancel() execution_task.cancel() heartbeat_task.cancel() for task in (shutdown_wait_task, execution_task, heartbeat_task): try: await task except asyncio.CancelledError: pass await close_redis_pool() await close_db() _HEALTH_PATH.unlink(missing_ok=True) _READY_PATH.unlink(missing_ok=True) logger.info("ansible_execution_broker_stopped") if __name__ == "__main__": asyncio.run(_main())