from __future__ import annotations import os from collections.abc import Mapping from dataclasses import dataclass from urllib.parse import urlparse class HostAgentConfigurationError(ValueError): """Raised when Host Agent process configuration is invalid.""" @dataclass(frozen=True) class HostAgentConfig: control_plane_url: str host_id: str token: str heartbeat_interval_seconds: float = 30.0 poll_timeout_seconds: float = 20.0 retry_backoff_seconds: float = 1.0 max_retry_backoff_seconds: float = 30.0 def load_host_agent_config( env: Mapping[str, str] | None = None, ) -> HostAgentConfig: values = os.environ if env is None else env control_plane_url = values.get( "HOST_AGENT_CONTROL_PLANE_URL", "http://127.0.0.1:8001", ).strip().rstrip("/") parsed_url = urlparse(control_plane_url) if parsed_url.scheme not in {"http", "https"} or not parsed_url.netloc: raise HostAgentConfigurationError( "HOST_AGENT_CONTROL_PLANE_URL must be an HTTP(S) URL" ) host_id = values.get("HOST_AGENT_HOST_ID", "").strip() if not host_id: raise HostAgentConfigurationError("HOST_AGENT_HOST_ID is required") token = values.get("HOST_AGENT_TOKEN", "").strip() if not token: raise HostAgentConfigurationError("HOST_AGENT_TOKEN is required") config = HostAgentConfig( control_plane_url=control_plane_url, host_id=host_id, token=token, heartbeat_interval_seconds=_positive_float( values, "HOST_AGENT_HEARTBEAT_INTERVAL_SECONDS", 30.0, ), poll_timeout_seconds=_positive_float( values, "HOST_AGENT_POLL_TIMEOUT_SECONDS", 20.0, ), retry_backoff_seconds=_positive_float( values, "HOST_AGENT_RETRY_BACKOFF_SECONDS", 1.0, ), max_retry_backoff_seconds=_positive_float( values, "HOST_AGENT_MAX_RETRY_BACKOFF_SECONDS", 30.0, ), ) if config.max_retry_backoff_seconds < config.retry_backoff_seconds: raise HostAgentConfigurationError( "maximum retry backoff must not be less than initial backoff" ) return config def _positive_float( values: Mapping[str, str], name: str, default: float, ) -> float: raw_value = values.get(name) if raw_value is None: return default try: value = float(raw_value) except ValueError as exc: raise HostAgentConfigurationError(f"{name} must be a number") from exc if value <= 0: raise HostAgentConfigurationError(f"{name} must be greater than zero") return value