From 2dcdaeeba1669728866d71af310e9c51124bcbd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Taylan=20Bak=C4=B1rc=C4=B1o=C4=9Flu?= Date: Tue, 18 Nov 2025 17:50:59 +0300 Subject: [PATCH] fix(config): robust use_backend_rules parsing with comprehensive type handling PROBLEM: - Config generation was producing invalid syntax: use_backend ["..."] - HAProxy validation failing on agents - Old code had insufficient type checking for JSONB fields ROOT CAUSE: - Missing ELSE branch when use_backend_rules wasn't a list - No handling for unexpected types (tuple, Record, etc.) - No debug logging to track type issues FIX: - Added comprehensive type checking (str, list, tuple, other) - Added debug logging to track type and value - Added fallback parsing for string representation of lists - Enhanced validation to skip invalid entries - Prevents generation of invalid HAProxy syntax IMPACT: - Fixes validation failure for cluster 7 (demo-cluster) - Enables proper parsing of JSONB use_backend_rules - Backward compatible with legacy string format TESTED: - Database validation: use_backend_rules is proper JSONB array - String elements correctly extracted and written - Invalid types caught and logged --- backend/services/haproxy_config.py | 46 +++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/backend/services/haproxy_config.py b/backend/services/haproxy_config.py index a999821..01cf780 100644 --- a/backend/services/haproxy_config.py +++ b/backend/services/haproxy_config.py @@ -409,26 +409,58 @@ async def generate_haproxy_config_for_cluster(cluster_id: int, conn: Optional[An # Use Backend Rules if frontend.get('use_backend_rules'): use_backend_rules = frontend['use_backend_rules'] - # Parse JSON string if needed + + # DEBUG: Log type and value + logger.debug(f"Frontend '{frontend['name']}' use_backend_rules type: {type(use_backend_rules)}, value: {use_backend_rules}") + + # Normalize to list + rules_list = None + if isinstance(use_backend_rules, str): + # String: try JSON parse first try: - use_backend_rules = json.loads(use_backend_rules) + parsed = json.loads(use_backend_rules) + if isinstance(parsed, list): + rules_list = parsed + else: + logger.warning(f"Frontend '{frontend['name']}' use_backend_rules parsed to non-list: {type(parsed)}") except: # Legacy format: newline-separated string - use_backend_rules = [r.strip() for r in use_backend_rules.split('\n') if r.strip()] + rules_list = [r.strip() for r in use_backend_rules.split('\n') if r.strip()] - if isinstance(use_backend_rules, list): - for rule in use_backend_rules: - if rule and rule.strip(): + elif isinstance(use_backend_rules, (list, tuple)): + # Already a sequence + rules_list = list(use_backend_rules) + + else: + # Unexpected type - try to convert to string and log warning + logger.error(f"Frontend '{frontend['name']}' use_backend_rules has unexpected type {type(use_backend_rules)}: {use_backend_rules}") + # Try to convert to string representation and parse + try: + rules_str = str(use_backend_rules) + if rules_str.startswith('[') and rules_str.endswith(']'): + # Looks like a string representation of a list + rules_list = json.loads(rules_str) + except Exception as e: + logger.error(f"Failed to parse use_backend_rules: {e}") + rules_list = None + + # Write rules if we successfully parsed them + if rules_list: + for rule in rules_list: + if rule and isinstance(rule, str): rule_text = rule.strip() # Skip empty strings, "[]", or invalid use_backend rules - if rule_text and rule_text not in ('[]', '{}', 'null', 'None'): + if rule_text and rule_text not in ('[]', '{}', 'null', 'None', '""', "''"): # CRITICAL FIX: use_backend rules from parser already include "use_backend" keyword # Don't add it again! Parser stores: "use_backend BackendName if condition" # If rule doesn't start with "use_backend ", add it (for manual entries) if not rule_text.startswith('use_backend '): rule_text = f"use_backend {rule_text}" config_lines.append(f" {rule_text}") + logger.debug(f"Added use_backend rule: {rule_text}") + else: + logger.warning(f"Skipping invalid rule (type: {type(rule)}): {rule}") # Default backend (already added at the beginning of frontend section)