"""Configuration constants and environment variables for HAProxy MCP Server.""" import os import re import logging # Configure structured logging log_level = os.getenv("LOG_LEVEL", "INFO").upper() logging.basicConfig( level=getattr(logging, log_level, logging.INFO), format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) logger = logging.getLogger("haproxy_mcp") # MCP Server configuration MCP_HOST: str = os.getenv("MCP_HOST", "0.0.0.0") MCP_PORT: int = int(os.getenv("MCP_PORT", "8000")) # HAProxy Runtime API configuration HAPROXY_HOST: str = os.getenv("HAPROXY_HOST", "localhost") HAPROXY_PORT: int = int(os.getenv("HAPROXY_PORT", "9999")) HAPROXY_SOCKET: tuple[str, int] = (HAPROXY_HOST, HAPROXY_PORT) # File paths (configurable via environment) STATE_FILE: str = os.getenv("HAPROXY_STATE_FILE", "/opt/haproxy/data/servers.state") MAP_FILE: str = os.getenv("HAPROXY_MAP_FILE", "/opt/haproxy/conf/domains.map") MAP_FILE_CONTAINER: str = os.getenv("HAPROXY_MAP_FILE_CONTAINER", "/usr/local/etc/haproxy/domains.map") # Wildcards map for 2-stage matching (map_dom fallback) WILDCARDS_MAP_FILE: str = os.getenv("HAPROXY_WILDCARDS_MAP_FILE", "/opt/haproxy/conf/wildcards.map") WILDCARDS_MAP_FILE_CONTAINER: str = os.getenv("HAPROXY_WILDCARDS_MAP_FILE_CONTAINER", "/usr/local/etc/haproxy/wildcards.map") SERVERS_FILE: str = os.getenv("HAPROXY_SERVERS_FILE", "/opt/haproxy/conf/servers.json") CERTS_FILE: str = os.getenv("HAPROXY_CERTS_FILE", "/opt/haproxy/conf/certificates.json") # Pool configuration POOL_COUNT: int = int(os.getenv("HAPROXY_POOL_COUNT", "100")) MAX_SLOTS: int = int(os.getenv("HAPROXY_MAX_SLOTS", "10")) # Container configuration HAPROXY_CONTAINER: str = os.getenv("HAPROXY_CONTAINER", "haproxy") # Validation patterns - compiled once for performance DOMAIN_PATTERN = re.compile( r'^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?' r'(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$' ) # Backend/server names: alphanumeric, underscore, hyphen only BACKEND_NAME_PATTERN = re.compile(r'^[a-zA-Z0-9_-]+$') # Pattern for converting domain to backend name NON_ALNUM_PATTERN = re.compile(r'[^a-zA-Z0-9]') # Limits MAX_RESPONSE_SIZE = 10 * 1024 * 1024 # 10 MB max response from HAProxy SUBPROCESS_TIMEOUT = 30 # seconds STARTUP_RETRY_COUNT = 10 # HAProxy ready check retries STATE_MIN_COLUMNS = 19 # Minimum columns in HAProxy server state output SOCKET_TIMEOUT = 5 # seconds for HAProxy socket connection SOCKET_RECV_TIMEOUT = 30 # seconds for HAProxy socket recv loop MAX_BULK_SERVERS = 10 # Max servers per bulk add call MAX_SERVERS_JSON_SIZE = 10000 # Max size of servers JSON in haproxy_add_servers # CSV field indices for HAProxy stats (show stat command) class StatField: """HAProxy CSV stat field indices.""" PXNAME = 0 # Proxy name (frontend/backend) SVNAME = 1 # Server name (or FRONTEND/BACKEND) SCUR = 4 # Current sessions SMAX = 6 # Max sessions STATUS = 17 # Status (UP/DOWN/MAINT/etc) WEIGHT = 18 # Server weight CHECK_STATUS = 36 # Check status # Field indices for HAProxy server state (show servers state command) class StateField: """HAProxy server state field indices.""" BE_ID = 0 # Backend ID BE_NAME = 1 # Backend name SRV_ID = 2 # Server ID SRV_NAME = 3 # Server name SRV_ADDR = 4 # Server address SRV_OP_STATE = 5 # Operational state SRV_ADMIN_STATE = 6 # Admin state SRV_PORT = 18 # Server port