generate_tinyrdm_connections.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. #!/usr/bin/env python3
  2. """
  3. Generate TinyRDM connections.yaml from sdm status --json output
  4. Organizes Redis connections into Internal, Stage, and Production groups
  5. Excludes activate and readonly connections
  6. """
  7. import subprocess
  8. import json
  9. import re
  10. import os
  11. import yaml
  12. from collections import defaultdict
  13. def run_sdm_status():
  14. try:
  15. result = subprocess.run(['sdm', 'status', '--json'], capture_output=True, text=True)
  16. if result.returncode != 0:
  17. print(f"Error running sdm status: {result.stderr}")
  18. return None
  19. return json.loads(result.stdout)
  20. except FileNotFoundError:
  21. print("Error: sdm command not found")
  22. return None
  23. except json.JSONDecodeError as e:
  24. print(f"Error parsing JSON: {e}")
  25. return None
  26. def parse_redis_connections(sdm_data):
  27. connections = []
  28. excluded = ['activate-cache', 'activate-readonly', 'readonly-redis']
  29. for item in sdm_data:
  30. if item.get('type') != 'redis':
  31. continue
  32. name = item.get('name', '')
  33. if any(x in name for x in excluded):
  34. continue
  35. address = item.get('address', '')
  36. if ':' not in address:
  37. continue
  38. addr, port = address.split(':')
  39. env_info = parse_connection_name(name)
  40. if env_info:
  41. connections.append({
  42. 'name': name,
  43. 'addr': addr,
  44. 'port': int(port),
  45. **env_info
  46. })
  47. return connections
  48. def parse_connection_name(name):
  49. pattern = r'oc-([^-]+)-([^-]+)-.*'
  50. match = re.match(pattern, name)
  51. if not match:
  52. return None
  53. environment = match.group(1)
  54. customer = match.group(2)
  55. if environment in ['dev', 'nextrc', 'nextrc2']:
  56. stage = 'internal'
  57. elif environment in ['stage', 'stage2', 'uat']:
  58. stage = 'stage'
  59. elif environment in ['prod', 'prod2']:
  60. stage = 'production'
  61. else:
  62. stage = 'unknown'
  63. return {
  64. 'environment': environment,
  65. 'customer': customer,
  66. 'stage': stage
  67. }
  68. def is_sdm_connection(conn_name):
  69. """Check if connection name matches SDM pattern (oc-*-*-*)"""
  70. pattern = r'oc-([^-]+)-([^-]+)-.*'
  71. return re.match(pattern, conn_name) is not None
  72. def load_existing_connections(output_file):
  73. """Load existing connections, filtering out SDM connections"""
  74. if not os.path.exists(output_file):
  75. return {}
  76. try:
  77. with open(output_file, 'r') as f:
  78. data = yaml.safe_load(f) or []
  79. preserved_groups = {}
  80. for item in data:
  81. if not isinstance(item, dict):
  82. continue
  83. # Handle grouped connections
  84. if item.get("type") == "group":
  85. group_name = item.get("name", "")
  86. connections = item.get("connections", [])
  87. # Filter out SDM connections
  88. non_sdm_conns = []
  89. for conn in connections:
  90. conn_name = conn.get("name", "")
  91. if conn_name and not is_sdm_connection(conn_name):
  92. non_sdm_conns.append(conn)
  93. if non_sdm_conns:
  94. preserved_groups[group_name] = non_sdm_conns
  95. # Handle ungrouped connections (top-level connections)
  96. elif item.get("type") != "group" and "addr" in item and "port" in item:
  97. # This is a standalone connection (not in a group)
  98. conn_name = item.get("name", "")
  99. if conn_name and not is_sdm_connection(conn_name):
  100. # Put ungrouped connections into a special group
  101. if 'ungrouped' not in preserved_groups:
  102. preserved_groups['ungrouped'] = []
  103. preserved_groups['ungrouped'].append(item)
  104. return preserved_groups
  105. except Exception as e:
  106. print(f"⚠️ Error reading existing config: {e}")
  107. return {}
  108. def create_connection_yaml(name, addr, port, stage):
  109. colors = {
  110. 'internal': '#4ECF60',
  111. 'stage': '#FFA500',
  112. 'production': '#FF0000'
  113. }
  114. color = colors.get(stage, '#808080')
  115. return f""" - name: {name}
  116. last_db: 0
  117. network: tcp
  118. addr: {addr}
  119. port: {port}
  120. default_filter: '*'
  121. key_separator: ':'
  122. conn_timeout: 60
  123. exec_timeout: 60
  124. db_filter_type: none
  125. load_size: 10000
  126. mark_color: '{color}'
  127. refresh_interval: 5"""
  128. def group_connections(connections):
  129. groups = defaultdict(list)
  130. for conn in connections:
  131. # Group by customer (internal/features -> 'internal', otherwise by customer)
  132. if conn['customer'] == 'internal' or conn['customer'].startswith('feature'):
  133. group_name = 'internal'
  134. elif conn['stage'] in ['stage', 'production']:
  135. group_name = conn['customer']
  136. else:
  137. group_name = 'other'
  138. # Use stage directly for color coding
  139. conn_yaml = create_connection_yaml(
  140. conn['name'],
  141. conn['addr'],
  142. conn['port'],
  143. conn['stage']
  144. )
  145. groups[group_name].append(conn_yaml)
  146. return groups
  147. def format_connection_for_yaml(conn):
  148. """Format a connection dict as YAML string with proper indentation"""
  149. yaml_str = yaml.dump(conn, default_flow_style=False, sort_keys=False)
  150. # Add list marker and proper indentation
  151. lines = yaml_str.split('\n')
  152. formatted_lines = []
  153. for i, line in enumerate(lines):
  154. if line.strip():
  155. # First line should start with " - "
  156. if i == 0:
  157. formatted_lines.append(f" - {line.strip()}")
  158. else:
  159. formatted_lines.append(f" {line}")
  160. return '\n'.join(formatted_lines)
  161. def generate_yaml_content(groups, preserved_connections):
  162. """Generate YAML content from SDM groups and preserved connections"""
  163. yaml_lines = []
  164. # Prefer 'internal' first, then alphabetical by customer
  165. ordered_groups = []
  166. if 'internal' in groups:
  167. ordered_groups.append('internal')
  168. ordered_groups.extend(sorted([g for g in groups.keys() if g != 'internal']))
  169. # Add preserved groups
  170. for group_name in preserved_connections.keys():
  171. if group_name not in ordered_groups:
  172. ordered_groups.append(group_name)
  173. for group_name in ordered_groups:
  174. yaml_lines.append(f"- name: {group_name}")
  175. yaml_lines.append(" last_db: 0")
  176. yaml_lines.append(" type: group")
  177. yaml_lines.append(" connections:")
  178. # Add connections (preserved first, then SDM)
  179. if group_name in preserved_connections:
  180. for conn_dict in preserved_connections[group_name]:
  181. yaml_lines.append(format_connection_for_yaml(conn_dict))
  182. if group_name in groups:
  183. for conn_yaml in groups[group_name]:
  184. yaml_lines.append(conn_yaml)
  185. yaml_lines.append("")
  186. return '\n'.join(yaml_lines)
  187. def main():
  188. print("Generating TinyRDM connections.yaml from sdm status --json...")
  189. output_file = os.path.expanduser('~/.config/TinyRDM/connections.yaml')
  190. # Load existing connections to preserve non-SDM connections
  191. preserved_connections = load_existing_connections(output_file)
  192. preserved_count = sum(len(conns) for conns in preserved_connections.values())
  193. if preserved_count > 0:
  194. print(f"📁 Found {preserved_count} existing non-SDM connections")
  195. sdm_data = run_sdm_status()
  196. if not sdm_data:
  197. return
  198. connections = parse_redis_connections(sdm_data)
  199. print(f"🔍 Found {len(connections)} Redis connections from SDM")
  200. groups = group_connections(connections)
  201. for group_name, conns in groups.items():
  202. print(f" {group_name}: {len(conns)} connections")
  203. yaml_content = generate_yaml_content(groups, preserved_connections)
  204. try:
  205. with open(output_file, 'w') as f:
  206. f.write(yaml_content)
  207. print(f"✅ Successfully generated {output_file}")
  208. if preserved_count > 0:
  209. print(f"💾 Preserved {preserved_count} non-SDM connections")
  210. except Exception as e:
  211. print(f"❌ Error writing file: {e}")
  212. if __name__ == '__main__':
  213. main()