feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile

- Добавлена колонка 'Тип' во все таблицы истории сборок
- Для push операций отображается registry вместо платформ
- Сохранение пользователя при создании push лога
- Исправлена ошибка с logger в push_docker_image endpoint
- Улучшено отображение истории сборок с визуальными индикаторами
This commit is contained in:
Сергей Антропов
2026-02-15 22:59:02 +03:00
parent 23e1a6037b
commit 1fbf9185a2
232 changed files with 38075 additions and 5 deletions

View File

@@ -0,0 +1,148 @@
"""
Исполнитель Ansible команд
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
import asyncio
import subprocess
from pathlib import Path
from typing import Optional, AsyncGenerator, Dict, List
from datetime import datetime
from app.core.config import settings
class AnsibleExecutor:
"""Выполнение Ansible команд без использования Makefile"""
def __init__(self):
self.project_root = settings.PROJECT_ROOT
async def run_playbook(
self,
playbook_path: str,
inventory: str,
tags: Optional[List[str]] = None,
limit: Optional[str] = None,
check: bool = False,
extra_vars: Optional[Dict] = None,
stream: bool = False
) -> AsyncGenerator[str, None]:
"""
Запуск ansible-playbook
Args:
playbook_path: Путь к playbook файлу
inventory: Путь к inventory файлу
tags: Список тегов для фильтрации задач
limit: Ограничение на хосты
check: Режим dry-run (--check)
extra_vars: Дополнительные переменные
stream: Если True, возвращает генератор строк
Yields:
Строки вывода команды
"""
cmd = ["ansible-playbook", playbook_path, "-i", inventory]
if tags:
cmd.extend(["--tags", ",".join(tags)])
if limit:
cmd.extend(["--limit", limit])
if check:
cmd.append("--check")
if extra_vars:
import json
cmd.extend(["-e", json.dumps(extra_vars)])
# Добавляем цветной вывод
env = {"ANSIBLE_FORCE_COLOR": "1"}
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
cwd=str(self.project_root),
env=env
)
async for line in process.stdout:
yield line.decode('utf-8', errors='replace')
await process.wait()
async def run_ad_hoc(
self,
module: str,
args: str,
inventory: str,
hosts: Optional[str] = None,
stream: bool = False
) -> AsyncGenerator[str, None]:
"""
Запуск ad-hoc команды ansible
Args:
module: Имя модуля (например, 'ping', 'command', 'shell')
args: Аргументы модуля
inventory: Путь к inventory файлу
hosts: Ограничение на хосты (по умолчанию 'all')
stream: Если True, возвращает генератор строк
Yields:
Строки вывода команды
"""
cmd = ["ansible"]
if hosts:
cmd.extend([hosts])
else:
cmd.append("all")
cmd.extend(["-m", module, "-a", args, "-i", inventory])
# Добавляем цветной вывод
env = {"ANSIBLE_FORCE_COLOR": "1"}
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
cwd=str(self.project_root),
env=env
)
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", "unreachable"]):
return "error"
elif any(word in line_lower for word in ["warning", "warn", "skipping"]):
return "warning"
elif any(word in line_lower for word in ["changed", "ok", "success"]):
return "info"
else:
return "debug"
def parse_play_recap(self, line: str) -> Dict:
"""Парсинг PLAY RECAP из Ansible вывода"""
import re
# Пример: "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 {}