""" Выполнение Makefile команд Автор: Сергей Антропов Сайт: https://devops.org.ru """ import subprocess import asyncio from typing import Dict, List, Optional, AsyncGenerator from pathlib import Path from app.core.config import settings from datetime import datetime import json import re class MakeExecutor: """Выполнение Makefile команд с отслеживанием прогресса""" def __init__(self): self.project_root = settings.PROJECT_ROOT def detect_command_type(self, command: str) -> str: """Определение типа команды""" if "test" in command: return "test" elif "deploy" in command: return "deploy" elif "export" in command: return "export" elif "import" in command: return "import" elif "lint" in command: return "lint" else: return "other" def parse_parameters(self, command: str, args: List[str] = None) -> dict: """Парсинг параметров команды""" params = {} # Парсинг команды parts = command.split() if len(parts) > 2: params["role_name"] = parts[2] if len(parts) > 3: params["preset"] = parts[3] # Парсинг аргументов if args: for i, arg in enumerate(args): if arg.startswith("--"): key = arg[2:].replace("-", "_") if i + 1 < len(args) and not args[i + 1].startswith("--"): params[key] = args[i + 1] else: params[key] = True return params async def execute( self, command: str, args: List[str] = None, user: str = None ) -> Dict: """ Выполнение команды make Args: command: Команда make (например, "role test nginx default") args: Дополнительные аргументы user: Пользователь, выполнивший команду Returns: Словарь с результатами выполнения """ cmd = ["make"] + command.split() + (args or []) try: result = subprocess.run( cmd, capture_output=True, text=True, cwd=self.project_root, timeout=3600 # 1 час максимум ) return { "success": result.returncode == 0, "stdout": result.stdout, "stderr": result.stderr, "returncode": result.returncode, "command": " ".join(cmd), "timestamp": datetime.now().isoformat() } except subprocess.TimeoutExpired: return { "success": False, "stdout": "", "stderr": "Command timeout after 1 hour", "returncode": -1, "command": " ".join(cmd), "timestamp": datetime.now().isoformat() } except Exception as e: return { "success": False, "stdout": "", "stderr": str(e), "returncode": -1, "command": " ".join(cmd), "timestamp": datetime.now().isoformat() } async def execute_stream( self, command: str, args: List[str] = None ) -> AsyncGenerator[str, None]: """ Выполнение команды с потоковым выводом для WebSocket Args: command: Команда make args: Дополнительные аргументы Yields: Строки вывода команды """ cmd = ["make"] + command.split() + (args or []) process = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, cwd=str(self.project_root) ) async for line in process.stdout: yield line.decode('utf-8', errors='replace') await process.wait() def detect_log_level(self, line: str) -> str: """Определение уровня лога из строки""" line_lower = line.lower() if any(word in line_lower for word in ["error", "failed", "fatal"]): return "error" elif any(word in line_lower for word in ["warning", "warn"]): return "warning" elif any(word in line_lower for word in ["changed", "ok"]): return "info" else: return "debug" def parse_play_recap(self, line: str) -> dict: """Парсинг PLAY RECAP из Ansible вывода""" # Пример: "web1.example.com : ok=5 changed=3 unreachable=0 failed=0" match = re.search(r'(\S+)\s*:\s*ok=(\d+)\s+changed=(\d+).*failed=(\d+)', line) if match: return { "host": match.group(1), "ok": int(match.group(2)), "changed": int(match.group(3)), "failed": int(match.group(4)), "status": "success" if int(match.group(4)) == 0 else "failed" } return {}