Files
DevOpsLab/app/core/make_executor.py
Сергей Антропов 1fbf9185a2 feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile
- Добавлена колонка 'Тип' во все таблицы истории сборок
- Для push операций отображается registry вместо платформ
- Сохранение пользователя при создании push лога
- Исправлена ошибка с logger в push_docker_image endpoint
- Улучшено отображение истории сборок с визуальными индикаторами
2026-02-15 22:59:02 +03:00

170 lines
5.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Выполнение 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 {}