feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile
- Добавлена колонка 'Тип' во все таблицы истории сборок - Для push операций отображается registry вместо платформ - Сохранение пользователя при создании push лога - Исправлена ошибка с logger в push_docker_image endpoint - Улучшено отображение истории сборок с визуальными индикаторами
This commit is contained in:
148
app/core/ansible_executor.py
Normal file
148
app/core/ansible_executor.py
Normal 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 {}
|
||||
Reference in New Issue
Block a user