- Добавлена колонка 'Тип' во все таблицы истории сборок - Для push операций отображается registry вместо платформ - Сохранение пользователя при создании push лога - Исправлена ошибка с logger в push_docker_image endpoint - Улучшено отображение истории сборок с визуальными индикаторами
149 lines
4.8 KiB
Python
149 lines
4.8 KiB
Python
"""
|
|
Исполнитель 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 {}
|