|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
| 3 | +import asyncio |
3 | 4 | import json |
4 | 5 | from contextlib import asynccontextmanager |
| 6 | +from pathlib import Path |
5 | 7 | from typing import Any, AsyncIterator |
6 | 8 |
|
7 | 9 | import os |
|
18 | 20 | from proxy.policy import Tier, classify_endpoint, evaluate_policy, extract_operation, is_read_only_endpoint, is_record_endpoint, resolve_zone |
19 | 21 |
|
20 | 22 | audit_log = structlog.get_logger("proxy.audit") |
| 23 | +_reload_log = structlog.get_logger("proxy.reload") |
21 | 24 |
|
22 | 25 | _config = load_config() |
23 | 26 | setup_logging(os.environ.get("LOG_LEVEL", "info")) |
24 | 27 |
|
| 28 | +_CONFIG_PATH = Path(os.environ.get("CONFIG_PATH", "config.yml")) |
| 29 | +_RELOAD_INTERVAL = int(os.environ.get("RELOAD_INTERVAL", "5")) |
| 30 | + |
| 31 | + |
| 32 | +async def _watch_config(app: FastAPI) -> None: |
| 33 | + """Poll config file for changes and hot-reload on modification.""" |
| 34 | + last_mtime: float = _CONFIG_PATH.stat().st_mtime if _CONFIG_PATH.exists() else 0 |
| 35 | + while True: |
| 36 | + await asyncio.sleep(_RELOAD_INTERVAL) |
| 37 | + try: |
| 38 | + current_mtime = _CONFIG_PATH.stat().st_mtime |
| 39 | + if current_mtime <= last_mtime: |
| 40 | + continue |
| 41 | + last_mtime = current_mtime |
| 42 | + new_config = load_config() |
| 43 | + app.state.config = new_config |
| 44 | + _reload_log.info("config_reloaded", config_path=str(_CONFIG_PATH)) |
| 45 | + except Exception as exc: |
| 46 | + _reload_log.error("config_reload_failed", error=str(exc), config_path=str(_CONFIG_PATH)) |
| 47 | + |
25 | 48 |
|
26 | 49 | @asynccontextmanager |
27 | 50 | async def lifespan(app: FastAPI) -> AsyncIterator[None]: |
28 | 51 | app.state.config = _config |
29 | 52 | app.state.http_client = httpx.AsyncClient(verify=_config.technitium.verify_ssl) |
| 53 | + watcher = asyncio.create_task(_watch_config(app)) if _RELOAD_INTERVAL > 0 else None |
30 | 54 | yield |
| 55 | + if watcher is not None: |
| 56 | + watcher.cancel() |
31 | 57 | await app.state.http_client.aclose() |
32 | 58 |
|
33 | 59 |
|
|
0 commit comments