"""Configuration loader for Airflow DAG Monitor.""" import dataclasses import pathlib import yaml @dataclasses.dataclass class AirflowConfig: """Airflow API connection settings.""" base_url: str = "http://localhost:8080" username: str = "airflow" password: str = "airflow" api_version: str = "auto" # "auto", "v1", or "experimental" timeout: int = 30 verify_ssl: bool = True request_delay: float = 0.1 # delay between API calls (seconds) @dataclasses.dataclass class MonitorConfig: """Monitoring behavior settings.""" cycle_interval: int = 300 # seconds between full cycles long_running_threshold: int = 1800 # 30 minutes max_failed_age: int = 86400 # ignore failed runs older than 24 hours retry_wait: int = 120 # wait after retry before re-check max_retries: int = 1 state_file: str = "/var/lib/airflow-monitor/state.json" lock_file: str = "/var/run/airflow-monitor.lock" state_max_age: int = 86400 # 24 hours @dataclasses.dataclass class ZabbixConfig: """Zabbix agent integration settings. Monitor writes data to files, Zabbix agent reads via UserParameter. """ enabled: bool = True data_dir: str = "/var/lib/airflow-monitor" problems_file: str = "problems.json" heartbeat_file: str = "heartbeat" status_file: str = "status.json" @dataclasses.dataclass class LoggingConfig: """Logging settings.""" level: str = "INFO" file: str = "/var/log/airflow-monitor/monitor.log" max_bytes: int = 10_485_760 # 10 MB backup_count: int = 5 @dataclasses.dataclass class AppConfig: """Top-level application configuration.""" airflow: AirflowConfig monitor: MonitorConfig zabbix: ZabbixConfig logging: LoggingConfig def _build_dataclass(cls, data: dict): """Build a dataclass instance from a dict, ignoring unknown keys.""" if data is None: return cls() fields = {f.name for f in dataclasses.fields(cls)} filtered = {k: v for k, v in data.items() if k in fields} return cls(**filtered) def load_config(path: str) -> AppConfig: """Load configuration from a YAML file. Missing sections fall back to defaults. Raises FileNotFoundError if the file does not exist. Raises ValueError on invalid YAML. """ config_path = pathlib.Path(path) if not config_path.exists(): raise FileNotFoundError(f"Config file not found: {path}") with open(config_path, "r", encoding="utf-8") as f: raw = yaml.safe_load(f) if not isinstance(raw, dict): raise ValueError(f"Config file must be a YAML mapping, got {type(raw).__name__}") return AppConfig( airflow=_build_dataclass(AirflowConfig, raw.get("airflow")), monitor=_build_dataclass(MonitorConfig, raw.get("monitor")), zabbix=_build_dataclass(ZabbixConfig, raw.get("zabbix")), logging=_build_dataclass(LoggingConfig, raw.get("logging")), )